@ultimat3/time 17.0.0 → 19.0.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/CLAUDE.md +14 -1
- package/package.json +3 -3
- package/src/duration.ts +24 -4
package/CLAUDE.md
CHANGED
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
agreeing.** ICU 78 (Bun 1.4) RESOLVES `CET`, `EST`, `EST5EDT`, `GMT`, `MST` and their families
|
|
37
37
|
where ICU 75 threw, so a runtime upgrade alone reopened the golden rule above — silently, and in
|
|
38
38
|
the direction that fails dangerous: an abbreviation names no DST rule. So the judgement is never
|
|
39
|
-
delegated to `Intl`.
|
|
39
|
+
delegated to `Intl`. Re-measured 2026-08-27 when the Bun 1.3 reversal was trialled:
|
|
40
|
+
`new Intl.DateTimeFormat('en', { timeZone: 'CET' })` still THROWS on 1.3.14 and resolves on 1.4.0,
|
|
41
|
+
while both list the same 445 zones — so the two ICUs disagree in the direction this rule already
|
|
42
|
+
refuses, and a structural judgement is the only thing that answers the same on either runtime.
|
|
43
|
+
That is the whole argument, and it is why the trial changed nothing here. `canonicalTimeZone` asserts the structural property itself: a zone is
|
|
40
44
|
`Area/Location`, and `UTC` is the one legal exception. Never a denylist of the names ICU newly
|
|
41
45
|
accepts — that list grows with every tzdata and ICU release, and no rule in it keeps `CET` out
|
|
42
46
|
while letting `Japan` in, both being one label. The single-label `backward` links go with them
|
|
@@ -156,6 +160,15 @@
|
|
|
156
160
|
`parseDuration` accepts a leading `-`, and `toSeconds(-3000)` is a tested `-3` — and legitimately
|
|
157
161
|
fractional. A caller that needs whole non-negative milliseconds narrows on top; narrowing here
|
|
158
162
|
breaks the signed-duration contract `toSeconds` is built on.
|
|
163
|
+
- **`toMs(duration, subject?, option?)` names the caller's knob in the refusal, `As of 2026-08-26`.**
|
|
164
|
+
`pass a finite duration to toMs` names a function an app author reached THROUGH rather than
|
|
165
|
+
wrote: `@ultimat3/testing`'s `clock.advance('3s')` hands its argument straight down, and so does
|
|
166
|
+
any wrapper an app builds. The two names are the same two, in the same order, that
|
|
167
|
+
`@ultimat3/jobs`' `finiteDurationMs` takes — one shape, not two. They are **optional** here and
|
|
168
|
+
required there because this one is published API: making them required is `TS2554` at every
|
|
169
|
+
existing call site in every app, a major for a better sentence. The defaults reproduce today's
|
|
170
|
+
message byte for byte, so the only observable change is at a call site that supplies them.
|
|
171
|
+
`toSeconds` threads them too and defaults its subject to **`toSeconds`**, not to the delegate.
|
|
159
172
|
- Tests must cover a spring-forward gap, a fall-back overlap and a non-hour offset zone.
|
|
160
173
|
|
|
161
174
|
## Commands
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/time",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "19.0.0",
|
|
4
4
|
"description": "UTC instants, DST-correct zone math, cron, durations and Intl formatting with an explicit timezone",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
29
|
"engines": {
|
|
30
|
-
"bun": ">=1.
|
|
30
|
+
"bun": ">=1.4.0"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
34
34
|
"test": "bun test"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@ultimat3/core": "
|
|
37
|
+
"@ultimat3/core": "19.0.0"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/src/duration.ts
CHANGED
|
@@ -75,10 +75,22 @@ export function parseDuration(input: string): number {
|
|
|
75
75
|
* accepts a leading `-` and `toSeconds(-3000)` is a tested `-3` — and legitimately fractional.
|
|
76
76
|
* A caller that needs whole non-negative milliseconds narrows on top of this, which is what
|
|
77
77
|
* `@ultimat3/notify`'s `toDurationMs` does; narrowing here would break both.
|
|
78
|
+
*
|
|
79
|
+
* `subject` and `option` name the knob the APP AUTHOR wrote, for the refusal only. They default to
|
|
80
|
+
* this function's own name, which is the right answer for the caller who typed `toMs(…)` and the
|
|
81
|
+
* wrong one for every caller reached THROUGH it: `clock.advance('3s')` in `@ultimat3/testing`
|
|
82
|
+
* passes its argument straight down, and `pass a finite duration to toMs` sends that author
|
|
83
|
+
* looking for a knob their code does not contain. Same two names, same order, as
|
|
84
|
+
* `@ultimat3/jobs`' `finiteDurationMs` — one shape, not two.
|
|
85
|
+
*
|
|
86
|
+
* OPTIONAL rather than required, unlike that one, because this function is published API: a
|
|
87
|
+
* required parameter is `TS2554` at every existing call site in every app, which is a major for a
|
|
88
|
+
* better sentence. The default reproduces today's message byte for byte, so supplying them is the
|
|
89
|
+
* only observable change.
|
|
78
90
|
*/
|
|
79
|
-
export function toMs(duration: string | number): number {
|
|
91
|
+
export function toMs(duration: string | number, subject = 'toMs', option = 'duration'): number {
|
|
80
92
|
return typeof duration === 'number'
|
|
81
|
-
? finiteOption(
|
|
93
|
+
? finiteOption(subject, option, duration)
|
|
82
94
|
: parseDuration(duration);
|
|
83
95
|
}
|
|
84
96
|
|
|
@@ -87,9 +99,17 @@ export function toMs(duration: string | number): number {
|
|
|
87
99
|
* and `'-1500ms'` was -1, so a signed duration and its mirror did not answer mirrored seconds.
|
|
88
100
|
* The sign is carried out and the MAGNITUDE rounded — `packages/money/src/rounding.ts` is the
|
|
89
101
|
* framework's one statement of this, and `signed()` there is why zero never comes back as `-0`.
|
|
102
|
+
*
|
|
103
|
+
* The screen is `toMs`'s, threaded — but the default subject is `toSeconds`, because that is the
|
|
104
|
+
* name a direct caller wrote. Naming the delegate would point them at a function they never typed,
|
|
105
|
+
* which is the whole defect this pair of parameters exists to fix.
|
|
90
106
|
*/
|
|
91
|
-
export function toSeconds(
|
|
92
|
-
|
|
107
|
+
export function toSeconds(
|
|
108
|
+
duration: string | number,
|
|
109
|
+
subject = 'toSeconds',
|
|
110
|
+
option = 'duration',
|
|
111
|
+
): number {
|
|
112
|
+
const ms = toMs(duration, subject, option);
|
|
93
113
|
const seconds = Math.round(Math.abs(ms) / SECOND);
|
|
94
114
|
if (seconds === 0) return 0;
|
|
95
115
|
return ms < 0 ? -seconds : seconds;
|