@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 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`. `canonicalTimeZone` asserts the structural property itself: a zone is
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": "17.0.0",
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.3.0"
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": "17.0.0"
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('toMs', 'duration', duration)
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(duration: string | number): number {
92
- const ms = toMs(duration);
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;