@c9up/chronos 0.1.3 → 0.1.5
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/DateTime.d.ts +108 -0
- package/dist/DateTime.d.ts.map +1 -0
- package/dist/DateTime.js +506 -0
- package/dist/DateTime.js.map +1 -0
- package/dist/Duration.d.ts +108 -0
- package/dist/Duration.d.ts.map +1 -0
- package/dist/Duration.js +397 -0
- package/dist/Duration.js.map +1 -0
- package/dist/Interval.d.ts +61 -0
- package/dist/Interval.d.ts.map +1 -0
- package/dist/Interval.js +168 -0
- package/dist/Interval.js.map +1 -0
- package/dist/atlas.d.ts +120 -0
- package/dist/atlas.d.ts.map +1 -0
- package/dist/atlas.js +163 -0
- package/dist/atlas.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/native.d.ts +47 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +77 -0
- package/dist/native.js.map +1 -0
- package/dist/rrule.d.ts +21 -0
- package/dist/rrule.d.ts.map +1 -0
- package/dist/rrule.js +58 -0
- package/dist/rrule.js.map +1 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +12 -0
- package/dist/utils.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
- package/src/Duration.ts +103 -32
package/dist/native.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal engine loader — auto-detects Node (NAPI) vs Browser (WASM).
|
|
3
|
+
*
|
|
4
|
+
* **No fallback.** If neither NAPI nor WASM loads, `nativeChronos()` throws
|
|
5
|
+
* immediately. Chronos operations (timezone, DST, RFC 5545) are too complex
|
|
6
|
+
* for a pure-TS fallback.
|
|
7
|
+
*/
|
|
8
|
+
let native;
|
|
9
|
+
let loadError;
|
|
10
|
+
const isNode = typeof globalThis.process !== "undefined" &&
|
|
11
|
+
typeof globalThis.process.versions?.node === "string";
|
|
12
|
+
if (isNode) {
|
|
13
|
+
try {
|
|
14
|
+
const { createRequire } = await import("node:module");
|
|
15
|
+
const { dirname, join } = await import("node:path");
|
|
16
|
+
const { fileURLToPath } = await import("node:url");
|
|
17
|
+
const { arch, platform } = await import("node:process");
|
|
18
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
19
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const platformMap = {
|
|
21
|
+
"linux-x64": "linux-x64-gnu",
|
|
22
|
+
"linux-arm64": "linux-arm64-gnu",
|
|
23
|
+
"darwin-x64": "darwin-x64",
|
|
24
|
+
"darwin-arm64": "darwin-arm64",
|
|
25
|
+
"win32-x64": "win32-x64-msvc",
|
|
26
|
+
};
|
|
27
|
+
const suffix = platformMap[`${platform}-${arch}`];
|
|
28
|
+
if (suffix) {
|
|
29
|
+
native = nodeRequire(join(currentDir, `../index.${suffix}.node`));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
loadError = e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
try {
|
|
38
|
+
// wasm-bindgen preserves Rust snake_case names (start_of, end_of, etc.)
|
|
39
|
+
// while NAPI-RS auto-converts to camelCase. We adapt the WASM module
|
|
40
|
+
// shape (which uses bigint for large integers) to the NativeChronos
|
|
41
|
+
// interface (number-based), so consumers don't deal with the difference.
|
|
42
|
+
const wasm = await import("../wasm/chronos_engine_wasm.js");
|
|
43
|
+
await wasm.default();
|
|
44
|
+
native = {
|
|
45
|
+
add: (iso, amount, unit) => wasm.add(iso, BigInt(amount), unit),
|
|
46
|
+
diff: (a, b, unit) => Number(wasm.diff(a, b, unit)),
|
|
47
|
+
startOf: (iso, unit) => wasm.start_of(iso, unit),
|
|
48
|
+
endOf: (iso, unit) => wasm.end_of(iso, unit),
|
|
49
|
+
format: (iso, pattern) => wasm.format(iso, pattern),
|
|
50
|
+
validateTimezone: (zone) => wasm.validate_timezone(zone),
|
|
51
|
+
toZone: (iso, zone) => wasm.to_zone(iso, zone),
|
|
52
|
+
addInZone: (iso, amount, unit, zone) => wasm.add_in_zone(iso, BigInt(amount), unit, zone),
|
|
53
|
+
diffInZone: (a, b, unit, zone) => Number(wasm.diff_in_zone(a, b, unit, zone)),
|
|
54
|
+
zoneOffset: (iso, zone) => wasm.zone_offset(iso, zone),
|
|
55
|
+
fromLocal: (naive, zone) => wasm.from_local(naive, zone),
|
|
56
|
+
parseRfc2822: (input) => wasm.parse_rfc2822(input),
|
|
57
|
+
parseSql: (input) => wasm.parse_sql(input),
|
|
58
|
+
parseHttp: (input) => wasm.parse_http(input),
|
|
59
|
+
rruleExpand: (start, rrule, limit) => wasm.rrule_expand(start, rrule, limit),
|
|
60
|
+
calendarParts: (iso) => wasm.calendar_parts(iso),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
loadError = e;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function nativeChronos() {
|
|
68
|
+
if (!native) {
|
|
69
|
+
throw new Error(`[CHRONOS_ENGINE_REQUIRED] The Chronos engine is required but not loaded.\n` +
|
|
70
|
+
` Environment: ${isNode ? "Node" : "Browser"}\n` +
|
|
71
|
+
` Reason: ${loadError ?? "binary not found"}\n` +
|
|
72
|
+
` Fix (Node): cd packages/chronos && pnpm build:napi\n` +
|
|
73
|
+
` Fix (Browser): cd packages/chronos && pnpm build:wasm`);
|
|
74
|
+
}
|
|
75
|
+
return native;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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"}
|
package/dist/rrule.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type RRuleWeekday = "MO" | "TU" | "WE" | "TH" | "FR" | "SA" | "SU";
|
|
2
|
+
export type RRuleByDayToken = RRuleWeekday | `${number}${RRuleWeekday}`;
|
|
3
|
+
export interface RRuleBuild {
|
|
4
|
+
freq: "SECONDLY" | "MINUTELY" | "HOURLY" | "DAILY" | "WEEKLY" | "MONTHLY" | "YEARLY";
|
|
5
|
+
interval?: number;
|
|
6
|
+
wkst?: RRuleWeekday;
|
|
7
|
+
byDay?: RRuleByDayToken[];
|
|
8
|
+
byMonthDay?: number[];
|
|
9
|
+
byMonth?: number[];
|
|
10
|
+
byWeekNo?: number[];
|
|
11
|
+
byYearDay?: number[];
|
|
12
|
+
bySetPos?: number[];
|
|
13
|
+
byHour?: number[];
|
|
14
|
+
byMinute?: number[];
|
|
15
|
+
bySecond?: number[];
|
|
16
|
+
count?: number;
|
|
17
|
+
until?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function toRRuleString(rule: RRuleBuild): string;
|
|
20
|
+
export declare function expandRRule(startIso: string, rrule: string | RRuleBuild, limit?: number): string[];
|
|
21
|
+
//# sourceMappingURL=rrule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rrule.d.ts","sourceRoot":"","sources":["../src/rrule.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1E,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,GAAG,MAAM,GAAG,YAAY,EAAE,CAAC;AAExE,MAAM,WAAW,UAAU;IAC1B,IAAI,EACD,UAAU,GACV,UAAU,GACV,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,SAAS,GACT,QAAQ,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAWD,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CA6BtD;AAgBD,wBAAgB,WAAW,CAC1B,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GAAG,UAAU,EAC1B,KAAK,SAAM,GACT,MAAM,EAAE,CAcV"}
|
package/dist/rrule.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { nativeChronos } from "./native.js";
|
|
2
|
+
function pushNumericList(parts, key, values) {
|
|
3
|
+
if (!values || values.length === 0)
|
|
4
|
+
return;
|
|
5
|
+
parts.push(`${key}=${values.join(",")}`);
|
|
6
|
+
}
|
|
7
|
+
export function toRRuleString(rule) {
|
|
8
|
+
const parts = [`FREQ=${rule.freq}`];
|
|
9
|
+
if (rule.interval && rule.interval !== 1)
|
|
10
|
+
parts.push(`INTERVAL=${rule.interval}`);
|
|
11
|
+
if (rule.wkst)
|
|
12
|
+
parts.push(`WKST=${rule.wkst}`);
|
|
13
|
+
if (rule.byDay?.length)
|
|
14
|
+
parts.push(`BYDAY=${rule.byDay.join(",")}`);
|
|
15
|
+
pushNumericList(parts, "BYMONTHDAY", rule.byMonthDay);
|
|
16
|
+
pushNumericList(parts, "BYMONTH", rule.byMonth);
|
|
17
|
+
pushNumericList(parts, "BYWEEKNO", rule.byWeekNo);
|
|
18
|
+
pushNumericList(parts, "BYYEARDAY", rule.byYearDay);
|
|
19
|
+
pushNumericList(parts, "BYSETPOS", rule.bySetPos);
|
|
20
|
+
pushNumericList(parts, "BYHOUR", rule.byHour);
|
|
21
|
+
pushNumericList(parts, "BYMINUTE", rule.byMinute);
|
|
22
|
+
pushNumericList(parts, "BYSECOND", rule.bySecond);
|
|
23
|
+
if (rule.count !== undefined)
|
|
24
|
+
parts.push(`COUNT=${rule.count}`);
|
|
25
|
+
if (rule.until) {
|
|
26
|
+
// RFC 5545 §3.3.10 UNTIL format: YYYYMMDDTHHMMSSZ (no hyphens/colons/ms).
|
|
27
|
+
parts.push(`UNTIL=${new Date(rule.until)
|
|
28
|
+
.toISOString()
|
|
29
|
+
.replace(/[-:]/g, "")
|
|
30
|
+
.replace(/\.\d+Z$/, "Z")}`);
|
|
31
|
+
}
|
|
32
|
+
return parts.join(";");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Expand an RRULE into concrete occurrence dates. **NAPI-only** — the Rust
|
|
36
|
+
* engine handles the full RFC 5545 spec (BYDAY, BYSETPOS, BYMONTHDAY,
|
|
37
|
+
* BYWEEKNO, BYYEARDAY, UNTIL, COUNT, INTERVAL, WKST). No TS fallback.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Hard cap on `expandRRule(... , limit)` so a caller passing an absurd
|
|
41
|
+
* number (or one derived from user input) can't ask the Rust engine to
|
|
42
|
+
* allocate gigabytes / run for seconds. 10_000 covers any realistic
|
|
43
|
+
* pagination + cushion (a year of every-minute occurrences = ~525k, so
|
|
44
|
+
* apps wanting that should iterate or stream, not call once).
|
|
45
|
+
*/
|
|
46
|
+
const MAX_RRULE_EXPAND = 10_000;
|
|
47
|
+
export function expandRRule(startIso, rrule, limit = 100) {
|
|
48
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
49
|
+
throw new RangeError(`expandRRule(limit) must be a positive integer (got ${String(limit)})`);
|
|
50
|
+
}
|
|
51
|
+
if (limit > MAX_RRULE_EXPAND) {
|
|
52
|
+
throw new RangeError(`expandRRule(limit=${limit}) exceeds the safety cap of ${MAX_RRULE_EXPAND}. ` +
|
|
53
|
+
"Page through the result set instead of asking for everything at once.");
|
|
54
|
+
}
|
|
55
|
+
const built = typeof rrule === "string" ? rrule : toRRuleString(rrule);
|
|
56
|
+
return nativeChronos().rruleExpand(startIso, built, limit);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=rrule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rrule.js","sourceRoot":"","sources":["../src/rrule.ts"],"names":[],"mappings":"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"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for DateTime and RRule modules.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Normalize an ISO 8601 string: strip the `.000` when subseconds are zero
|
|
6
|
+
* for compact output that matches `new Date(iso).toISOString().replace('.000Z', 'Z')`.
|
|
7
|
+
* When subseconds are non-zero, preserve them so precision isn't silently lost.
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizeIso(iso: string): string;
|
|
10
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for DateTime and RRule modules.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Normalize an ISO 8601 string: strip the `.000` when subseconds are zero
|
|
6
|
+
* for compact output that matches `new Date(iso).toISOString().replace('.000Z', 'Z')`.
|
|
7
|
+
* When subseconds are non-zero, preserve them so precision isn't silently lost.
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeIso(iso) {
|
|
10
|
+
return iso.replace(".000Z", "Z");
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACvC,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAClC,CAAC"}
|
package/index.darwin-arm64.node
CHANGED
|
Binary file
|
package/index.darwin-x64.node
CHANGED
|
Binary file
|
|
Binary file
|
package/index.linux-x64-gnu.node
CHANGED
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/src/Duration.ts
CHANGED
|
@@ -84,14 +84,18 @@ export class Duration {
|
|
|
84
84
|
* that cannot be mixed with other designators per the spec.
|
|
85
85
|
*/
|
|
86
86
|
static fromISO(iso: string): Duration {
|
|
87
|
-
const
|
|
87
|
+
const trimmed = iso.trim();
|
|
88
|
+
// A leading '-' marks a negative duration (the extension toISO emits).
|
|
89
|
+
const negative = trimmed.startsWith("-P");
|
|
90
|
+
const s = negative ? trimmed.slice(1) : trimmed;
|
|
88
91
|
if (!s.startsWith("P"))
|
|
89
92
|
throw new Error(`Invalid ISO 8601 duration: ${iso}`);
|
|
93
|
+
const apply = (d: Duration): Duration => (negative ? d.negate() : d);
|
|
90
94
|
|
|
91
95
|
// Week form: P3W
|
|
92
96
|
const weekMatch = /^P(\d+(?:\.\d+)?)W$/.exec(s);
|
|
93
97
|
if (weekMatch) {
|
|
94
|
-
return new Duration({ weeks: Number.parseFloat(weekMatch[1]) });
|
|
98
|
+
return apply(new Duration({ weeks: Number.parseFloat(weekMatch[1]) }));
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
const match =
|
|
@@ -101,19 +105,26 @@ export class Duration {
|
|
|
101
105
|
if (!match) throw new Error(`Invalid ISO 8601 duration: ${iso}`);
|
|
102
106
|
|
|
103
107
|
const [, y, mo, d, h, mi, sec] = match;
|
|
108
|
+
// The all-optional regex also matches bare "P"/"PT" — a duration needs at
|
|
109
|
+
// least one component. An explicit zero ("PT0S", "P0D") still parses.
|
|
110
|
+
if (!y && !mo && !d && !h && !mi && !sec) {
|
|
111
|
+
throw new Error(`Invalid ISO 8601 duration: ${iso}`);
|
|
112
|
+
}
|
|
104
113
|
const seconds = sec ? Math.floor(Number.parseFloat(sec)) : 0;
|
|
105
114
|
const milliseconds = sec
|
|
106
115
|
? Math.round((Number.parseFloat(sec) - seconds) * 1000)
|
|
107
116
|
: 0;
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
return apply(
|
|
118
|
+
new Duration({
|
|
119
|
+
years: y ? Number(y) : 0,
|
|
120
|
+
months: mo ? Number(mo) : 0,
|
|
121
|
+
days: d ? Number(d) : 0,
|
|
122
|
+
hours: h ? Number(h) : 0,
|
|
123
|
+
minutes: mi ? Number(mi) : 0,
|
|
124
|
+
seconds,
|
|
125
|
+
milliseconds,
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
117
128
|
}
|
|
118
129
|
|
|
119
130
|
// ─── Accessors ──────────────────────────────────────────
|
|
@@ -297,42 +308,102 @@ export class Duration {
|
|
|
297
308
|
|
|
298
309
|
// ─── Formatting ─────────────────────────────────────────
|
|
299
310
|
|
|
300
|
-
/**
|
|
311
|
+
/**
|
|
312
|
+
* ISO 8601 duration string (`P1Y2M3DT4H5M6S`). Always round-trips through
|
|
313
|
+
* {@link fromISO}.
|
|
314
|
+
*
|
|
315
|
+
* Two spec constraints are honoured: the week form (`PnW`) cannot mix with
|
|
316
|
+
* other designators — so weeks are emitted alone as `PnW`, otherwise folded
|
|
317
|
+
* into days; and durations are non-negative — a uniformly-negative duration
|
|
318
|
+
* (e.g. from {@link negate}) is emitted with a single leading `-` over
|
|
319
|
+
* absolute components, which `fromISO` parses back.
|
|
320
|
+
*/
|
|
301
321
|
toISO(): string {
|
|
302
322
|
const v = this.#values;
|
|
323
|
+
const units = ORDERED_UNITS.map((u) => v[u]);
|
|
324
|
+
const negative = units.some((n) => n < 0) && units.every((n) => n <= 0);
|
|
325
|
+
const sign = negative ? "-" : "";
|
|
326
|
+
const a = (n: number): number => (negative ? Math.abs(n) : n);
|
|
327
|
+
|
|
328
|
+
// Week form only when weeks is the sole unit; never combined with others.
|
|
329
|
+
const onlyWeeks =
|
|
330
|
+
v.weeks !== 0 &&
|
|
331
|
+
v.years === 0 &&
|
|
332
|
+
v.months === 0 &&
|
|
333
|
+
v.days === 0 &&
|
|
334
|
+
v.hours === 0 &&
|
|
335
|
+
v.minutes === 0 &&
|
|
336
|
+
v.seconds === 0 &&
|
|
337
|
+
v.milliseconds === 0;
|
|
338
|
+
if (onlyWeeks) return `${sign}P${a(v.weeks)}W`;
|
|
339
|
+
|
|
340
|
+
const days = v.days + v.weeks * 7;
|
|
303
341
|
let date = "";
|
|
304
|
-
if (v.years) date += `${v.years}Y`;
|
|
305
|
-
if (v.months) date += `${v.months}M`;
|
|
306
|
-
if (
|
|
307
|
-
if (v.days) date += `${v.days}D`;
|
|
342
|
+
if (v.years) date += `${a(v.years)}Y`;
|
|
343
|
+
if (v.months) date += `${a(v.months)}M`;
|
|
344
|
+
if (days) date += `${a(days)}D`;
|
|
308
345
|
let time = "";
|
|
309
|
-
if (v.hours) time += `${v.hours}H`;
|
|
310
|
-
if (v.minutes) time += `${v.minutes}M`;
|
|
346
|
+
if (v.hours) time += `${a(v.hours)}H`;
|
|
347
|
+
if (v.minutes) time += `${a(v.minutes)}M`;
|
|
311
348
|
const totalSec = v.seconds + v.milliseconds / 1000;
|
|
312
|
-
if (totalSec) time += `${totalSec}S`;
|
|
349
|
+
if (totalSec) time += `${a(totalSec)}S`;
|
|
313
350
|
if (!date && !time) return "PT0S";
|
|
314
|
-
return
|
|
351
|
+
return `${sign}P${date}${time ? `T${time}` : ""}`;
|
|
315
352
|
}
|
|
316
353
|
|
|
317
354
|
/**
|
|
318
355
|
* Simple pattern format: `hh:mm:ss`, `HH:mm`, etc. Supported tokens:
|
|
319
356
|
* `Y` (years), `M` (months), `d` (days), `h`/`hh` (hours), `m`/`mm`
|
|
320
357
|
* (minutes), `s`/`ss` (seconds), `S`/`SSS` (milliseconds).
|
|
358
|
+
*
|
|
359
|
+
* Wrap literal text in square brackets so its letters aren't read as tokens:
|
|
360
|
+
* `"h [hours], m [min]"` → `"2 hours, 30 min"`. A single left-to-right scan
|
|
361
|
+
* (longest token first) means substituted values are never re-scanned and
|
|
362
|
+
* literal/overlapping text is left intact.
|
|
321
363
|
*/
|
|
322
364
|
toFormat(pattern: string): string {
|
|
323
365
|
const v = this.normalize().#values;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
366
|
+
const tokens: Record<string, string> = {
|
|
367
|
+
SSS: String(v.milliseconds).padStart(3, "0"),
|
|
368
|
+
hh: String(v.hours).padStart(2, "0"),
|
|
369
|
+
mm: String(v.minutes).padStart(2, "0"),
|
|
370
|
+
ss: String(v.seconds).padStart(2, "0"),
|
|
371
|
+
S: String(v.milliseconds),
|
|
372
|
+
h: String(v.hours),
|
|
373
|
+
m: String(v.minutes),
|
|
374
|
+
s: String(v.seconds),
|
|
375
|
+
Y: String(v.years),
|
|
376
|
+
M: String(v.months),
|
|
377
|
+
d: String(v.days),
|
|
378
|
+
};
|
|
379
|
+
const order = ["SSS", "hh", "mm", "ss", "S", "h", "m", "s", "Y", "M", "d"];
|
|
380
|
+
let out = "";
|
|
381
|
+
let i = 0;
|
|
382
|
+
while (i < pattern.length) {
|
|
383
|
+
if (pattern[i] === "[") {
|
|
384
|
+
i++;
|
|
385
|
+
while (i < pattern.length && pattern[i] !== "]") {
|
|
386
|
+
out += pattern[i];
|
|
387
|
+
i++;
|
|
388
|
+
}
|
|
389
|
+
if (i < pattern.length) i++; // skip the closing ']'
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
let matched = false;
|
|
393
|
+
for (const key of order) {
|
|
394
|
+
if (pattern.startsWith(key, i)) {
|
|
395
|
+
out += tokens[key];
|
|
396
|
+
i += key.length;
|
|
397
|
+
matched = true;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (!matched) {
|
|
402
|
+
out += pattern[i];
|
|
403
|
+
i++;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return out;
|
|
336
407
|
}
|
|
337
408
|
|
|
338
409
|
/**
|