Merge pull request #577 from quantified-uncertainty/DateTime-integration

Simple dateTime integration
This commit is contained in:
Ozzie Gooen 2022-05-24 16:03:57 -04:00 committed by GitHub
commit 7b865c95f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 197 additions and 3 deletions

View File

@ -189,6 +189,19 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
{expression.value.map((r) => `"${r}"`).join(", ")}
</VariableBox>
);
case "date":
return (
<VariableBox heading="Date" showTypes={showTypes}>
{expression.value.toDateString()}
</VariableBox>
);
case "timeDuration": {
return (
<VariableBox heading="Time Duration" showTypes={showTypes}>
<NumberShower precision={3} number={expression.value} />
</VariableBox>
);
}
case "lambda":
return (
<FunctionChart

View File

@ -181,5 +181,9 @@ function createTsExport(
return tag("string", x.value);
case "EvSymbol":
return tag("symbol", x.value);
case "EvDate":
return tag("date", x.value);
case "EvTimeDuration":
return tag("timeDuration", x.value);
}
}

View File

@ -55,6 +55,14 @@ export type rescriptExport =
| {
TAG: 9; // EvSymbol
_0: string;
}
| {
TAG: 10; // EvDate
_0: Date;
}
| {
TAG: 11; // EvTimeDuration
_0: number;
};
type rescriptDist =
@ -86,6 +94,8 @@ export type squiggleExpression =
| tagged<"boolean", boolean>
| tagged<"distribution", Distribution>
| tagged<"number", number>
| tagged<"date", Date>
| tagged<"timeDuration", number>
| tagged<"record", { [key: string]: squiggleExpression }>;
export { lambdaValue };
@ -127,6 +137,10 @@ export function convertRawToTypescript(
return tag("string", result._0);
case 9: // EvSymbol
return tag("symbol", result._0);
case 10: // EvDate
return tag("date", result._0);
case 11: // EvTimeDuration
return tag("number", result._0);
}
}

View File

@ -0,0 +1,70 @@
module EV = ReducerInterface_ExpressionValue
type expressionValue = EV.expressionValue
let dateDispatch = (call: EV.functionCall, _: DistributionOperation.env): option<
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
> => {
switch call {
| ("toString", [EvDate(t)]) => EV.EvString(DateTime.Date.toString(t))->Ok->Some
| ("makeDateFromYear", [EvNumber(year)]) =>
switch DateTime.Date.makeFromYear(year) {
| Ok(t) => EV.EvDate(t)->Ok->Some
| Error(e) => Reducer_ErrorValue.RETodo(e)->Error->Some
}
| ("dateFromNumber", [EvNumber(f)]) => EV.EvDate(DateTime.Date.fromFloat(f))->Ok->Some
| ("toNumber", [EvDate(f)]) => EV.EvNumber(DateTime.Date.toFloat(f))->Ok->Some
| ("subtract", [EvDate(d1), EvDate(d2)]) =>
switch DateTime.Date.subtract(d1, d2) {
| Ok(d) => EV.EvTimeDuration(d)->Ok
| Error(e) => Error(RETodo(e))
}->Some
| ("subtract", [EvDate(d1), EvTimeDuration(d2)]) =>
EV.EvDate(DateTime.Date.subtractDuration(d1, d2))->Ok->Some
| ("add", [EvDate(d1), EvTimeDuration(d2)]) =>
EV.EvDate(DateTime.Date.addDuration(d1, d2))->Ok->Some
| _ => None
}
}
let durationDispatch = (call: EV.functionCall, _: DistributionOperation.env): option<
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
> => {
switch call {
| ("toString", [EvTimeDuration(t)]) => EV.EvString(DateTime.Duration.toString(t))->Ok->Some
| ("minutes", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromMinutes(f))->Ok->Some
| ("fromUnit_minutes", [EvNumber(f)]) =>
EV.EvTimeDuration(DateTime.Duration.fromMinutes(f))->Ok->Some
| ("hours", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromHours(f))->Ok->Some
| ("fromUnit_hours", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromHours(f))->Ok->Some
| ("days", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromDays(f))->Ok->Some
| ("fromUnit_days", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromDays(f))->Ok->Some
| ("years", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromYears(f))->Ok->Some
| ("fromUnit_years", [EvNumber(f)]) => EV.EvTimeDuration(DateTime.Duration.fromYears(f))->Ok->Some
| ("toHours", [EvTimeDuration(f)]) => EV.EvNumber(DateTime.Duration.toHours(f))->Ok->Some
| ("toMinutes", [EvTimeDuration(f)]) => EV.EvNumber(DateTime.Duration.toMinutes(f))->Ok->Some
| ("toDays", [EvTimeDuration(f)]) => EV.EvNumber(DateTime.Duration.toDays(f))->Ok->Some
| ("toYears", [EvTimeDuration(f)]) => EV.EvNumber(DateTime.Duration.toYears(f))->Ok->Some
| ("add", [EvTimeDuration(d1), EvTimeDuration(d2)]) =>
EV.EvTimeDuration(DateTime.Duration.add(d1, d2))->Ok->Some
| ("subtract", [EvTimeDuration(d1), EvTimeDuration(d2)]) =>
EV.EvTimeDuration(DateTime.Duration.subtract(d1, d2))->Ok->Some
| ("multiply", [EvTimeDuration(d1), EvNumber(d2)]) =>
EV.EvTimeDuration(DateTime.Duration.multiply(d1, d2))->Ok->Some
| ("divide", [EvTimeDuration(d1), EvNumber(d2)]) =>
EV.EvTimeDuration(DateTime.Duration.divide(d1, d2))->Ok->Some
| _ => None
}
}
let dispatch = (call: EV.functionCall, env: DistributionOperation.env): option<
result<expressionValue, QuriSquiggleLang.Reducer_ErrorValue.errorValue>,
> => {
switch dateDispatch(call, env) {
| Some(r) => Some(r)
| None =>
switch durationDispatch(call, env) {
| Some(r) => Some(r)
| None => None
}
}
}

View File

@ -20,6 +20,8 @@ type rec expressionValue =
| EvRecord(record)
| EvString(string)
| EvSymbol(string)
| EvDate(Js.Date.t)
| EvTimeDuration(float)
and record = Js.Dict.t<expressionValue>
and externalBindings = record
and lambdaValue = {
@ -51,6 +53,8 @@ let rec toString = aValue =>
| EvSymbol(aString) => `:${aString}`
| EvRecord(aRecord) => aRecord->toStringRecord
| EvDistribution(dist) => GenericDist.toString(dist)
| EvDate(date) => DateTime.Date.toString(date)
| EvTimeDuration(t) => DateTime.Duration.toString(t)
}
and toStringRecord = aRecord => {
let pairs =
@ -73,6 +77,8 @@ let toStringWithType = aValue =>
| EvRecord(_) => `Record::${toString(aValue)}`
| EvString(_) => `String::${toString(aValue)}`
| EvSymbol(_) => `Symbol::${toString(aValue)}`
| EvDate(_) => `Date::${toString(aValue)}`
| EvTimeDuration(_) => `Date::${toString(aValue)}`
}
let argsToString = (args: array<expressionValue>): string => {
@ -116,6 +122,8 @@ type expressionValueType =
| EvtRecord
| EvtString
| EvtSymbol
| EvtDate
| EvtTimeDuration
type functionCallSignature = CallSignature(string, array<expressionValueType>)
type functionDefinitionSignature =
@ -133,6 +141,8 @@ let valueToValueType = value =>
| EvRecord(_) => EvtRecord
| EvString(_) => EvtArray
| EvSymbol(_) => EvtSymbol
| EvDate(_) => EvtDate
| EvTimeDuration(_) => EvtTimeDuration
}
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
@ -152,6 +162,8 @@ let valueTypeToString = (valueType: expressionValueType): string =>
| EvtRecord => `Record`
| EvtString => `String`
| EvtSymbol => `Symbol`
| EvtDate => `Date`
| EvtTimeDuration => `Duration`
}
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {

View File

@ -18,9 +18,11 @@ let dispatch = (call: ExpressionValue.functionCall, environment, chain): result<
expressionValue,
'e,
> =>
ReducerInterface_GenericDistribution.dispatch(call, environment) |> E.O.default(
chain(call, environment),
)
switch ReducerInterface_GenericDistribution.dispatch(call, environment) {
| Some(r) => r
| None =>
ReducerInterface_DateTime.dispatch(call, environment) |> E.O.default(chain(call, environment))
}
/*
If your dispatch is too big you can divide it into smaller dispatches and pass the call so that it gets called finally.

View File

@ -0,0 +1,79 @@
module Duration = {
//Stores in Unix milliseconds
type t = float
let minute = Belt.Float.fromInt(60 * 1000)
let hour = Belt.Float.fromInt(60 * 60 * 1000)
let day = Belt.Float.fromInt(24 * 60 * 60 * 1000)
let year = Belt.Float.fromInt(24 * 60 * 60 * 1000) *. 365.25
let fromFloat = (f: float): t => f
let toFloat = (d: t): float => d
let fromMinutes = (h: float): t => h *. minute
let fromHours = (h: float): t => h *. hour
let fromDays = (d: float): t => d *. day
let fromYears = (y: float): t => y *. year
let toMinutes = (t: t): float => t /. minute
let toHours = (t: t): float => t /. hour
let toDays = (t: t): float => t /. day
let toYears = (t: t): float => t /. year
let toString = (t: t): string => {
let shouldPluralize = f => f != 1.0
let display = (f: float, s: string) =>
`${E.Float.with3DigitsPrecision(f)} ${s}${shouldPluralize(f) ? "s" : ""}`
let abs = Js.Math.abs_float(t)
if abs >= year {
display(t /. year, "year")
} else if abs >= day {
display(t /. day, "day")
} else if abs >= hour {
display(t /. hour, "hour")
} else if abs >= minute {
display(t /. minute, "minute")
} else {
E.Float.toFixed(t) ++ "ms"
}
}
let add = (t1: t, t2: t): t => t1 +. t2
let subtract = (t1: t, t2: t): t => t1 -. t2
let multiply = (t1: t, t2: float): t => t1 *. t2
let divide = (t1: t, t2: float): t => t1 /. t2
}
module Date = {
//The Rescript/JS implementation of Date is pretty mediocre. It would be good to improve upon later.
type t = Js.Date.t
let toFloat = Js.Date.getTime
let getFullYear = Js.Date.getFullYear
let toString = Js.Date.toDateString
let fromFloat = Js.Date.fromFloat
let fmap = (t: t, fn: float => float) => t->toFloat->fn->fromFloat
let subtract = (t1: t, t2: t) => {
let (f1, f2) = (toFloat(t1), toFloat(t2))
let diff = f1 -. f2
if diff < 0.0 {
Error("Cannot subtract a date by one that is in its future")
} else {
Ok(Duration.fromFloat(diff))
}
}
let addDuration = (t: t, duration: Duration.t) => fmap(t, t => t +. duration)
let subtractDuration = (t: t, duration: Duration.t) => fmap(t, t => t -. duration)
//The Js.Date.makeWithYM function accepts a float, but only treats it as a whole number.
//Our version takes an integer to make this distinction clearer.
let makeWithYearInt = (y: int): result<t, string> => {
if y < 100 {
Error("Year must be over 100")
} else if y > 200000 {
Error("Year must be less than 200000")
} else {
Ok(Js.Date.makeWithYM(~year=Belt.Float.fromInt(y), ~month=0.0, ()))
}
}
let makeFromYear = (year: float): result<t, string> => {
let floor = year->Js.Math.floor_float
makeWithYearInt(Belt.Float.toInt(floor))->E.R2.fmap(earlyDate => {
let diff = year -. floor
earlyDate->addDuration(diff *. Duration.year)
})
}
}