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(", ")} {expression.value.map((r) => `"${r}"`).join(", ")}
</VariableBox> </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": case "lambda":
return ( return (
<FunctionChart <FunctionChart

View File

@ -181,5 +181,9 @@ function createTsExport(
return tag("string", x.value); return tag("string", x.value);
case "EvSymbol": case "EvSymbol":
return tag("symbol", x.value); 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 TAG: 9; // EvSymbol
_0: string; _0: string;
}
| {
TAG: 10; // EvDate
_0: Date;
}
| {
TAG: 11; // EvTimeDuration
_0: number;
}; };
type rescriptDist = type rescriptDist =
@ -86,6 +94,8 @@ export type squiggleExpression =
| tagged<"boolean", boolean> | tagged<"boolean", boolean>
| tagged<"distribution", Distribution> | tagged<"distribution", Distribution>
| tagged<"number", number> | tagged<"number", number>
| tagged<"date", Date>
| tagged<"timeDuration", number>
| tagged<"record", { [key: string]: squiggleExpression }>; | tagged<"record", { [key: string]: squiggleExpression }>;
export { lambdaValue }; export { lambdaValue };
@ -127,6 +137,10 @@ export function convertRawToTypescript(
return tag("string", result._0); return tag("string", result._0);
case 9: // EvSymbol case 9: // EvSymbol
return tag("symbol", result._0); 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) | EvRecord(record)
| EvString(string) | EvString(string)
| EvSymbol(string) | EvSymbol(string)
| EvDate(Js.Date.t)
| EvTimeDuration(float)
and record = Js.Dict.t<expressionValue> and record = Js.Dict.t<expressionValue>
and externalBindings = record and externalBindings = record
and lambdaValue = { and lambdaValue = {
@ -51,6 +53,8 @@ let rec toString = aValue =>
| EvSymbol(aString) => `:${aString}` | EvSymbol(aString) => `:${aString}`
| EvRecord(aRecord) => aRecord->toStringRecord | EvRecord(aRecord) => aRecord->toStringRecord
| EvDistribution(dist) => GenericDist.toString(dist) | EvDistribution(dist) => GenericDist.toString(dist)
| EvDate(date) => DateTime.Date.toString(date)
| EvTimeDuration(t) => DateTime.Duration.toString(t)
} }
and toStringRecord = aRecord => { and toStringRecord = aRecord => {
let pairs = let pairs =
@ -73,6 +77,8 @@ let toStringWithType = aValue =>
| EvRecord(_) => `Record::${toString(aValue)}` | EvRecord(_) => `Record::${toString(aValue)}`
| EvString(_) => `String::${toString(aValue)}` | EvString(_) => `String::${toString(aValue)}`
| EvSymbol(_) => `Symbol::${toString(aValue)}` | EvSymbol(_) => `Symbol::${toString(aValue)}`
| EvDate(_) => `Date::${toString(aValue)}`
| EvTimeDuration(_) => `Date::${toString(aValue)}`
} }
let argsToString = (args: array<expressionValue>): string => { let argsToString = (args: array<expressionValue>): string => {
@ -116,6 +122,8 @@ type expressionValueType =
| EvtRecord | EvtRecord
| EvtString | EvtString
| EvtSymbol | EvtSymbol
| EvtDate
| EvtTimeDuration
type functionCallSignature = CallSignature(string, array<expressionValueType>) type functionCallSignature = CallSignature(string, array<expressionValueType>)
type functionDefinitionSignature = type functionDefinitionSignature =
@ -133,6 +141,8 @@ let valueToValueType = value =>
| EvRecord(_) => EvtRecord | EvRecord(_) => EvtRecord
| EvString(_) => EvtArray | EvString(_) => EvtArray
| EvSymbol(_) => EvtSymbol | EvSymbol(_) => EvtSymbol
| EvDate(_) => EvtDate
| EvTimeDuration(_) => EvtTimeDuration
} }
let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => { let functionCallToCallSignature = (functionCall: functionCall): functionCallSignature => {
@ -152,6 +162,8 @@ let valueTypeToString = (valueType: expressionValueType): string =>
| EvtRecord => `Record` | EvtRecord => `Record`
| EvtString => `String` | EvtString => `String`
| EvtSymbol => `Symbol` | EvtSymbol => `Symbol`
| EvtDate => `Date`
| EvtTimeDuration => `Duration`
} }
let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => { let functionCallSignatureToString = (functionCallSignature: functionCallSignature): string => {

View File

@ -18,9 +18,11 @@ let dispatch = (call: ExpressionValue.functionCall, environment, chain): result<
expressionValue, expressionValue,
'e, 'e,
> => > =>
ReducerInterface_GenericDistribution.dispatch(call, environment) |> E.O.default( switch ReducerInterface_GenericDistribution.dispatch(call, environment) {
chain(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. 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)
})
}
}