@ultimat3/time 17.0.0 → 18.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
@@ -156,6 +156,15 @@
156
156
  `parseDuration` accepts a leading `-`, and `toSeconds(-3000)` is a tested `-3` — and legitimately
157
157
  fractional. A caller that needs whole non-negative milliseconds narrows on top; narrowing here
158
158
  breaks the signed-duration contract `toSeconds` is built on.
159
+ - **`toMs(duration, subject?, option?)` names the caller's knob in the refusal, `As of 2026-08-26`.**
160
+ `pass a finite duration to toMs` names a function an app author reached THROUGH rather than
161
+ wrote: `@ultimat3/testing`'s `clock.advance('3s')` hands its argument straight down, and so does
162
+ any wrapper an app builds. The two names are the same two, in the same order, that
163
+ `@ultimat3/jobs`' `finiteDurationMs` takes — one shape, not two. They are **optional** here and
164
+ required there because this one is published API: making them required is `TS2554` at every
165
+ existing call site in every app, a major for a better sentence. The defaults reproduce today's
166
+ message byte for byte, so the only observable change is at a call site that supplies them.
167
+ `toSeconds` threads them too and defaults its subject to **`toSeconds`**, not to the delegate.
159
168
  - Tests must cover a spring-forward gap, a fall-back overlap and a non-hour offset zone.
160
169
 
161
170
  ## Commands
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/time",
3
- "version": "17.0.0",
3
+ "version": "18.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": "18.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;