solve-engine 2.25.0 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CalendarBackend-MdS3Bb4P.d.cts +181 -0
- package/dist/CalendarBackend-MdS3Bb4P.d.ts +181 -0
- package/dist/{Configuration-DnzYPmoK.d.cts → Configuration-BxriK9Kw.d.cts} +63 -4
- package/dist/{Configuration-DnzYPmoK.d.ts → Configuration-BxriK9Kw.d.ts} +63 -4
- package/dist/DateCalendar-B85t6Vk6.d.ts +90 -0
- package/dist/DateCalendar-CUGI1fT4.d.cts +90 -0
- package/dist/{EngineError-fR1K1rvx.d.cts → EngineError-C-kBwYlN.d.cts} +31 -4
- package/dist/{EngineError-fR1K1rvx.d.ts → EngineError-C-kBwYlN.d.ts} +31 -4
- package/dist/{FormattingSettings-bRKjU7wI.d.ts → FormattingSettings-ByO-4wiM.d.cts} +14 -0
- package/dist/{FormattingSettings-bRKjU7wI.d.cts → FormattingSettings-CvCzAfsz.d.ts} +14 -0
- package/dist/{Lexer-aB95pjoX.d.cts → Lexer-DhGhoAa2.d.cts} +13 -3
- package/dist/{Lexer-Bmctxl0n.d.ts → Lexer-DqtnZMC5.d.ts} +13 -3
- package/dist/{PackageCompatibility-CUVg8tmt.d.cts → PackageCompatibility-CNiTtC6S.d.cts} +1 -1
- package/dist/{PackageCompatibility-BludTs7v.d.ts → PackageCompatibility-fj_f7uqC.d.ts} +1 -1
- package/dist/{PackageRegistry-Dvn4i2QK.d.cts → PackageRegistry-BHBCFswo.d.cts} +328 -14
- package/dist/{PackageRegistry-DT-x32OM.d.ts → PackageRegistry-CNZUwWHJ.d.ts} +328 -14
- package/dist/{Parselet-BawfGuTF.d.cts → Parselet-DF3864la.d.cts} +19 -3
- package/dist/{Parselet-DqTbbtWA.d.ts → Parselet-Dgymjxzd.d.ts} +19 -3
- package/dist/{Token-CbP_OutD.d.cts → Token-D8f7yaz1.d.cts} +17 -0
- package/dist/{Token-CbP_OutD.d.ts → Token-D8f7yaz1.d.ts} +17 -0
- package/dist/{TokenNormalizer-CLBtiE8M.d.ts → TokenNormalizer-IF7nPRMp.d.ts} +1 -1
- package/dist/{TokenNormalizer-CjTDmHgB.d.cts → TokenNormalizer-Pe3_390t.d.cts} +1 -1
- package/dist/{ScopeManager-B636am7Y.d.ts → VMBuiltins-CZiZRKn-.d.ts} +91 -9
- package/dist/{ScopeManager-CdwX0VTw.d.cts → VMBuiltins-kv4rr0y5.d.cts} +91 -9
- package/dist/{VMCheckpoints-DDlxNssY.d.ts → VMCheckpoints-DXze6ypg.d.ts} +2 -2
- package/dist/{VMCheckpoints-CYqeq8IV.d.cts → VMCheckpoints-yun4ocSv.d.cts} +2 -2
- package/dist/{Value-Ds3Gy07C.d.cts → Value-BYHw-x7q.d.cts} +57 -1
- package/dist/{Value-Ds3Gy07C.d.ts → Value-BYHw-x7q.d.ts} +57 -1
- package/dist/{WorkerError-B1dF3VYT.d.cts → WorkerError-BLZyNq6M.d.cts} +1 -1
- package/dist/{WorkerError-dneODN-u.d.ts → WorkerError-CfBJYueR.d.ts} +1 -1
- package/dist/chunk-2SYYKQ4Q.cjs +2 -0
- package/dist/chunk-2SYYKQ4Q.cjs.map +1 -0
- package/dist/chunk-6EYKVQT3.cjs +2 -0
- package/dist/chunk-6EYKVQT3.cjs.map +1 -0
- package/dist/chunk-7RC6RBS6.cjs +2 -0
- package/dist/chunk-7RC6RBS6.cjs.map +1 -0
- package/dist/chunk-AZX4NTYN.cjs +3 -0
- package/dist/chunk-AZX4NTYN.cjs.map +1 -0
- package/dist/{chunk-JFZ5SLF7.js → chunk-BCR53EIL.js} +2 -2
- package/dist/{chunk-JFZ5SLF7.js.map → chunk-BCR53EIL.js.map} +1 -1
- package/dist/chunk-BCY2WY5P.js +5 -0
- package/dist/chunk-BCY2WY5P.js.map +1 -0
- package/dist/chunk-BLJ6E577.js +2 -0
- package/dist/chunk-BLJ6E577.js.map +1 -0
- package/dist/chunk-CL7DM2FX.js +2 -0
- package/dist/chunk-CL7DM2FX.js.map +1 -0
- package/dist/{chunk-BMXYR5LI.cjs → chunk-CQLCTKMX.cjs} +2 -2
- package/dist/{chunk-BMXYR5LI.cjs.map → chunk-CQLCTKMX.cjs.map} +1 -1
- package/dist/chunk-D4VBWPWN.js +2 -0
- package/dist/chunk-D4VBWPWN.js.map +1 -0
- package/dist/chunk-DFMAZ6XM.cjs +3 -0
- package/dist/chunk-DFMAZ6XM.cjs.map +1 -0
- package/dist/chunk-E4HAKNBQ.cjs +2 -0
- package/dist/chunk-E4HAKNBQ.cjs.map +1 -0
- package/dist/{chunk-XHQYHXRE.js → chunk-ESZNJQ2C.js} +3 -3
- package/dist/{chunk-XHQYHXRE.js.map → chunk-ESZNJQ2C.js.map} +1 -1
- package/dist/{chunk-IJMNVBIS.js → chunk-FAO6DQ74.js} +2 -2
- package/dist/chunk-FAO6DQ74.js.map +1 -0
- package/dist/{chunk-WGP4UUO5.cjs → chunk-FDKTESBC.cjs} +2 -2
- package/dist/{chunk-WGP4UUO5.cjs.map → chunk-FDKTESBC.cjs.map} +1 -1
- package/dist/{chunk-NEGZZB7F.cjs → chunk-G2V33LFM.cjs} +2 -2
- package/dist/{chunk-NEGZZB7F.cjs.map → chunk-G2V33LFM.cjs.map} +1 -1
- package/dist/chunk-GJZIJK2Q.cjs +2 -0
- package/dist/chunk-GJZIJK2Q.cjs.map +1 -0
- package/dist/chunk-GVL3ZMS7.cjs +2 -0
- package/dist/chunk-GVL3ZMS7.cjs.map +1 -0
- package/dist/{chunk-YBQPEWT7.cjs → chunk-GXO7TSXQ.cjs} +3 -3
- package/dist/chunk-GXO7TSXQ.cjs.map +1 -0
- package/dist/{chunk-OONQ3V3I.js → chunk-HANGBVEE.js} +2 -2
- package/dist/{chunk-OONQ3V3I.js.map → chunk-HANGBVEE.js.map} +1 -1
- package/dist/chunk-J4K72CQN.js +2 -0
- package/dist/chunk-J4K72CQN.js.map +1 -0
- package/dist/{chunk-72PYPENZ.js → chunk-LE6WZLJ4.js} +2 -2
- package/dist/{chunk-72PYPENZ.js.map → chunk-LE6WZLJ4.js.map} +1 -1
- package/dist/chunk-LQIRBP4Q.js +2 -0
- package/dist/chunk-LQIRBP4Q.js.map +1 -0
- package/dist/{chunk-RDYXH7ML.js → chunk-LTUYWJGO.js} +2 -2
- package/dist/{chunk-RDYXH7ML.js.map → chunk-LTUYWJGO.js.map} +1 -1
- package/dist/chunk-O5PO4D2A.cjs +2 -0
- package/dist/chunk-O5PO4D2A.cjs.map +1 -0
- package/dist/chunk-PQFI55AC.js +3 -0
- package/dist/chunk-PQFI55AC.js.map +1 -0
- package/dist/chunk-QQHHZPEW.cjs +2 -0
- package/dist/chunk-QQHHZPEW.cjs.map +1 -0
- package/dist/chunk-REZQFPVX.js +2 -0
- package/dist/chunk-REZQFPVX.js.map +1 -0
- package/dist/chunk-RRUGQ6DM.js +2 -0
- package/dist/chunk-RRUGQ6DM.js.map +1 -0
- package/dist/chunk-S3ODNMJS.js +2 -0
- package/dist/chunk-S3ODNMJS.js.map +1 -0
- package/dist/{chunk-SAF3B3AX.cjs → chunk-SE6ZCGZ5.cjs} +2 -2
- package/dist/{chunk-SAF3B3AX.cjs.map → chunk-SE6ZCGZ5.cjs.map} +1 -1
- package/dist/chunk-TC23XEAV.js +2 -0
- package/dist/chunk-TC23XEAV.js.map +1 -0
- package/dist/{chunk-FYAMQFZZ.cjs → chunk-TMQ37JQY.cjs} +3 -3
- package/dist/{chunk-FYAMQFZZ.cjs.map → chunk-TMQ37JQY.cjs.map} +1 -1
- package/dist/chunk-TPPM4QSS.cjs +2 -0
- package/dist/chunk-TPPM4QSS.cjs.map +1 -0
- package/dist/chunk-UVHD7BBC.js +3 -0
- package/dist/{chunk-ZDCZ5IDD.js.map → chunk-UVHD7BBC.js.map} +1 -1
- package/dist/{chunk-57PSJQQW.js → chunk-VB6QMU2W.js} +3 -3
- package/dist/chunk-VB6QMU2W.js.map +1 -0
- package/dist/chunk-VQO54ZMA.js +2 -0
- package/dist/chunk-VQO54ZMA.js.map +1 -0
- package/dist/{chunk-7DP57BA7.js → chunk-Y7XRA2EV.js} +2 -2
- package/dist/{chunk-7DP57BA7.js.map → chunk-Y7XRA2EV.js.map} +1 -1
- package/dist/{chunk-5IJD5L3O.cjs → chunk-YBQOQTVL.cjs} +2 -2
- package/dist/chunk-YBQOQTVL.cjs.map +1 -0
- package/dist/{chunk-EL2XUOWX.js → chunk-YG7UWQFV.js} +2 -2
- package/dist/{chunk-EL2XUOWX.js.map → chunk-YG7UWQFV.js.map} +1 -1
- package/dist/chunk-YSHWNCDH.cjs +5 -0
- package/dist/chunk-YSHWNCDH.cjs.map +1 -0
- package/dist/chunk-ZC2NPRLO.js +3 -0
- package/dist/chunk-ZC2NPRLO.js.map +1 -0
- package/dist/{chunk-FYTB7SDP.cjs → chunk-ZDJTDFTR.cjs} +2 -2
- package/dist/{chunk-FYTB7SDP.cjs.map → chunk-ZDJTDFTR.cjs.map} +1 -1
- package/dist/chunk-ZE5KKMBV.cjs +2 -0
- package/dist/chunk-ZE5KKMBV.cjs.map +1 -0
- package/dist/{chunk-4BAUQE6A.cjs → chunk-ZPHUTCJ5.cjs} +3 -3
- package/dist/{chunk-4BAUQE6A.cjs.map → chunk-ZPHUTCJ5.cjs.map} +1 -1
- package/dist/{chunk-GBCN7FNV.cjs → chunk-ZSLVLMO5.cjs} +2 -2
- package/dist/{chunk-GBCN7FNV.cjs.map → chunk-ZSLVLMO5.cjs.map} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.d.cts +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/engine.cjs +1 -1
- package/dist/engine.d.cts +14 -12
- package/dist/engine.d.ts +14 -12
- package/dist/engine.js +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.d.cts +3 -3
- package/dist/errors.d.ts +3 -3
- package/dist/errors.js +1 -1
- package/dist/format.cjs +1 -1
- package/dist/format.d.cts +4 -3
- package/dist/format.d.ts +4 -3
- package/dist/format.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -13
- package/dist/index.d.ts +15 -13
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/language.d.cts +12 -11
- package/dist/language.d.ts +12 -11
- package/dist/lexer.cjs +1 -1
- package/dist/lexer.d.cts +4 -4
- package/dist/lexer.d.ts +4 -4
- package/dist/lexer.js +1 -1
- package/dist/normalizer.cjs +1 -1
- package/dist/normalizer.d.cts +3 -3
- package/dist/normalizer.d.ts +3 -3
- package/dist/normalizer.js +1 -1
- package/dist/packages.cjs +1 -1
- package/dist/packages.d.cts +11 -10
- package/dist/packages.d.ts +11 -10
- package/dist/packages.js +1 -1
- package/dist/parser.cjs +1 -1
- package/dist/parser.d.cts +5 -4
- package/dist/parser.d.ts +5 -4
- package/dist/parser.js +1 -1
- package/dist/{pipeline-69q2tsLE.d.cts → pipeline-BYkKNpql.d.cts} +1 -1
- package/dist/{pipeline-DTqGLPsV.d.ts → pipeline-Cl7KW8CB.d.ts} +1 -1
- package/dist/resolvers.d.cts +2 -2
- package/dist/resolvers.d.ts +2 -2
- package/dist/temporal.cjs +2 -0
- package/dist/temporal.cjs.map +1 -0
- package/dist/temporal.d.cts +201 -0
- package/dist/temporal.d.ts +201 -0
- package/dist/temporal.js +2 -0
- package/dist/temporal.js.map +1 -0
- package/dist/testing.cjs +2 -2
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +12 -11
- package/dist/testing.d.ts +12 -11
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/uom.cjs +1 -1
- package/dist/uom.d.cts +2 -2
- package/dist/uom.d.ts +2 -2
- package/dist/uom.js +1 -1
- package/dist/vm.cjs +1 -1
- package/dist/vm.cjs.map +1 -1
- package/dist/vm.d.cts +8 -56
- package/dist/vm.d.ts +8 -56
- package/dist/vm.js +1 -1
- package/dist/vm.js.map +1 -1
- package/dist/worker.cjs +2 -2
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +35 -13
- package/dist/worker.d.ts +35 -13
- package/dist/worker.js +2 -2
- package/dist/worker.js.map +1 -1
- package/package.json +12 -1
- package/dist/chunk-4BRTUFTH.js +0 -2
- package/dist/chunk-4BRTUFTH.js.map +0 -1
- package/dist/chunk-4NZXCHQL.cjs +0 -2
- package/dist/chunk-4NZXCHQL.cjs.map +0 -1
- package/dist/chunk-57PSJQQW.js.map +0 -1
- package/dist/chunk-5IJD5L3O.cjs.map +0 -1
- package/dist/chunk-634ILVMP.cjs +0 -2
- package/dist/chunk-634ILVMP.cjs.map +0 -1
- package/dist/chunk-63ZOOUOL.js +0 -2
- package/dist/chunk-63ZOOUOL.js.map +0 -1
- package/dist/chunk-77KI7AKJ.cjs +0 -2
- package/dist/chunk-77KI7AKJ.cjs.map +0 -1
- package/dist/chunk-7N3IJSWF.js +0 -5
- package/dist/chunk-7N3IJSWF.js.map +0 -1
- package/dist/chunk-DUYTXLO2.js +0 -2
- package/dist/chunk-DUYTXLO2.js.map +0 -1
- package/dist/chunk-IJMNVBIS.js.map +0 -1
- package/dist/chunk-JSM3O7EM.cjs +0 -2
- package/dist/chunk-JSM3O7EM.cjs.map +0 -1
- package/dist/chunk-MZMA32NE.js +0 -3
- package/dist/chunk-MZMA32NE.js.map +0 -1
- package/dist/chunk-N4SSR7Q6.js +0 -2
- package/dist/chunk-N4SSR7Q6.js.map +0 -1
- package/dist/chunk-NMU5E7W7.js +0 -2
- package/dist/chunk-NMU5E7W7.js.map +0 -1
- package/dist/chunk-OB2VX4IL.js +0 -2
- package/dist/chunk-OB2VX4IL.js.map +0 -1
- package/dist/chunk-RAPEZR5L.cjs +0 -2
- package/dist/chunk-RAPEZR5L.cjs.map +0 -1
- package/dist/chunk-SPOVFRZO.cjs +0 -5
- package/dist/chunk-SPOVFRZO.cjs.map +0 -1
- package/dist/chunk-SQHBOWES.cjs +0 -2
- package/dist/chunk-SQHBOWES.cjs.map +0 -1
- package/dist/chunk-UF5XMZTT.js +0 -2
- package/dist/chunk-UF5XMZTT.js.map +0 -1
- package/dist/chunk-UV3EUIBT.cjs +0 -3
- package/dist/chunk-UV3EUIBT.cjs.map +0 -1
- package/dist/chunk-VMSCRVNX.cjs +0 -2
- package/dist/chunk-VMSCRVNX.cjs.map +0 -1
- package/dist/chunk-XBGK3TTU.cjs +0 -3
- package/dist/chunk-XBGK3TTU.cjs.map +0 -1
- package/dist/chunk-XX55RR4Q.js +0 -3
- package/dist/chunk-XX55RR4Q.js.map +0 -1
- package/dist/chunk-YBQPEWT7.cjs.map +0 -1
- package/dist/chunk-ZDCZ5IDD.js +0 -3
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The calendar the engine computes dates with, behind one interface.
|
|
3
|
+
*
|
|
4
|
+
* Every date the engine holds is an instant: epoch milliseconds in a
|
|
5
|
+
* `Datetime` value, with no zone attached. Every date *question* the engine
|
|
6
|
+
* answers is a calendar question about that instant: which day it falls on,
|
|
7
|
+
* what the same wall-clock time a month later is, whether it is a Saturday,
|
|
8
|
+
* how it is written out. Answering those needs a time zone and a set of
|
|
9
|
+
* calendar rules, and until this interface existed the answer was always the
|
|
10
|
+
* JavaScript `Date` object read in the host process's own zone, scattered
|
|
11
|
+
* across some twenty files.
|
|
12
|
+
*
|
|
13
|
+
* A backend gathers those questions into one place so that a different
|
|
14
|
+
* implementation can answer them. Two ship with the engine. {@link DateCalendar}
|
|
15
|
+
* is the same `Date` code moved behind these methods and is the default, so
|
|
16
|
+
* nothing observable changes for a host that configures nothing. The
|
|
17
|
+
* `Temporal` backend (`solve-engine/temporal`) answers the same questions
|
|
18
|
+
* through a `Temporal` implementation the host hands it, native or polyfilled,
|
|
19
|
+
* and carries a time zone of its own rather than the process's; the engine
|
|
20
|
+
* never imports a polyfill.
|
|
21
|
+
*
|
|
22
|
+
* The contract is deliberately narrow. Methods take and return plain numbers
|
|
23
|
+
* and strings, never a `Date` or a `Temporal` object, so a `Datetime` value's
|
|
24
|
+
* payload stays a number and no worker message, snapshot or arena entry
|
|
25
|
+
* changes shape. Arithmetic whose answer does not depend on a zone (a day
|
|
26
|
+
* number, the days in a month, the ISO week) lives beside the backend in
|
|
27
|
+
* `calendar/Gregorian.ts` rather than behind it, because sharing one
|
|
28
|
+
* implementation is what keeps two backends from disagreeing on it.
|
|
29
|
+
*
|
|
30
|
+
* The shape is public and settled by the two backends that implement it. A
|
|
31
|
+
* host may implement the interface directly; a change to it follows the
|
|
32
|
+
* package's semantic versioning, so a new required method is a major.
|
|
33
|
+
*
|
|
34
|
+
* @module CalendarBackend
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* The calendar fields of one instant, read in the backend's zone.
|
|
38
|
+
*
|
|
39
|
+
* `month0` counts from zero (January is 0) and `weekday` from Sunday (0) to
|
|
40
|
+
* Saturday (6), matching `Date` at every internal boundary so a backend cannot
|
|
41
|
+
* be off by one against the code that reads it. Every field is `NaN` for an
|
|
42
|
+
* instant the backend cannot represent.
|
|
43
|
+
*/
|
|
44
|
+
interface CalendarFields {
|
|
45
|
+
readonly year: number;
|
|
46
|
+
/** Zero-based month: 0 is January, 11 is December. */
|
|
47
|
+
readonly month0: number;
|
|
48
|
+
/** Day of the month, from 1. */
|
|
49
|
+
readonly day: number;
|
|
50
|
+
/** Day of the week: 0 is Sunday, 6 is Saturday. */
|
|
51
|
+
readonly weekday: number;
|
|
52
|
+
readonly hour: number;
|
|
53
|
+
readonly minute: number;
|
|
54
|
+
readonly second: number;
|
|
55
|
+
readonly millisecond: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The calendar fields a named time zone shows for an instant: the date and
|
|
59
|
+
* the wall-clock time, with the same zero-based month as {@link CalendarFields}.
|
|
60
|
+
* Sub-second precision is never needed for a zone conversion, so there is no
|
|
61
|
+
* millisecond field.
|
|
62
|
+
*/
|
|
63
|
+
interface ZonedFields {
|
|
64
|
+
readonly year: number;
|
|
65
|
+
/** Zero-based month: 0 is January, 11 is December. */
|
|
66
|
+
readonly month0: number;
|
|
67
|
+
/** Day of the month, from 1. */
|
|
68
|
+
readonly day: number;
|
|
69
|
+
readonly hour: number;
|
|
70
|
+
readonly minute: number;
|
|
71
|
+
readonly second: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The calendar computations the engine performs, as one replaceable unit.
|
|
75
|
+
*
|
|
76
|
+
* "Local" throughout means the backend's own zone: the host process's zone for
|
|
77
|
+
* the `Date` backend, the configured zone for the `Temporal` one. A local
|
|
78
|
+
* operation that cannot be represented (an instant past the range the backend
|
|
79
|
+
* supports, a field that is not finite) answers `NaN`, never throws; the
|
|
80
|
+
* caller decides what an unrepresentable date means for its form.
|
|
81
|
+
*
|
|
82
|
+
* The four named-zone methods are the exception, and the contract is stated on
|
|
83
|
+
* each: a zone name the runtime does not know throws its `RangeError`, as
|
|
84
|
+
* `Intl` and `Temporal` both do, because a zone reaches the backend only from
|
|
85
|
+
* the engine's own zone registry and an unknown one is a data fault the caller
|
|
86
|
+
* should see rather than a `NaN` to display. Callers hand those methods a
|
|
87
|
+
* finite instant (`now()`, or a literal already checked).
|
|
88
|
+
*/
|
|
89
|
+
interface CalendarBackend {
|
|
90
|
+
/** The current instant, in epoch milliseconds. Read at evaluation time, never baked into bytecode. */
|
|
91
|
+
now(): number;
|
|
92
|
+
/** The local calendar fields of an instant, `weekday` included. See {@link CalendarFields}. */
|
|
93
|
+
fields(epochMs: number): CalendarFields;
|
|
94
|
+
/**
|
|
95
|
+
* Local midnight on a calendar date, in epoch milliseconds.
|
|
96
|
+
*
|
|
97
|
+
* Fields overflow the way `Date`'s do (month 12 is January of the next
|
|
98
|
+
* year, day 0 the last day of the month before), which is what a caller
|
|
99
|
+
* checking for a rolled-over literal relies on: build the date, read its
|
|
100
|
+
* fields back, and a 30 February shows up as 1 or 2 March.
|
|
101
|
+
*/
|
|
102
|
+
localMidnight(year: number, month0: number, day: number): number;
|
|
103
|
+
/**
|
|
104
|
+
* A wall-clock time on a calendar date, given as minutes past local
|
|
105
|
+
* midnight, in epoch milliseconds.
|
|
106
|
+
*
|
|
107
|
+
* The minutes name a clock reading, not an elapsed span: 540 minutes is
|
|
108
|
+
* 09:00 on that date even on a day with a daylight-saving transition,
|
|
109
|
+
* where 540 minutes of elapsed time from midnight would land at 10:00 or
|
|
110
|
+
* 08:00.
|
|
111
|
+
*/
|
|
112
|
+
localWallClock(year: number, month0: number, day: number, minutesPastMidnight: number): number;
|
|
113
|
+
/**
|
|
114
|
+
* Move an instant by whole calendar days, holding the local wall-clock
|
|
115
|
+
* time. A day that contains a daylight-saving transition is 23 or 25
|
|
116
|
+
* hours long, so this is a field step, not an addition of milliseconds.
|
|
117
|
+
*/
|
|
118
|
+
addDays(epochMs: number, days: number): number;
|
|
119
|
+
/**
|
|
120
|
+
* Move an instant by whole calendar months, holding the local wall-clock
|
|
121
|
+
* time and clamping the day to the length of the month landed in: 31
|
|
122
|
+
* January plus a month is 28 February, or 29 in a leap year, never 3 March.
|
|
123
|
+
*/
|
|
124
|
+
addMonths(epochMs: number, months: number): number;
|
|
125
|
+
/** The local zone's offset from UTC at an instant, in minutes, positive when ahead of UTC. */
|
|
126
|
+
utcOffsetMinutes(epochMs: number): number;
|
|
127
|
+
/**
|
|
128
|
+
* Parse an ISO 8601 date or date-time string to epoch milliseconds, or
|
|
129
|
+
* `NaN` when it names no instant.
|
|
130
|
+
*
|
|
131
|
+
* A date-only string (`2019-04-01`) is UTC midnight; a date-time with no
|
|
132
|
+
* offset and no `Z` is local time. Both readings are the ECMAScript ones,
|
|
133
|
+
* and every backend reproduces them so the two spellings of a literal keep
|
|
134
|
+
* meaning the same instant whichever backend is in use.
|
|
135
|
+
*/
|
|
136
|
+
parseIso8601(text: string): number;
|
|
137
|
+
/** The spelled-out local date in a locale, `Tuesday, March 10, 2026` in `en`. */
|
|
138
|
+
formatLongDate(epochMs: number, locale: string): string;
|
|
139
|
+
/** The local time of day in a locale, `9:30:00 AM` in `en`. */
|
|
140
|
+
formatTimeOfDay(epochMs: number, locale: string): string;
|
|
141
|
+
/**
|
|
142
|
+
* A named IANA zone's offset from UTC at an instant, in minutes, positive
|
|
143
|
+
* when ahead of UTC. Throws the runtime's `RangeError` for a zone it does
|
|
144
|
+
* not know or an instant it cannot represent.
|
|
145
|
+
*/
|
|
146
|
+
zoneOffsetMinutes(zone: string, epochMs: number): number;
|
|
147
|
+
/**
|
|
148
|
+
* The calendar date and wall-clock time a named IANA zone shows for an
|
|
149
|
+
* instant. Throws the runtime's `RangeError` for a zone it does not know or
|
|
150
|
+
* an instant it cannot represent.
|
|
151
|
+
*/
|
|
152
|
+
fieldsInZone(zone: string, epochMs: number): ZonedFields;
|
|
153
|
+
/**
|
|
154
|
+
* The wall-clock time in a named IANA zone, `1:00 AM`, in the `en-US` style
|
|
155
|
+
* the timezone forms answer in. Throws the runtime's `RangeError` for a
|
|
156
|
+
* zone it does not know or an instant it cannot represent.
|
|
157
|
+
*/
|
|
158
|
+
formatTimeInZone(zone: string, epochMs: number): string;
|
|
159
|
+
/**
|
|
160
|
+
* The calendar date in a named IANA zone, `July 31, 2026`, in the `en-US`
|
|
161
|
+
* style the timezone forms answer in. Throws the runtime's `RangeError` for
|
|
162
|
+
* a zone it does not know or an instant it cannot represent.
|
|
163
|
+
*/
|
|
164
|
+
formatDateInZone(zone: string, epochMs: number): string;
|
|
165
|
+
/**
|
|
166
|
+
* The zone this backend's "local" means, when it can name one.
|
|
167
|
+
*
|
|
168
|
+
* Optional, and deliberately so: the interface's own contract says a new
|
|
169
|
+
* REQUIRED method is a major, and every backend written against the shipped
|
|
170
|
+
* shape has to keep compiling. A backend that reads the host process's zone
|
|
171
|
+
* (the default {@link DateCalendar}) leaves it undefined rather than
|
|
172
|
+
* reporting a zone it does not itself compute in; one built for a named zone
|
|
173
|
+
* (`dateCalendarInZone`, the `Temporal` backend) returns that IANA name.
|
|
174
|
+
*
|
|
175
|
+
* A caller that needs "which zone is local here" and gets `undefined` should
|
|
176
|
+
* fall back to whatever it meant by local before, never guess a name.
|
|
177
|
+
*/
|
|
178
|
+
zone?(): string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type { CalendarBackend as C, ZonedFields as Z, CalendarFields as a };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The calendar the engine computes dates with, behind one interface.
|
|
3
|
+
*
|
|
4
|
+
* Every date the engine holds is an instant: epoch milliseconds in a
|
|
5
|
+
* `Datetime` value, with no zone attached. Every date *question* the engine
|
|
6
|
+
* answers is a calendar question about that instant: which day it falls on,
|
|
7
|
+
* what the same wall-clock time a month later is, whether it is a Saturday,
|
|
8
|
+
* how it is written out. Answering those needs a time zone and a set of
|
|
9
|
+
* calendar rules, and until this interface existed the answer was always the
|
|
10
|
+
* JavaScript `Date` object read in the host process's own zone, scattered
|
|
11
|
+
* across some twenty files.
|
|
12
|
+
*
|
|
13
|
+
* A backend gathers those questions into one place so that a different
|
|
14
|
+
* implementation can answer them. Two ship with the engine. {@link DateCalendar}
|
|
15
|
+
* is the same `Date` code moved behind these methods and is the default, so
|
|
16
|
+
* nothing observable changes for a host that configures nothing. The
|
|
17
|
+
* `Temporal` backend (`solve-engine/temporal`) answers the same questions
|
|
18
|
+
* through a `Temporal` implementation the host hands it, native or polyfilled,
|
|
19
|
+
* and carries a time zone of its own rather than the process's; the engine
|
|
20
|
+
* never imports a polyfill.
|
|
21
|
+
*
|
|
22
|
+
* The contract is deliberately narrow. Methods take and return plain numbers
|
|
23
|
+
* and strings, never a `Date` or a `Temporal` object, so a `Datetime` value's
|
|
24
|
+
* payload stays a number and no worker message, snapshot or arena entry
|
|
25
|
+
* changes shape. Arithmetic whose answer does not depend on a zone (a day
|
|
26
|
+
* number, the days in a month, the ISO week) lives beside the backend in
|
|
27
|
+
* `calendar/Gregorian.ts` rather than behind it, because sharing one
|
|
28
|
+
* implementation is what keeps two backends from disagreeing on it.
|
|
29
|
+
*
|
|
30
|
+
* The shape is public and settled by the two backends that implement it. A
|
|
31
|
+
* host may implement the interface directly; a change to it follows the
|
|
32
|
+
* package's semantic versioning, so a new required method is a major.
|
|
33
|
+
*
|
|
34
|
+
* @module CalendarBackend
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* The calendar fields of one instant, read in the backend's zone.
|
|
38
|
+
*
|
|
39
|
+
* `month0` counts from zero (January is 0) and `weekday` from Sunday (0) to
|
|
40
|
+
* Saturday (6), matching `Date` at every internal boundary so a backend cannot
|
|
41
|
+
* be off by one against the code that reads it. Every field is `NaN` for an
|
|
42
|
+
* instant the backend cannot represent.
|
|
43
|
+
*/
|
|
44
|
+
interface CalendarFields {
|
|
45
|
+
readonly year: number;
|
|
46
|
+
/** Zero-based month: 0 is January, 11 is December. */
|
|
47
|
+
readonly month0: number;
|
|
48
|
+
/** Day of the month, from 1. */
|
|
49
|
+
readonly day: number;
|
|
50
|
+
/** Day of the week: 0 is Sunday, 6 is Saturday. */
|
|
51
|
+
readonly weekday: number;
|
|
52
|
+
readonly hour: number;
|
|
53
|
+
readonly minute: number;
|
|
54
|
+
readonly second: number;
|
|
55
|
+
readonly millisecond: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The calendar fields a named time zone shows for an instant: the date and
|
|
59
|
+
* the wall-clock time, with the same zero-based month as {@link CalendarFields}.
|
|
60
|
+
* Sub-second precision is never needed for a zone conversion, so there is no
|
|
61
|
+
* millisecond field.
|
|
62
|
+
*/
|
|
63
|
+
interface ZonedFields {
|
|
64
|
+
readonly year: number;
|
|
65
|
+
/** Zero-based month: 0 is January, 11 is December. */
|
|
66
|
+
readonly month0: number;
|
|
67
|
+
/** Day of the month, from 1. */
|
|
68
|
+
readonly day: number;
|
|
69
|
+
readonly hour: number;
|
|
70
|
+
readonly minute: number;
|
|
71
|
+
readonly second: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The calendar computations the engine performs, as one replaceable unit.
|
|
75
|
+
*
|
|
76
|
+
* "Local" throughout means the backend's own zone: the host process's zone for
|
|
77
|
+
* the `Date` backend, the configured zone for the `Temporal` one. A local
|
|
78
|
+
* operation that cannot be represented (an instant past the range the backend
|
|
79
|
+
* supports, a field that is not finite) answers `NaN`, never throws; the
|
|
80
|
+
* caller decides what an unrepresentable date means for its form.
|
|
81
|
+
*
|
|
82
|
+
* The four named-zone methods are the exception, and the contract is stated on
|
|
83
|
+
* each: a zone name the runtime does not know throws its `RangeError`, as
|
|
84
|
+
* `Intl` and `Temporal` both do, because a zone reaches the backend only from
|
|
85
|
+
* the engine's own zone registry and an unknown one is a data fault the caller
|
|
86
|
+
* should see rather than a `NaN` to display. Callers hand those methods a
|
|
87
|
+
* finite instant (`now()`, or a literal already checked).
|
|
88
|
+
*/
|
|
89
|
+
interface CalendarBackend {
|
|
90
|
+
/** The current instant, in epoch milliseconds. Read at evaluation time, never baked into bytecode. */
|
|
91
|
+
now(): number;
|
|
92
|
+
/** The local calendar fields of an instant, `weekday` included. See {@link CalendarFields}. */
|
|
93
|
+
fields(epochMs: number): CalendarFields;
|
|
94
|
+
/**
|
|
95
|
+
* Local midnight on a calendar date, in epoch milliseconds.
|
|
96
|
+
*
|
|
97
|
+
* Fields overflow the way `Date`'s do (month 12 is January of the next
|
|
98
|
+
* year, day 0 the last day of the month before), which is what a caller
|
|
99
|
+
* checking for a rolled-over literal relies on: build the date, read its
|
|
100
|
+
* fields back, and a 30 February shows up as 1 or 2 March.
|
|
101
|
+
*/
|
|
102
|
+
localMidnight(year: number, month0: number, day: number): number;
|
|
103
|
+
/**
|
|
104
|
+
* A wall-clock time on a calendar date, given as minutes past local
|
|
105
|
+
* midnight, in epoch milliseconds.
|
|
106
|
+
*
|
|
107
|
+
* The minutes name a clock reading, not an elapsed span: 540 minutes is
|
|
108
|
+
* 09:00 on that date even on a day with a daylight-saving transition,
|
|
109
|
+
* where 540 minutes of elapsed time from midnight would land at 10:00 or
|
|
110
|
+
* 08:00.
|
|
111
|
+
*/
|
|
112
|
+
localWallClock(year: number, month0: number, day: number, minutesPastMidnight: number): number;
|
|
113
|
+
/**
|
|
114
|
+
* Move an instant by whole calendar days, holding the local wall-clock
|
|
115
|
+
* time. A day that contains a daylight-saving transition is 23 or 25
|
|
116
|
+
* hours long, so this is a field step, not an addition of milliseconds.
|
|
117
|
+
*/
|
|
118
|
+
addDays(epochMs: number, days: number): number;
|
|
119
|
+
/**
|
|
120
|
+
* Move an instant by whole calendar months, holding the local wall-clock
|
|
121
|
+
* time and clamping the day to the length of the month landed in: 31
|
|
122
|
+
* January plus a month is 28 February, or 29 in a leap year, never 3 March.
|
|
123
|
+
*/
|
|
124
|
+
addMonths(epochMs: number, months: number): number;
|
|
125
|
+
/** The local zone's offset from UTC at an instant, in minutes, positive when ahead of UTC. */
|
|
126
|
+
utcOffsetMinutes(epochMs: number): number;
|
|
127
|
+
/**
|
|
128
|
+
* Parse an ISO 8601 date or date-time string to epoch milliseconds, or
|
|
129
|
+
* `NaN` when it names no instant.
|
|
130
|
+
*
|
|
131
|
+
* A date-only string (`2019-04-01`) is UTC midnight; a date-time with no
|
|
132
|
+
* offset and no `Z` is local time. Both readings are the ECMAScript ones,
|
|
133
|
+
* and every backend reproduces them so the two spellings of a literal keep
|
|
134
|
+
* meaning the same instant whichever backend is in use.
|
|
135
|
+
*/
|
|
136
|
+
parseIso8601(text: string): number;
|
|
137
|
+
/** The spelled-out local date in a locale, `Tuesday, March 10, 2026` in `en`. */
|
|
138
|
+
formatLongDate(epochMs: number, locale: string): string;
|
|
139
|
+
/** The local time of day in a locale, `9:30:00 AM` in `en`. */
|
|
140
|
+
formatTimeOfDay(epochMs: number, locale: string): string;
|
|
141
|
+
/**
|
|
142
|
+
* A named IANA zone's offset from UTC at an instant, in minutes, positive
|
|
143
|
+
* when ahead of UTC. Throws the runtime's `RangeError` for a zone it does
|
|
144
|
+
* not know or an instant it cannot represent.
|
|
145
|
+
*/
|
|
146
|
+
zoneOffsetMinutes(zone: string, epochMs: number): number;
|
|
147
|
+
/**
|
|
148
|
+
* The calendar date and wall-clock time a named IANA zone shows for an
|
|
149
|
+
* instant. Throws the runtime's `RangeError` for a zone it does not know or
|
|
150
|
+
* an instant it cannot represent.
|
|
151
|
+
*/
|
|
152
|
+
fieldsInZone(zone: string, epochMs: number): ZonedFields;
|
|
153
|
+
/**
|
|
154
|
+
* The wall-clock time in a named IANA zone, `1:00 AM`, in the `en-US` style
|
|
155
|
+
* the timezone forms answer in. Throws the runtime's `RangeError` for a
|
|
156
|
+
* zone it does not know or an instant it cannot represent.
|
|
157
|
+
*/
|
|
158
|
+
formatTimeInZone(zone: string, epochMs: number): string;
|
|
159
|
+
/**
|
|
160
|
+
* The calendar date in a named IANA zone, `July 31, 2026`, in the `en-US`
|
|
161
|
+
* style the timezone forms answer in. Throws the runtime's `RangeError` for
|
|
162
|
+
* a zone it does not know or an instant it cannot represent.
|
|
163
|
+
*/
|
|
164
|
+
formatDateInZone(zone: string, epochMs: number): string;
|
|
165
|
+
/**
|
|
166
|
+
* The zone this backend's "local" means, when it can name one.
|
|
167
|
+
*
|
|
168
|
+
* Optional, and deliberately so: the interface's own contract says a new
|
|
169
|
+
* REQUIRED method is a major, and every backend written against the shipped
|
|
170
|
+
* shape has to keep compiling. A backend that reads the host process's zone
|
|
171
|
+
* (the default {@link DateCalendar}) leaves it undefined rather than
|
|
172
|
+
* reporting a zone it does not itself compute in; one built for a named zone
|
|
173
|
+
* (`dateCalendarInZone`, the `Temporal` backend) returns that IANA name.
|
|
174
|
+
*
|
|
175
|
+
* A caller that needs "which zone is local here" and gets `undefined` should
|
|
176
|
+
* fall back to whatever it meant by local before, never guess a name.
|
|
177
|
+
*/
|
|
178
|
+
zone?(): string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type { CalendarBackend as C, ZonedFields as Z, CalendarFields as a };
|
|
@@ -58,11 +58,25 @@ type HolidayCalendar = HolidayPredicate | Iterable<string | number | Date>;
|
|
|
58
58
|
* a host can match its readers' locale. `'MDY'` is what lets a US reader's
|
|
59
59
|
* `12/25/2023` parse, which `'auto'` refuses because the slash defaults to
|
|
60
60
|
* day-first.
|
|
61
|
+
* - `'locale'`: the order the reader's own machine writes dates in, asked of
|
|
62
|
+
* `Intl` once per engine (see `calendar/HostLocale.ts`). Where that cannot
|
|
63
|
+
* be answered, the engine reads dates exactly as `'auto'` does and reports
|
|
64
|
+
* the fact through `ExpressionEngine.getDateReading()`, rather than guessing.
|
|
65
|
+
* A host that is not the reader (a server rendering someone else's document)
|
|
66
|
+
* names the reader instead, through {@link DateConfig.inputLocale}.
|
|
61
67
|
*
|
|
62
|
-
* Only
|
|
63
|
-
*
|
|
68
|
+
* Only ambiguous literals are affected. A spelled-out month (`March 9, 2024`)
|
|
69
|
+
* is never ambiguous; nor is a hyphen literal with a four-digit leading group
|
|
70
|
+
* (`2024-03-09`), which has no reading but year-month-day and is read as ISO
|
|
71
|
+
* under every order, timestamp or not.
|
|
64
72
|
*/
|
|
65
|
-
type DateInputOrder = 'auto' | 'DMY' | 'MDY' | 'YMD';
|
|
73
|
+
type DateInputOrder = 'auto' | 'locale' | 'DMY' | 'MDY' | 'YMD';
|
|
74
|
+
/**
|
|
75
|
+
* What a date-shaped numeric run the configured order cannot read does: report
|
|
76
|
+
* the problem, or fall through to the arithmetic it is spelled like. See
|
|
77
|
+
* {@link DateConfig.onAmbiguous}.
|
|
78
|
+
*/
|
|
79
|
+
type DateAmbiguity = 'refuse' | 'arithmetic';
|
|
66
80
|
/** Date-related engine configuration: offset limits, input order, holidays. */
|
|
67
81
|
interface DateConfig {
|
|
68
82
|
/**
|
|
@@ -70,6 +84,51 @@ interface DateConfig {
|
|
|
70
84
|
* `'auto'` (by separator, the historic behaviour). See {@link DateInputOrder}.
|
|
71
85
|
*/
|
|
72
86
|
readonly inputOrder: DateInputOrder;
|
|
87
|
+
/**
|
|
88
|
+
* The BCP-47 tag `'locale'` reads the order from, `"en-US"` or `"de-DE"`.
|
|
89
|
+
* Unset, the host machine is asked.
|
|
90
|
+
*
|
|
91
|
+
* Read ONLY when {@link inputOrder} is `'locale'`. Setting it beside any
|
|
92
|
+
* other order changes no result: a field that quietly switched inference on
|
|
93
|
+
* would make the predictable mistake a wrong reading rather than no change.
|
|
94
|
+
* Its shape is checked wherever it is set, though, and a tag `Intl` refuses
|
|
95
|
+
* (`"en_US"`, with an underscore) raises `DATE_INPUT_LOCALE_INVALID` at
|
|
96
|
+
* construction, because a locale silently ignored is a date order silently
|
|
97
|
+
* wrong.
|
|
98
|
+
*
|
|
99
|
+
* Deliberately separate from the engine's `locale` option, which is a
|
|
100
|
+
* language (`'en' | 'de' | 'fr'`) and carries no region. Day/month order is a
|
|
101
|
+
* region question: a bare `en` probes as month-first while a UK machine
|
|
102
|
+
* resolves to `en-GB` and probes as day-first, so wiring the two together
|
|
103
|
+
* would flip every existing British engine to month-first.
|
|
104
|
+
*/
|
|
105
|
+
readonly inputLocale?: string;
|
|
106
|
+
/**
|
|
107
|
+
* What a date-shaped numeric run the resolved order cannot read does.
|
|
108
|
+
* Defaults to `'refuse'`.
|
|
109
|
+
*
|
|
110
|
+
* - `'refuse'`: the line reports a structured Error value naming the
|
|
111
|
+
* problem, DATE_ORDER_MISMATCH when another order would have read it and
|
|
112
|
+
* DATE_NOT_A_CALENDAR_DAY when no order names a real day. `12/25/2026` on
|
|
113
|
+
* a day-first engine says there is no month 25, rather than answering
|
|
114
|
+
* 0.00.
|
|
115
|
+
* - `'arithmetic'`: the run falls through to the division or subtraction it
|
|
116
|
+
* is spelled like, which is what every version before this one did.
|
|
117
|
+
* `12/25/2026` is 0.00 again, and `2026-02-29` is 1,995.
|
|
118
|
+
*
|
|
119
|
+
* The refusal is scoped to runs nobody writes as arithmetic: a two-step
|
|
120
|
+
* chain ending in a four-digit denominator (`03/04/2026` as division is
|
|
121
|
+
* 0.0004), and an ISO-shaped hyphen run. A four-digit LEADING group is
|
|
122
|
+
* ordinary arithmetic (`1000/10/5` is 20, `1024/8/2` is 64) and is never
|
|
123
|
+
* refused, and neither is a run whose groups are all one or two digits
|
|
124
|
+
* (`12/13/14` stays 0.07), because a two-digit year is too weak a signal to
|
|
125
|
+
* hang a refusal on.
|
|
126
|
+
*
|
|
127
|
+
* The one refusal this setting does not restore is the dot form
|
|
128
|
+
* (`25.12.2026` under a month-first order): its only other outcome is a
|
|
129
|
+
* parse error, and an error is not an answer.
|
|
130
|
+
*/
|
|
131
|
+
readonly onAmbiguous: DateAmbiguity;
|
|
73
132
|
/**
|
|
74
133
|
* How far forward a date offset whose COST grows with the offset may reach,
|
|
75
134
|
* in years. Enforced by `vm/VM.ts`'s `addBusinessDays()`.
|
|
@@ -439,4 +498,4 @@ interface ValidationResult {
|
|
|
439
498
|
warnings?: string[];
|
|
440
499
|
}
|
|
441
500
|
|
|
442
|
-
export { ConfigManager as C,
|
|
501
|
+
export { ConfigManager as C, type DateAmbiguity as D, type EngineConfigOverride as E, type HolidayCalendar as H, type PerformanceConfig as P, type VMConfig as V, type WorkerConfig as W, type EngineConfig as a, DEFAULT_CONFIG as b, type DateConfig as c, type DiagnosticConfig as d, type HolidayPredicate as e, type ValidationConfig as f, type ValidationResult as g };
|
|
@@ -58,11 +58,25 @@ type HolidayCalendar = HolidayPredicate | Iterable<string | number | Date>;
|
|
|
58
58
|
* a host can match its readers' locale. `'MDY'` is what lets a US reader's
|
|
59
59
|
* `12/25/2023` parse, which `'auto'` refuses because the slash defaults to
|
|
60
60
|
* day-first.
|
|
61
|
+
* - `'locale'`: the order the reader's own machine writes dates in, asked of
|
|
62
|
+
* `Intl` once per engine (see `calendar/HostLocale.ts`). Where that cannot
|
|
63
|
+
* be answered, the engine reads dates exactly as `'auto'` does and reports
|
|
64
|
+
* the fact through `ExpressionEngine.getDateReading()`, rather than guessing.
|
|
65
|
+
* A host that is not the reader (a server rendering someone else's document)
|
|
66
|
+
* names the reader instead, through {@link DateConfig.inputLocale}.
|
|
61
67
|
*
|
|
62
|
-
* Only
|
|
63
|
-
*
|
|
68
|
+
* Only ambiguous literals are affected. A spelled-out month (`March 9, 2024`)
|
|
69
|
+
* is never ambiguous; nor is a hyphen literal with a four-digit leading group
|
|
70
|
+
* (`2024-03-09`), which has no reading but year-month-day and is read as ISO
|
|
71
|
+
* under every order, timestamp or not.
|
|
64
72
|
*/
|
|
65
|
-
type DateInputOrder = 'auto' | 'DMY' | 'MDY' | 'YMD';
|
|
73
|
+
type DateInputOrder = 'auto' | 'locale' | 'DMY' | 'MDY' | 'YMD';
|
|
74
|
+
/**
|
|
75
|
+
* What a date-shaped numeric run the configured order cannot read does: report
|
|
76
|
+
* the problem, or fall through to the arithmetic it is spelled like. See
|
|
77
|
+
* {@link DateConfig.onAmbiguous}.
|
|
78
|
+
*/
|
|
79
|
+
type DateAmbiguity = 'refuse' | 'arithmetic';
|
|
66
80
|
/** Date-related engine configuration: offset limits, input order, holidays. */
|
|
67
81
|
interface DateConfig {
|
|
68
82
|
/**
|
|
@@ -70,6 +84,51 @@ interface DateConfig {
|
|
|
70
84
|
* `'auto'` (by separator, the historic behaviour). See {@link DateInputOrder}.
|
|
71
85
|
*/
|
|
72
86
|
readonly inputOrder: DateInputOrder;
|
|
87
|
+
/**
|
|
88
|
+
* The BCP-47 tag `'locale'` reads the order from, `"en-US"` or `"de-DE"`.
|
|
89
|
+
* Unset, the host machine is asked.
|
|
90
|
+
*
|
|
91
|
+
* Read ONLY when {@link inputOrder} is `'locale'`. Setting it beside any
|
|
92
|
+
* other order changes no result: a field that quietly switched inference on
|
|
93
|
+
* would make the predictable mistake a wrong reading rather than no change.
|
|
94
|
+
* Its shape is checked wherever it is set, though, and a tag `Intl` refuses
|
|
95
|
+
* (`"en_US"`, with an underscore) raises `DATE_INPUT_LOCALE_INVALID` at
|
|
96
|
+
* construction, because a locale silently ignored is a date order silently
|
|
97
|
+
* wrong.
|
|
98
|
+
*
|
|
99
|
+
* Deliberately separate from the engine's `locale` option, which is a
|
|
100
|
+
* language (`'en' | 'de' | 'fr'`) and carries no region. Day/month order is a
|
|
101
|
+
* region question: a bare `en` probes as month-first while a UK machine
|
|
102
|
+
* resolves to `en-GB` and probes as day-first, so wiring the two together
|
|
103
|
+
* would flip every existing British engine to month-first.
|
|
104
|
+
*/
|
|
105
|
+
readonly inputLocale?: string;
|
|
106
|
+
/**
|
|
107
|
+
* What a date-shaped numeric run the resolved order cannot read does.
|
|
108
|
+
* Defaults to `'refuse'`.
|
|
109
|
+
*
|
|
110
|
+
* - `'refuse'`: the line reports a structured Error value naming the
|
|
111
|
+
* problem, DATE_ORDER_MISMATCH when another order would have read it and
|
|
112
|
+
* DATE_NOT_A_CALENDAR_DAY when no order names a real day. `12/25/2026` on
|
|
113
|
+
* a day-first engine says there is no month 25, rather than answering
|
|
114
|
+
* 0.00.
|
|
115
|
+
* - `'arithmetic'`: the run falls through to the division or subtraction it
|
|
116
|
+
* is spelled like, which is what every version before this one did.
|
|
117
|
+
* `12/25/2026` is 0.00 again, and `2026-02-29` is 1,995.
|
|
118
|
+
*
|
|
119
|
+
* The refusal is scoped to runs nobody writes as arithmetic: a two-step
|
|
120
|
+
* chain ending in a four-digit denominator (`03/04/2026` as division is
|
|
121
|
+
* 0.0004), and an ISO-shaped hyphen run. A four-digit LEADING group is
|
|
122
|
+
* ordinary arithmetic (`1000/10/5` is 20, `1024/8/2` is 64) and is never
|
|
123
|
+
* refused, and neither is a run whose groups are all one or two digits
|
|
124
|
+
* (`12/13/14` stays 0.07), because a two-digit year is too weak a signal to
|
|
125
|
+
* hang a refusal on.
|
|
126
|
+
*
|
|
127
|
+
* The one refusal this setting does not restore is the dot form
|
|
128
|
+
* (`25.12.2026` under a month-first order): its only other outcome is a
|
|
129
|
+
* parse error, and an error is not an answer.
|
|
130
|
+
*/
|
|
131
|
+
readonly onAmbiguous: DateAmbiguity;
|
|
73
132
|
/**
|
|
74
133
|
* How far forward a date offset whose COST grows with the offset may reach,
|
|
75
134
|
* in years. Enforced by `vm/VM.ts`'s `addBusinessDays()`.
|
|
@@ -439,4 +498,4 @@ interface ValidationResult {
|
|
|
439
498
|
warnings?: string[];
|
|
440
499
|
}
|
|
441
500
|
|
|
442
|
-
export { ConfigManager as C,
|
|
501
|
+
export { ConfigManager as C, type DateAmbiguity as D, type EngineConfigOverride as E, type HolidayCalendar as H, type PerformanceConfig as P, type VMConfig as V, type WorkerConfig as W, type EngineConfig as a, DEFAULT_CONFIG as b, type DateConfig as c, type DiagnosticConfig as d, type HolidayPredicate as e, type ValidationConfig as f, type ValidationResult as g };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { C as CalendarBackend, a as CalendarFields, Z as ZonedFields } from './CalendarBackend-MdS3Bb4P.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The default {@link CalendarBackend}: the JavaScript `Date` object, read in
|
|
5
|
+
* the host process's time zone, with `Intl.DateTimeFormat` for named zones.
|
|
6
|
+
*
|
|
7
|
+
* This is the calendar code the engine has always run, moved behind the
|
|
8
|
+
* interface method by method rather than rewritten, so a host that configures
|
|
9
|
+
* nothing sees the same instants and the same strings it did before the
|
|
10
|
+
* backend existed. Where `Date` has a quirk (a two-digit year in a
|
|
11
|
+
* constructor maps to the 1900s, an out-of-range instant answers `NaN`), the
|
|
12
|
+
* quirk is kept here deliberately: the point of this backend is to be the
|
|
13
|
+
* behaviour the `Temporal` backend is measured against, not to improve on it.
|
|
14
|
+
*
|
|
15
|
+
* It stays the default because it is the one calendar every supported runtime
|
|
16
|
+
* has. `Temporal` ships unflagged in Node 26 and in current Chrome, Firefox
|
|
17
|
+
* and Deno, but not in Safari or in the Node 22 and 24 the engine supports,
|
|
18
|
+
* and the smallest polyfill adds about twenty kilobytes gzipped to a bundle
|
|
19
|
+
* that a host doing plain arithmetic never needs. A host that wants
|
|
20
|
+
* `Temporal` opts in through the engine's `calendar` option with the backend
|
|
21
|
+
* from `solve-engine/temporal`, and pays for it only then.
|
|
22
|
+
*/
|
|
23
|
+
declare class DateCalendar implements CalendarBackend {
|
|
24
|
+
now(): number;
|
|
25
|
+
fields(epochMs: number): CalendarFields;
|
|
26
|
+
localMidnight(year: number, month0: number, day: number): number;
|
|
27
|
+
localWallClock(year: number, month0: number, day: number, minutesPastMidnight: number): number;
|
|
28
|
+
addDays(epochMs: number, days: number): number;
|
|
29
|
+
addMonths(epochMs: number, months: number): number;
|
|
30
|
+
utcOffsetMinutes(epochMs: number): number;
|
|
31
|
+
parseIso8601(text: string): number;
|
|
32
|
+
formatLongDate(epochMs: number, locale: string): string;
|
|
33
|
+
formatTimeOfDay(epochMs: number, locale: string): string;
|
|
34
|
+
zoneOffsetMinutes(zone: string, epochMs: number): number;
|
|
35
|
+
fieldsInZone(zone: string, epochMs: number): ZonedFields;
|
|
36
|
+
formatTimeInZone(zone: string, epochMs: number): string;
|
|
37
|
+
formatDateInZone(zone: string, epochMs: number): string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The one `Date` backend, shared by every engine that configures no other.
|
|
41
|
+
*
|
|
42
|
+
* Stateless, so sharing is safe: it holds no zone of its own and reads the
|
|
43
|
+
* process's. It is also what the sites with no engine in hand use (the
|
|
44
|
+
* normaliser rules that fuse a literal, `days in <period>`, the stocks and
|
|
45
|
+
* historical-currency date phrases, and `formatValue`), which is why an
|
|
46
|
+
* engine's own backend is the default here rather than a second copy.
|
|
47
|
+
*/
|
|
48
|
+
declare const DATE_CALENDAR: CalendarBackend;
|
|
49
|
+
/**
|
|
50
|
+
* The backend a plugin function or converter should compute with: the one
|
|
51
|
+
* on its execution context, or the `Date` backend when the handler was
|
|
52
|
+
* called with no context (a direct call from a test, or an `as` converter
|
|
53
|
+
* invoked outside the VM).
|
|
54
|
+
*
|
|
55
|
+
* @param context - The execution context the handler received, if any.
|
|
56
|
+
* @returns The engine's backend when the context carries one, else {@link DATE_CALENDAR}.
|
|
57
|
+
*/
|
|
58
|
+
declare function calendarOf(context?: {
|
|
59
|
+
readonly calendar?: CalendarBackend;
|
|
60
|
+
}): CalendarBackend;
|
|
61
|
+
/**
|
|
62
|
+
* A `Date` backend that computes in a named time zone rather than the host
|
|
63
|
+
* process's.
|
|
64
|
+
*
|
|
65
|
+
* This is how a host pins the zone the engine reads dates in, and it is the
|
|
66
|
+
* only knob for it: the zone belongs to the calendar backend, which already
|
|
67
|
+
* owns what "local" means, so there is deliberately no `date.zone` config
|
|
68
|
+
* field to drift against it.
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { createEngine, dateCalendarInZone } from "solve-engine";
|
|
72
|
+
* const engine = createEngine({ calendar: dateCalendarInZone("Asia/Tokyo") });
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* It ships on the `Date` backend on purpose. `Temporal` is undefined on Node
|
|
76
|
+
* 24 and in Safari, so "pass a `Temporal` backend" would put the zone out of
|
|
77
|
+
* reach for most hosts today; `Temporal` stays an accuracy upgrade for the
|
|
78
|
+
* ambiguous wall clocks around a daylight-saving transition, never a
|
|
79
|
+
* prerequisite for naming a zone at all.
|
|
80
|
+
*
|
|
81
|
+
* @param zone - An IANA zone name, e.g. `"Asia/Tokyo"`.
|
|
82
|
+
* @returns A backend whose local zone is that one.
|
|
83
|
+
* @throws A `DATE_ZONE_UNKNOWN` config error when this runtime's `Intl` does
|
|
84
|
+
* not know the zone. Refused here rather than per line, because a backend
|
|
85
|
+
* that cannot compute in the zone it was asked for must not quietly answer
|
|
86
|
+
* in another one.
|
|
87
|
+
*/
|
|
88
|
+
declare function dateCalendarInZone(zone: string): CalendarBackend;
|
|
89
|
+
|
|
90
|
+
export { DATE_CALENDAR as D, DateCalendar as a, calendarOf as c, dateCalendarInZone as d };
|