@ultimat3/time 25.0.0 → 25.1.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 +2 -0
- package/README.md +1 -1
- package/package.json +3 -3
- package/src/duration.ts +13 -2
package/CLAUDE.md
CHANGED
|
@@ -139,6 +139,8 @@
|
|
|
139
139
|
canonical zero most emitters write — was refused along with `'PT0H0M0S'` and `'P0W'`, while
|
|
140
140
|
`'P0D'` was let through by a special case. `ISO_8601` already requires a component group for any
|
|
141
141
|
body past a bare `'P'`, which is the case the guard was written for.
|
|
142
|
+
- **`parseDuration`'s rounded total must be `Number.isSafeInteger`** (`safeTotal`, both arms) —
|
|
143
|
+
finite components can still sum to `Infinity` or past 2^53. `X_DURATION_INVALID`.
|
|
142
144
|
- **An impossible day/month pair is refused by `parseCron`, in constant time.**
|
|
143
145
|
`isValidCron('0 0 30 2 *')` answered `true` and the refusal arrived ~150 ms later out of
|
|
144
146
|
`nextCronOccurrence`, after 200,000 walk steps — a cost `firedSince` pays on every tick of the
|
package/README.md
CHANGED
|
@@ -220,7 +220,7 @@ negated; an empty one is `0` in either direction, never `-0`.
|
|
|
220
220
|
|---|---|
|
|
221
221
|
| `X_TIMEZONE_INVALID` | not `Area/Location` or `UTC`: an abbreviation (`CET`), a numeric offset (`+02:00`), or a single-label legacy name (`Japan`) |
|
|
222
222
|
| `X_CRON_INVALID` | unparseable expression, or one that can never match |
|
|
223
|
-
| `X_DURATION_INVALID` | `'3'` with no unit, trailing junk, unknown unit |
|
|
223
|
+
| `X_DURATION_INVALID` | `'3'` with no unit, trailing junk, unknown unit, or a total past `Number.MAX_SAFE_INTEGER` ms (`'99999999999999999999d'`) |
|
|
224
224
|
| `X_DST_AMBIGUOUS` | overlap hit with `overlap: 'throw'` |
|
|
225
225
|
| `X_DST_NONEXISTENT` | gap hit with `gap: 'throw'` |
|
|
226
226
|
| `X_INSTANT_INVALID` | unparseable timestamp |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultimat3/time",
|
|
3
|
-
"version": "25.
|
|
3
|
+
"version": "25.1.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",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"test": "bun test"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@ultimat3/core": "25.
|
|
39
|
-
"@ultimat3/schema": "25.
|
|
38
|
+
"@ultimat3/core": "25.1.0",
|
|
39
|
+
"@ultimat3/schema": "25.1.0"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/duration.ts
CHANGED
|
@@ -40,7 +40,7 @@ export function parseDuration(input: string): number {
|
|
|
40
40
|
const negative = value.startsWith('-');
|
|
41
41
|
const body = negative ? value.slice(1) : value;
|
|
42
42
|
|
|
43
|
-
if (/^p/i.test(body)) return (
|
|
43
|
+
if (/^p/i.test(body)) return safeTotal(parseIso8601Duration(body, input), negative, input);
|
|
44
44
|
|
|
45
45
|
COMPONENT.lastIndex = 0;
|
|
46
46
|
let total = 0;
|
|
@@ -58,7 +58,18 @@ export function parseDuration(input: string): number {
|
|
|
58
58
|
// Sticky matching starts at 0, so anything short of the end is trailing junk, and a
|
|
59
59
|
// zero-length match means there was no unit at all (`'3'` must not mean 3 of anything).
|
|
60
60
|
if (matched === 0 || matched !== body.length) throw durationInvalid(input);
|
|
61
|
-
return (
|
|
61
|
+
return safeTotal(Math.round(total), negative, input);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Each component is finite and their SUM still need not be: `'9'.repeat(305) + 'd'` is
|
|
66
|
+
* `Infinity`, and twenty nines of days is 8.64e27 — past 2^53, where no timer, `Date` or
|
|
67
|
+
* `wakeAt > now` reads what was written. Refused like any other unreadable duration, on the
|
|
68
|
+
* rounded total of both arms.
|
|
69
|
+
*/
|
|
70
|
+
function safeTotal(ms: number, negative: boolean, input: string): number {
|
|
71
|
+
if (!Number.isSafeInteger(ms)) throw durationInvalid(input);
|
|
72
|
+
return negative ? -ms : ms;
|
|
62
73
|
}
|
|
63
74
|
|
|
64
75
|
/**
|