@vielzeug/tempo 1.0.2
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/README.md +71 -0
- package/dist/_convert.cjs +2 -0
- package/dist/_convert.cjs.map +1 -0
- package/dist/_convert.d.ts +47 -0
- package/dist/_convert.d.ts.map +1 -0
- package/dist/_convert.js +26 -0
- package/dist/_convert.js.map +1 -0
- package/dist/_floor.cjs +2 -0
- package/dist/_floor.cjs.map +1 -0
- package/dist/_floor.d.ts +12 -0
- package/dist/_floor.d.ts.map +1 -0
- package/dist/_floor.js +50 -0
- package/dist/_floor.js.map +1 -0
- package/dist/_tz.cjs +2 -0
- package/dist/_tz.cjs.map +1 -0
- package/dist/_tz.d.ts +15 -0
- package/dist/_tz.d.ts.map +1 -0
- package/dist/_tz.js +42 -0
- package/dist/_tz.js.map +1 -0
- package/dist/boundary.cjs +2 -0
- package/dist/boundary.cjs.map +1 -0
- package/dist/boundary.d.ts +26 -0
- package/dist/boundary.d.ts.map +1 -0
- package/dist/boundary.js +30 -0
- package/dist/boundary.js.map +1 -0
- package/dist/classify.cjs +2 -0
- package/dist/classify.cjs.map +1 -0
- package/dist/classify.d.ts +46 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/classify.js +83 -0
- package/dist/classify.js.map +1 -0
- package/dist/compare.cjs +2 -0
- package/dist/compare.cjs.map +1 -0
- package/dist/compare.d.ts +75 -0
- package/dist/compare.d.ts.map +1 -0
- package/dist/compare.js +71 -0
- package/dist/compare.js.map +1 -0
- package/dist/core.cjs +2 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.ts +136 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +89 -0
- package/dist/core.js.map +1 -0
- package/dist/errors.cjs +2 -0
- package/dist/errors.cjs.map +1 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +16 -0
- package/dist/errors.js.map +1 -0
- package/dist/format.cjs +2 -0
- package/dist/format.cjs.map +1 -0
- package/dist/format.d.ts +132 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +182 -0
- package/dist/format.js.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/range.cjs +2 -0
- package/dist/range.cjs.map +1 -0
- package/dist/range.d.ts +59 -0
- package/dist/range.d.ts.map +1 -0
- package/dist/range.js +38 -0
- package/dist/range.js.map +1 -0
- package/dist/tempo.cjs +2 -0
- package/dist/tempo.cjs.map +1 -0
- package/dist/tempo.iife.js +2 -0
- package/dist/tempo.iife.js.map +1 -0
- package/dist/tempo.js +2 -0
- package/dist/tempo.js.map +1 -0
- package/dist/types.d.ts +93 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# @vielzeug/tempo
|
|
2
|
+
|
|
3
|
+
> Temporal-powered parsing, timezone conversion, arithmetic (DST-safe), and Intl formatting for modern TypeScript.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@vielzeug/tempo) [](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
<details>
|
|
8
|
+
<summary>Quick Reference</summary>
|
|
9
|
+
|
|
10
|
+
**Package:** `@vielzeug/tempo` · **Category:** Date-time
|
|
11
|
+
|
|
12
|
+
**Key exports:** `now`, `parsePlainDateTime`, `parseInstant`, `parse`, `toInstant`, `inTz`, `shift`, `difference`, `format`, `formatRelative`, `isBefore`, `isAfter`, `isSame`, `startOf`, `endOf`, `expires`, `timeDiff`, `recurrence`
|
|
13
|
+
|
|
14
|
+
**When to use:** Temporal-powered parsing, timezone conversion, arithmetic (DST-safe), and Intl formatting for modern TypeScript.
|
|
15
|
+
|
|
16
|
+
**Related:** [@vielzeug/arsenal](https://vielzeug.dev/arsenal/)
|
|
17
|
+
|
|
18
|
+
</details>
|
|
19
|
+
|
|
20
|
+
`@vielzeug/tempo` is part of Vielzeug and ships as a zero-dependency TypeScript package with ESM+CJS output.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pnpm add @vielzeug/tempo
|
|
26
|
+
npm install @vielzeug/tempo
|
|
27
|
+
yarn add @vielzeug/tempo
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { format, formatInstant, inTz, parsePlainDateTime, shift, toInstant } from '@vielzeug/tempo';
|
|
34
|
+
|
|
35
|
+
// Parse a wall-clock string (no timezone attached)
|
|
36
|
+
const localMeeting = parsePlainDateTime('2026-03-21T10:30:00');
|
|
37
|
+
|
|
38
|
+
// Convert to an absolute instant using the user's timezone
|
|
39
|
+
const meetingInstant = toInstant(localMeeting, { tz: 'America/New_York' });
|
|
40
|
+
|
|
41
|
+
// Project to a zoned view and subtract 15 minutes (DST-safe)
|
|
42
|
+
const meetingNY = inTz(meetingInstant, 'America/New_York');
|
|
43
|
+
const reminder = shift(meetingNY, { minutes: -15 });
|
|
44
|
+
|
|
45
|
+
// Format for display
|
|
46
|
+
console.log(format(reminder, { pattern: 'short', locale: 'en-US', tz: 'America/New_York' }));
|
|
47
|
+
|
|
48
|
+
// Format for APIs / logs (stable UTC instant string)
|
|
49
|
+
console.log(formatInstant(reminder));
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Since many function names (`now`, `shift`, `clamp`, `difference`, …) are common in application code, use a namespace import to avoid collisions while still getting full tree-shaking:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import * as tempo from '@vielzeug/tempo';
|
|
56
|
+
|
|
57
|
+
tempo.now('UTC');
|
|
58
|
+
tempo.difference(start, end, { largestUnit: 'day', tz: 'UTC' });
|
|
59
|
+
tempo.format(meeting, { pattern: 'short', locale: 'en-US', tz: 'America/New_York' });
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Documentation
|
|
63
|
+
|
|
64
|
+
- [Overview](https://vielzeug.dev/tempo/)
|
|
65
|
+
- [Usage Guide](https://vielzeug.dev/tempo/usage)
|
|
66
|
+
- [API Reference](https://vielzeug.dev/tempo/api)
|
|
67
|
+
- [Examples](https://vielzeug.dev/tempo/examples)
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./_tz.cjs");let n=require("@js-temporal/polyfill");function r(r,i={}){if(r instanceof n.Temporal.Instant)return r;if(r instanceof n.Temporal.ZonedDateTime)return r.toInstant();if(r instanceof n.Temporal.PlainDateTime)return i.tz||e.fail(`This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.`,e.TempoMissingTzError),r.toZonedDateTime(t.validateTz(i.tz),{disambiguation:i.prefer}).toInstant();if(r instanceof n.Temporal.PlainDate)return i.tz||e.fail(`This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.`,e.TempoMissingTzError),r.toZonedDateTime({timeZone:t.validateTz(i.tz)}).toInstant();e.fail(`Unsupported time input type: ${String(r)}`,e.TempoUnsupportedInputError)}function i(r,i){let a=i,o=t.validateTz(a.tz);if(r instanceof n.Temporal.ZonedDateTime)return r.withTimeZone(o);if(r instanceof n.Temporal.PlainDateTime)return r.toZonedDateTime(o,{disambiguation:a.prefer});if(r instanceof n.Temporal.PlainDate)return r.toZonedDateTime({timeZone:o});if(r instanceof n.Temporal.Instant)return r.toZonedDateTimeISO(o);e.fail(`Unsupported time input type: ${String(r)}`,e.TempoUnsupportedInputError)}function a(e,t){return i(e,{tz:t})}exports.inTz=a,exports.toInstant=r,exports.toZoned=i;
|
|
2
|
+
//# sourceMappingURL=_convert.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_convert.cjs","names":[],"sources":["../src/_convert.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\n\nimport type { DateTimeDisambiguation, TimeInput } from './types';\n\nimport { validateTz } from './_tz';\nimport { TempoMissingTzError, TempoUnsupportedInputError, fail } from './errors';\n\ntype WithPrefer = { prefer?: DateTimeDisambiguation };\ntype TimeOptionsWithTz = { tz: string };\n\n// ─── Direct resolution ────────────────────────────────────────────────────────\n\n/**\n * Converts any {@link TimeInput} to an absolute `Instant`.\n * Requires `options.tz` when input is a `PlainDate` or `PlainDateTime`.\n */\nexport function toInstant(input: TimeInput, options: WithPrefer & { tz?: string } = {}): Temporal.Instant {\n if (input instanceof Temporal.Instant) return input;\n\n if (input instanceof Temporal.ZonedDateTime) return input.toInstant();\n\n if (input instanceof Temporal.PlainDateTime) {\n if (!options.tz)\n fail('This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.', TempoMissingTzError);\n\n return input\n .toZonedDateTime(validateTz(options.tz), {\n disambiguation: options.prefer as 'compatible' | 'earlier' | 'later' | 'reject' | undefined,\n })\n .toInstant();\n }\n\n if (input instanceof Temporal.PlainDate) {\n if (!options.tz)\n fail('This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.', TempoMissingTzError);\n\n return input.toZonedDateTime({ timeZone: validateTz(options.tz) }).toInstant();\n }\n\n fail(`Unsupported time input type: ${String(input)}`, TempoUnsupportedInputError);\n}\n\n/**\n * Projects any {@link TimeInput} into `options.tz` as a `ZonedDateTime`.\n *\n * When `input` is already a `ZonedDateTime`, it is **re-projected** into `options.tz`\n * via `withTimeZone()` — the wall-clock time changes but the absolute instant is preserved.\n *\n * @example\n * ```ts\n * // Re-projection: same instant, different wall-clock\n * toZoned(parseZoned('2026-03-21T11:00:00+01:00[Europe/Berlin]'), { tz: 'UTC' })\n * // 2026-03-21T10:00:00+00:00[UTC] ← wall-clock changed from 11:00 → 10:00\n * ```\n */\nexport function toZoned(\n input: TimeInput,\n options: TimeOptionsWithTz | (WithPrefer & { tz: string }),\n): Temporal.ZonedDateTime {\n const opts = options as TimeOptionsWithTz & WithPrefer;\n const tz = validateTz(opts.tz);\n\n if (input instanceof Temporal.ZonedDateTime) return input.withTimeZone(tz);\n\n if (input instanceof Temporal.PlainDateTime) {\n return input.toZonedDateTime(tz, {\n disambiguation: opts.prefer as 'compatible' | 'earlier' | 'later' | 'reject' | undefined,\n });\n }\n\n if (input instanceof Temporal.PlainDate) {\n return input.toZonedDateTime({ timeZone: tz });\n }\n\n if (input instanceof Temporal.Instant) return input.toZonedDateTimeISO(tz);\n\n fail(`Unsupported time input type: ${String(input)}`, TempoUnsupportedInputError);\n}\n\n/**\n * Projects any {@link TimeInput} into a specific timezone as a `ZonedDateTime`.\n * Unlike {@link toZoned}, this is the clean public API: explicit `tz` string parameter\n * rather than an options bag, signalling intent clearly.\n *\n * When `input` is already a `ZonedDateTime`, it is re-projected — same instant, new zone.\n *\n * @example\n * ```ts\n * inTz(parseInstant('2026-03-21T10:00:00Z'), 'Europe/Berlin')\n * // 2026-03-21T11:00:00+01:00[Europe/Berlin]\n * ```\n */\nexport function inTz(input: TimeInput, tz: string): Temporal.ZonedDateTime {\n return toZoned(input, { tz });\n}\n"],"mappings":"8FAgBA,SAAgB,EAAU,EAAkB,EAAwC,CAAC,EAAqB,CACxG,GAAI,aAAiB,EAAA,SAAS,QAAS,OAAO,EAE9C,GAAI,aAAiB,EAAA,SAAS,cAAe,OAAO,EAAM,UAAU,EAEpE,GAAI,aAAiB,EAAA,SAAS,cAI5B,OAHK,EAAQ,IACX,EAAA,KAAK,oFAAqF,EAAA,mBAAmB,EAExG,EACJ,gBAAgB,EAAA,WAAW,EAAQ,EAAE,EAAG,CACvC,eAAgB,EAAQ,MAC1B,CAAC,CAAC,CACD,UAAU,EAGf,GAAI,aAAiB,EAAA,SAAS,UAI5B,OAHK,EAAQ,IACX,EAAA,KAAK,oFAAqF,EAAA,mBAAmB,EAExG,EAAM,gBAAgB,CAAE,SAAU,EAAA,WAAW,EAAQ,EAAE,CAAE,CAAC,CAAC,CAAC,UAAU,EAG/E,EAAA,KAAK,gCAAgC,OAAO,CAAK,IAAK,EAAA,0BAA0B,CAClF,CAeA,SAAgB,EACd,EACA,EACwB,CACxB,IAAM,EAAO,EACP,EAAK,EAAA,WAAW,EAAK,EAAE,EAE7B,GAAI,aAAiB,EAAA,SAAS,cAAe,OAAO,EAAM,aAAa,CAAE,EAEzE,GAAI,aAAiB,EAAA,SAAS,cAC5B,OAAO,EAAM,gBAAgB,EAAI,CAC/B,eAAgB,EAAK,MACvB,CAAC,EAGH,GAAI,aAAiB,EAAA,SAAS,UAC5B,OAAO,EAAM,gBAAgB,CAAE,SAAU,CAAG,CAAC,EAG/C,GAAI,aAAiB,EAAA,SAAS,QAAS,OAAO,EAAM,mBAAmB,CAAE,EAEzE,EAAA,KAAK,gCAAgC,OAAO,CAAK,IAAK,EAAA,0BAA0B,CAClF,CAeA,SAAgB,EAAK,EAAkB,EAAoC,CACzE,OAAO,EAAQ,EAAO,CAAE,IAAG,CAAC,CAC9B"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
+
import type { DateTimeDisambiguation, TimeInput } from './types';
|
|
3
|
+
type WithPrefer = {
|
|
4
|
+
prefer?: DateTimeDisambiguation;
|
|
5
|
+
};
|
|
6
|
+
type TimeOptionsWithTz = {
|
|
7
|
+
tz: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Converts any {@link TimeInput} to an absolute `Instant`.
|
|
11
|
+
* Requires `options.tz` when input is a `PlainDate` or `PlainDateTime`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function toInstant(input: TimeInput, options?: WithPrefer & {
|
|
14
|
+
tz?: string;
|
|
15
|
+
}): Temporal.Instant;
|
|
16
|
+
/**
|
|
17
|
+
* Projects any {@link TimeInput} into `options.tz` as a `ZonedDateTime`.
|
|
18
|
+
*
|
|
19
|
+
* When `input` is already a `ZonedDateTime`, it is **re-projected** into `options.tz`
|
|
20
|
+
* via `withTimeZone()` — the wall-clock time changes but the absolute instant is preserved.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* // Re-projection: same instant, different wall-clock
|
|
25
|
+
* toZoned(parseZoned('2026-03-21T11:00:00+01:00[Europe/Berlin]'), { tz: 'UTC' })
|
|
26
|
+
* // 2026-03-21T10:00:00+00:00[UTC] ← wall-clock changed from 11:00 → 10:00
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function toZoned(input: TimeInput, options: TimeOptionsWithTz | (WithPrefer & {
|
|
30
|
+
tz: string;
|
|
31
|
+
})): Temporal.ZonedDateTime;
|
|
32
|
+
/**
|
|
33
|
+
* Projects any {@link TimeInput} into a specific timezone as a `ZonedDateTime`.
|
|
34
|
+
* Unlike {@link toZoned}, this is the clean public API: explicit `tz` string parameter
|
|
35
|
+
* rather than an options bag, signalling intent clearly.
|
|
36
|
+
*
|
|
37
|
+
* When `input` is already a `ZonedDateTime`, it is re-projected — same instant, new zone.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* inTz(parseInstant('2026-03-21T10:00:00Z'), 'Europe/Berlin')
|
|
42
|
+
* // 2026-03-21T11:00:00+01:00[Europe/Berlin]
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function inTz(input: TimeInput, tz: string): Temporal.ZonedDateTime;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=_convert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_convert.d.ts","sourceRoot":"","sources":["../src/_convert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,KAAK,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAKjE,KAAK,UAAU,GAAG;IAAE,MAAM,CAAC,EAAE,sBAAsB,CAAA;CAAE,CAAC;AACtD,KAAK,iBAAiB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAIxC;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,UAAU,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,QAAQ,CAAC,OAAO,CAwBxG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CACrB,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,iBAAiB,GAAG,CAAC,UAAU,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GACzD,QAAQ,CAAC,aAAa,CAmBxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,GAAG,QAAQ,CAAC,aAAa,CAEzE"}
|
package/dist/_convert.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TempoMissingTzError as e, TempoUnsupportedInputError as t, fail as n } from "./errors.js";
|
|
2
|
+
import { validateTz as r } from "./_tz.js";
|
|
3
|
+
import { Temporal as i } from "@js-temporal/polyfill";
|
|
4
|
+
//#region src/_convert.ts
|
|
5
|
+
function a(a, o = {}) {
|
|
6
|
+
if (a instanceof i.Instant) return a;
|
|
7
|
+
if (a instanceof i.ZonedDateTime) return a.toInstant();
|
|
8
|
+
if (a instanceof i.PlainDateTime) return o.tz || n("This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.", e), a.toZonedDateTime(r(o.tz), { disambiguation: o.prefer }).toInstant();
|
|
9
|
+
if (a instanceof i.PlainDate) return o.tz || n("This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.", e), a.toZonedDateTime({ timeZone: r(o.tz) }).toInstant();
|
|
10
|
+
n(`Unsupported time input type: ${String(a)}`, t);
|
|
11
|
+
}
|
|
12
|
+
function o(e, a) {
|
|
13
|
+
let o = a, s = r(o.tz);
|
|
14
|
+
if (e instanceof i.ZonedDateTime) return e.withTimeZone(s);
|
|
15
|
+
if (e instanceof i.PlainDateTime) return e.toZonedDateTime(s, { disambiguation: o.prefer });
|
|
16
|
+
if (e instanceof i.PlainDate) return e.toZonedDateTime({ timeZone: s });
|
|
17
|
+
if (e instanceof i.Instant) return e.toZonedDateTimeISO(s);
|
|
18
|
+
n(`Unsupported time input type: ${String(e)}`, t);
|
|
19
|
+
}
|
|
20
|
+
function s(e, t) {
|
|
21
|
+
return o(e, { tz: t });
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { s as inTz, a as toInstant, o as toZoned };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=_convert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_convert.js","names":[],"sources":["../src/_convert.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\n\nimport type { DateTimeDisambiguation, TimeInput } from './types';\n\nimport { validateTz } from './_tz';\nimport { TempoMissingTzError, TempoUnsupportedInputError, fail } from './errors';\n\ntype WithPrefer = { prefer?: DateTimeDisambiguation };\ntype TimeOptionsWithTz = { tz: string };\n\n// ─── Direct resolution ────────────────────────────────────────────────────────\n\n/**\n * Converts any {@link TimeInput} to an absolute `Instant`.\n * Requires `options.tz` when input is a `PlainDate` or `PlainDateTime`.\n */\nexport function toInstant(input: TimeInput, options: WithPrefer & { tz?: string } = {}): Temporal.Instant {\n if (input instanceof Temporal.Instant) return input;\n\n if (input instanceof Temporal.ZonedDateTime) return input.toInstant();\n\n if (input instanceof Temporal.PlainDateTime) {\n if (!options.tz)\n fail('This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.', TempoMissingTzError);\n\n return input\n .toZonedDateTime(validateTz(options.tz), {\n disambiguation: options.prefer as 'compatible' | 'earlier' | 'later' | 'reject' | undefined,\n })\n .toInstant();\n }\n\n if (input instanceof Temporal.PlainDate) {\n if (!options.tz)\n fail('This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.', TempoMissingTzError);\n\n return input.toZonedDateTime({ timeZone: validateTz(options.tz) }).toInstant();\n }\n\n fail(`Unsupported time input type: ${String(input)}`, TempoUnsupportedInputError);\n}\n\n/**\n * Projects any {@link TimeInput} into `options.tz` as a `ZonedDateTime`.\n *\n * When `input` is already a `ZonedDateTime`, it is **re-projected** into `options.tz`\n * via `withTimeZone()` — the wall-clock time changes but the absolute instant is preserved.\n *\n * @example\n * ```ts\n * // Re-projection: same instant, different wall-clock\n * toZoned(parseZoned('2026-03-21T11:00:00+01:00[Europe/Berlin]'), { tz: 'UTC' })\n * // 2026-03-21T10:00:00+00:00[UTC] ← wall-clock changed from 11:00 → 10:00\n * ```\n */\nexport function toZoned(\n input: TimeInput,\n options: TimeOptionsWithTz | (WithPrefer & { tz: string }),\n): Temporal.ZonedDateTime {\n const opts = options as TimeOptionsWithTz & WithPrefer;\n const tz = validateTz(opts.tz);\n\n if (input instanceof Temporal.ZonedDateTime) return input.withTimeZone(tz);\n\n if (input instanceof Temporal.PlainDateTime) {\n return input.toZonedDateTime(tz, {\n disambiguation: opts.prefer as 'compatible' | 'earlier' | 'later' | 'reject' | undefined,\n });\n }\n\n if (input instanceof Temporal.PlainDate) {\n return input.toZonedDateTime({ timeZone: tz });\n }\n\n if (input instanceof Temporal.Instant) return input.toZonedDateTimeISO(tz);\n\n fail(`Unsupported time input type: ${String(input)}`, TempoUnsupportedInputError);\n}\n\n/**\n * Projects any {@link TimeInput} into a specific timezone as a `ZonedDateTime`.\n * Unlike {@link toZoned}, this is the clean public API: explicit `tz` string parameter\n * rather than an options bag, signalling intent clearly.\n *\n * When `input` is already a `ZonedDateTime`, it is re-projected — same instant, new zone.\n *\n * @example\n * ```ts\n * inTz(parseInstant('2026-03-21T10:00:00Z'), 'Europe/Berlin')\n * // 2026-03-21T11:00:00+01:00[Europe/Berlin]\n * ```\n */\nexport function inTz(input: TimeInput, tz: string): Temporal.ZonedDateTime {\n return toZoned(input, { tz });\n}\n"],"mappings":";;;;AAgBA,SAAgB,EAAU,GAAkB,IAAwC,CAAC,GAAqB;CACxG,IAAI,aAAiB,EAAS,SAAS,OAAO;CAE9C,IAAI,aAAiB,EAAS,eAAe,OAAO,EAAM,UAAU;CAEpE,IAAI,aAAiB,EAAS,eAI5B,OAHK,EAAQ,MACX,EAAK,qFAAqF,CAAmB,GAExG,EACJ,gBAAgB,EAAW,EAAQ,EAAE,GAAG,EACvC,gBAAgB,EAAQ,OAC1B,CAAC,CAAC,CACD,UAAU;CAGf,IAAI,aAAiB,EAAS,WAI5B,OAHK,EAAQ,MACX,EAAK,qFAAqF,CAAmB,GAExG,EAAM,gBAAgB,EAAE,UAAU,EAAW,EAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU;CAG/E,EAAK,gCAAgC,OAAO,CAAK,KAAK,CAA0B;AAClF;AAeA,SAAgB,EACd,GACA,GACwB;CACxB,IAAM,IAAO,GACP,IAAK,EAAW,EAAK,EAAE;CAE7B,IAAI,aAAiB,EAAS,eAAe,OAAO,EAAM,aAAa,CAAE;CAEzE,IAAI,aAAiB,EAAS,eAC5B,OAAO,EAAM,gBAAgB,GAAI,EAC/B,gBAAgB,EAAK,OACvB,CAAC;CAGH,IAAI,aAAiB,EAAS,WAC5B,OAAO,EAAM,gBAAgB,EAAE,UAAU,EAAG,CAAC;CAG/C,IAAI,aAAiB,EAAS,SAAS,OAAO,EAAM,mBAAmB,CAAE;CAEzE,EAAK,gCAAgC,OAAO,CAAK,KAAK,CAA0B;AAClF;AAeA,SAAgB,EAAK,GAAkB,GAAoC;CACzE,OAAO,EAAQ,GAAO,EAAE,MAAG,CAAC;AAC9B"}
|
package/dist/_floor.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./_convert.cjs");require("@js-temporal/polyfill");var t={hour:0,microsecond:0,millisecond:0,minute:0,nanosecond:0,second:0},n={day:t,hour:{microsecond:0,millisecond:0,minute:0,nanosecond:0,second:0},minute:{microsecond:0,millisecond:0,nanosecond:0,second:0},month:{...t,day:1},year:{...t,day:1,month:1}};function r(r,i,a){let o=e.toZoned(r,{prefer:a.prefer,tz:a.tz});if(i===`week`){let e=(o.dayOfWeek-(a.weekStartsOn??1)+7)%7;return o.subtract({days:e}).with(t).toInstant()}return o.with(n[i]).toInstant()}exports.floorToUnit=r;
|
|
2
|
+
//# sourceMappingURL=_floor.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_floor.cjs","names":[],"sources":["../src/_floor.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\n\nimport type { BoundaryUnit, DateTimeDisambiguation, TimeInput } from './types';\n\nimport { toZoned } from './_convert';\n\n// ─── Floor-to-boundary-unit helper ───────────────────────────────────────────\n\nconst TIME_ZERO: Temporal.ZonedDateTimeLike = {\n hour: 0,\n microsecond: 0,\n millisecond: 0,\n minute: 0,\n nanosecond: 0,\n second: 0,\n};\n\nconst BOUNDARY_CLEAR: Record<Exclude<BoundaryUnit, 'week'>, Temporal.ZonedDateTimeLike> = {\n day: TIME_ZERO,\n hour: { microsecond: 0, millisecond: 0, minute: 0, nanosecond: 0, second: 0 },\n minute: { microsecond: 0, millisecond: 0, nanosecond: 0, second: 0 },\n month: { ...TIME_ZERO, day: 1 },\n year: { ...TIME_ZERO, day: 1, month: 1 },\n};\n\n/**\n * Floors `input` to the start of `unit` in `tz`, returning an `Instant`.\n * Used internally by both `boundary.ts` and `compare.ts` without either depending on the other.\n */\nexport function floorToUnit(\n input: TimeInput,\n unit: BoundaryUnit,\n options: { prefer?: DateTimeDisambiguation; tz: string; weekStartsOn?: number },\n): Temporal.Instant {\n const zoned = toZoned(input, { prefer: options.prefer, tz: options.tz });\n\n if (unit === 'week') {\n const daysToSubtract = (zoned.dayOfWeek - (options.weekStartsOn ?? 1) + 7) % 7;\n\n return zoned.subtract({ days: daysToSubtract }).with(TIME_ZERO).toInstant();\n }\n\n return zoned.with(BOUNDARY_CLEAR[unit]).toInstant();\n}\n"],"mappings":"mEAQA,IAAM,EAAwC,CAC5C,KAAM,EACN,YAAa,EACb,YAAa,EACb,OAAQ,EACR,WAAY,EACZ,OAAQ,CACV,EAEM,EAAoF,CACxF,IAAK,EACL,KAAM,CAAE,YAAa,EAAG,YAAa,EAAG,OAAQ,EAAG,WAAY,EAAG,OAAQ,CAAE,EAC5E,OAAQ,CAAE,YAAa,EAAG,YAAa,EAAG,WAAY,EAAG,OAAQ,CAAE,EACnE,MAAO,CAAE,GAAG,EAAW,IAAK,CAAE,EAC9B,KAAM,CAAE,GAAG,EAAW,IAAK,EAAG,MAAO,CAAE,CACzC,EAMA,SAAgB,EACd,EACA,EACA,EACkB,CAClB,IAAM,EAAQ,EAAA,QAAQ,EAAO,CAAE,OAAQ,EAAQ,OAAQ,GAAI,EAAQ,EAAG,CAAC,EAEvE,GAAI,IAAS,OAAQ,CACnB,IAAM,GAAkB,EAAM,WAAa,EAAQ,cAAgB,GAAK,GAAK,EAE7E,OAAO,EAAM,SAAS,CAAE,KAAM,CAAe,CAAC,CAAC,CAAC,KAAK,CAAS,CAAC,CAAC,UAAU,CAC5E,CAEA,OAAO,EAAM,KAAK,EAAe,EAAK,CAAC,CAAC,UAAU,CACpD"}
|
package/dist/_floor.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
+
import type { BoundaryUnit, DateTimeDisambiguation, TimeInput } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Floors `input` to the start of `unit` in `tz`, returning an `Instant`.
|
|
5
|
+
* Used internally by both `boundary.ts` and `compare.ts` without either depending on the other.
|
|
6
|
+
*/
|
|
7
|
+
export declare function floorToUnit(input: TimeInput, unit: BoundaryUnit, options: {
|
|
8
|
+
prefer?: DateTimeDisambiguation;
|
|
9
|
+
tz: string;
|
|
10
|
+
weekStartsOn?: number;
|
|
11
|
+
}): Temporal.Instant;
|
|
12
|
+
//# sourceMappingURL=_floor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_floor.d.ts","sourceRoot":"","sources":["../src/_floor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAuB/E;;;GAGG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,YAAY,EAClB,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9E,QAAQ,CAAC,OAAO,CAUlB"}
|
package/dist/_floor.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { toZoned as e } from "./_convert.js";
|
|
2
|
+
import "@js-temporal/polyfill";
|
|
3
|
+
//#region src/_floor.ts
|
|
4
|
+
var t = {
|
|
5
|
+
hour: 0,
|
|
6
|
+
microsecond: 0,
|
|
7
|
+
millisecond: 0,
|
|
8
|
+
minute: 0,
|
|
9
|
+
nanosecond: 0,
|
|
10
|
+
second: 0
|
|
11
|
+
}, n = {
|
|
12
|
+
day: t,
|
|
13
|
+
hour: {
|
|
14
|
+
microsecond: 0,
|
|
15
|
+
millisecond: 0,
|
|
16
|
+
minute: 0,
|
|
17
|
+
nanosecond: 0,
|
|
18
|
+
second: 0
|
|
19
|
+
},
|
|
20
|
+
minute: {
|
|
21
|
+
microsecond: 0,
|
|
22
|
+
millisecond: 0,
|
|
23
|
+
nanosecond: 0,
|
|
24
|
+
second: 0
|
|
25
|
+
},
|
|
26
|
+
month: {
|
|
27
|
+
...t,
|
|
28
|
+
day: 1
|
|
29
|
+
},
|
|
30
|
+
year: {
|
|
31
|
+
...t,
|
|
32
|
+
day: 1,
|
|
33
|
+
month: 1
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
function r(r, i, a) {
|
|
37
|
+
let o = e(r, {
|
|
38
|
+
prefer: a.prefer,
|
|
39
|
+
tz: a.tz
|
|
40
|
+
});
|
|
41
|
+
if (i === "week") {
|
|
42
|
+
let e = (o.dayOfWeek - (a.weekStartsOn ?? 1) + 7) % 7;
|
|
43
|
+
return o.subtract({ days: e }).with(t).toInstant();
|
|
44
|
+
}
|
|
45
|
+
return o.with(n[i]).toInstant();
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { r as floorToUnit };
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=_floor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_floor.js","names":[],"sources":["../src/_floor.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\n\nimport type { BoundaryUnit, DateTimeDisambiguation, TimeInput } from './types';\n\nimport { toZoned } from './_convert';\n\n// ─── Floor-to-boundary-unit helper ───────────────────────────────────────────\n\nconst TIME_ZERO: Temporal.ZonedDateTimeLike = {\n hour: 0,\n microsecond: 0,\n millisecond: 0,\n minute: 0,\n nanosecond: 0,\n second: 0,\n};\n\nconst BOUNDARY_CLEAR: Record<Exclude<BoundaryUnit, 'week'>, Temporal.ZonedDateTimeLike> = {\n day: TIME_ZERO,\n hour: { microsecond: 0, millisecond: 0, minute: 0, nanosecond: 0, second: 0 },\n minute: { microsecond: 0, millisecond: 0, nanosecond: 0, second: 0 },\n month: { ...TIME_ZERO, day: 1 },\n year: { ...TIME_ZERO, day: 1, month: 1 },\n};\n\n/**\n * Floors `input` to the start of `unit` in `tz`, returning an `Instant`.\n * Used internally by both `boundary.ts` and `compare.ts` without either depending on the other.\n */\nexport function floorToUnit(\n input: TimeInput,\n unit: BoundaryUnit,\n options: { prefer?: DateTimeDisambiguation; tz: string; weekStartsOn?: number },\n): Temporal.Instant {\n const zoned = toZoned(input, { prefer: options.prefer, tz: options.tz });\n\n if (unit === 'week') {\n const daysToSubtract = (zoned.dayOfWeek - (options.weekStartsOn ?? 1) + 7) % 7;\n\n return zoned.subtract({ days: daysToSubtract }).with(TIME_ZERO).toInstant();\n }\n\n return zoned.with(BOUNDARY_CLEAR[unit]).toInstant();\n}\n"],"mappings":";;;AAQA,IAAM,IAAwC;CAC5C,MAAM;CACN,aAAa;CACb,aAAa;CACb,QAAQ;CACR,YAAY;CACZ,QAAQ;AACV,GAEM,IAAoF;CACxF,KAAK;CACL,MAAM;EAAE,aAAa;EAAG,aAAa;EAAG,QAAQ;EAAG,YAAY;EAAG,QAAQ;CAAE;CAC5E,QAAQ;EAAE,aAAa;EAAG,aAAa;EAAG,YAAY;EAAG,QAAQ;CAAE;CACnE,OAAO;EAAE,GAAG;EAAW,KAAK;CAAE;CAC9B,MAAM;EAAE,GAAG;EAAW,KAAK;EAAG,OAAO;CAAE;AACzC;AAMA,SAAgB,EACd,GACA,GACA,GACkB;CAClB,IAAM,IAAQ,EAAQ,GAAO;EAAE,QAAQ,EAAQ;EAAQ,IAAI,EAAQ;CAAG,CAAC;CAEvE,IAAI,MAAS,QAAQ;EACnB,IAAM,KAAkB,EAAM,aAAa,EAAQ,gBAAgB,KAAK,KAAK;EAE7E,OAAO,EAAM,SAAS,EAAE,MAAM,EAAe,CAAC,CAAC,CAAC,KAAK,CAAS,CAAC,CAAC,UAAU;CAC5E;CAEA,OAAO,EAAM,KAAK,EAAe,EAAK,CAAC,CAAC,UAAU;AACpD"}
|
package/dist/_tz.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./errors.cjs");let t=require("@js-temporal/polyfill");function n(n){try{t.Temporal.Instant.fromEpochMilliseconds(0).toZonedDateTimeISO(n)}catch{e.fail(`Unknown or invalid timezone: "${n}". Expected an IANA timezone name (e.g. "America/New_York") or UTC offset (e.g. "+05:30").`,e.TempoInvalidTzError)}return n}function r(r,i){let a=i.tz??(r instanceof t.Temporal.ZonedDateTime?r.timeZoneId:void 0);return a||e.fail(`This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.`,e.TempoMissingTzError),n(a)}function i(r,i){if(i.tz)return n(i.tz);let a;for(let n of r){if(!(n instanceof t.Temporal.ZonedDateTime))continue;let r=n.timeZoneId;if(!a){a=r;continue}a!==r&&e.fail(`Comparison received ZonedDateTime inputs with different time zones. Pass options.tz explicitly.`)}return a||e.fail(`This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.`,e.TempoMissingTzError),a}function a(e,n){return t.Temporal.Instant.compare(e,n)<=0?[e,n]:[n,e]}var o=new Set([`day`,`month`,`week`,`year`]),s=30.4375*864e5;exports.CALENDAR_UNITS=o,exports.MS_PER_MONTH=s,exports.inferSharedTimeZone=i,exports.inferTimeZone=r,exports.normalizeRange=a,exports.validateTz=n;
|
|
2
|
+
//# sourceMappingURL=_tz.cjs.map
|
package/dist/_tz.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_tz.cjs","names":[],"sources":["../src/_tz.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\n\nimport type { CalendarUnit, TimeInput } from './types';\n\nimport { TempoInvalidTzError, TempoMissingTzError, fail } from './errors';\n\n// ─── Timezone validation ──────────────────────────────────────────────────────\n\nexport function validateTz(tz: string): string {\n try {\n Temporal.Instant.fromEpochMilliseconds(0).toZonedDateTimeISO(tz);\n } catch {\n fail(\n `Unknown or invalid timezone: \"${tz}\". Expected an IANA timezone name (e.g. \"America/New_York\") or UTC offset (e.g. \"+05:30\").`,\n TempoInvalidTzError,\n );\n }\n\n return tz;\n}\n\n// ─── Timezone inference ───────────────────────────────────────────────────────\n\nexport function inferTimeZone(input: TimeInput, options: { tz?: string }): string {\n const tz = options.tz ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n if (!tz)\n fail('This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.', TempoMissingTzError);\n\n return validateTz(tz);\n}\n\nexport function inferSharedTimeZone(inputs: TimeInput[], options: { tz?: string }): string {\n if (options.tz) return validateTz(options.tz);\n\n let inferred: string | undefined;\n\n for (const input of inputs) {\n if (!(input instanceof Temporal.ZonedDateTime)) continue;\n\n const tz = input.timeZoneId;\n\n if (!inferred) {\n inferred = tz;\n continue;\n }\n\n if (inferred !== tz) {\n fail('Comparison received ZonedDateTime inputs with different time zones. Pass options.tz explicitly.');\n }\n }\n\n if (!inferred)\n fail('This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.', TempoMissingTzError);\n\n return inferred;\n}\n\n// ─── Range normalization ──────────────────────────────────────────────────────\n\nexport function normalizeRange(start: Temporal.Instant, end: Temporal.Instant): [Temporal.Instant, Temporal.Instant] {\n return Temporal.Instant.compare(start, end) <= 0 ? [start, end] : [end, start];\n}\n\n// ─── Shared constants ─────────────────────────────────────────────────────────\n\n/** Units that require timezone-aware context for calendar-accurate operations. */\nexport const CALENDAR_UNITS = new Set<CalendarUnit>(['day', 'month', 'week', 'year']);\n\n/** Approximate millisecond constants for threshold arithmetic. */\nexport const MS_PER_MONTH = 30.4375 * 86_400_000; // 365.25 / 12 days × 86400 s × 1000\n"],"mappings":"uEAQA,SAAgB,EAAW,EAAoB,CAC7C,GAAI,CACF,EAAA,SAAS,QAAQ,sBAAsB,CAAC,CAAC,CAAC,mBAAmB,CAAE,CACjE,MAAQ,CACN,EAAA,KACE,iCAAiC,EAAG,4FACpC,EAAA,mBACF,CACF,CAEA,OAAO,CACT,CAIA,SAAgB,EAAc,EAAkB,EAAkC,CAChF,IAAM,EAAK,EAAQ,KAAO,aAAiB,EAAA,SAAS,cAAgB,EAAM,WAAa,IAAA,IAKvF,OAHK,GACH,EAAA,KAAK,oFAAqF,EAAA,mBAAmB,EAExG,EAAW,CAAE,CACtB,CAEA,SAAgB,EAAoB,EAAqB,EAAkC,CACzF,GAAI,EAAQ,GAAI,OAAO,EAAW,EAAQ,EAAE,EAE5C,IAAI,EAEJ,IAAK,IAAM,KAAS,EAAQ,CAC1B,GAAI,EAAE,aAAiB,EAAA,SAAS,eAAgB,SAEhD,IAAM,EAAK,EAAM,WAEjB,GAAI,CAAC,EAAU,CACb,EAAW,EACX,QACF,CAEI,IAAa,GACf,EAAA,KAAK,iGAAiG,CAE1G,CAKA,OAHK,GACH,EAAA,KAAK,oFAAqF,EAAA,mBAAmB,EAExG,CACT,CAIA,SAAgB,EAAe,EAAyB,EAA6D,CACnH,OAAO,EAAA,SAAS,QAAQ,QAAQ,EAAO,CAAG,GAAK,EAAI,CAAC,EAAO,CAAG,EAAI,CAAC,EAAK,CAAK,CAC/E,CAKA,IAAa,EAAiB,IAAI,IAAkB,CAAC,MAAO,QAAS,OAAQ,MAAM,CAAC,EAGvE,EAAe,QAAU"}
|
package/dist/_tz.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
+
import type { CalendarUnit, TimeInput } from './types';
|
|
3
|
+
export declare function validateTz(tz: string): string;
|
|
4
|
+
export declare function inferTimeZone(input: TimeInput, options: {
|
|
5
|
+
tz?: string;
|
|
6
|
+
}): string;
|
|
7
|
+
export declare function inferSharedTimeZone(inputs: TimeInput[], options: {
|
|
8
|
+
tz?: string;
|
|
9
|
+
}): string;
|
|
10
|
+
export declare function normalizeRange(start: Temporal.Instant, end: Temporal.Instant): [Temporal.Instant, Temporal.Instant];
|
|
11
|
+
/** Units that require timezone-aware context for calendar-accurate operations. */
|
|
12
|
+
export declare const CALENDAR_UNITS: Set<CalendarUnit>;
|
|
13
|
+
/** Approximate millisecond constants for threshold arithmetic. */
|
|
14
|
+
export declare const MS_PER_MONTH: number;
|
|
15
|
+
//# sourceMappingURL=_tz.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_tz.d.ts","sourceRoot":"","sources":["../src/_tz.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAMvD,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAW7C;AAID,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAOhF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAwBzF;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE,QAAQ,CAAC,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAEnH;AAID,kFAAkF;AAClF,eAAO,MAAM,cAAc,mBAA0D,CAAC;AAEtF,kEAAkE;AAClE,eAAO,MAAM,YAAY,QAAuB,CAAC"}
|
package/dist/_tz.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TempoInvalidTzError as e, TempoMissingTzError as t, fail as n } from "./errors.js";
|
|
2
|
+
import { Temporal as r } from "@js-temporal/polyfill";
|
|
3
|
+
//#region src/_tz.ts
|
|
4
|
+
function i(t) {
|
|
5
|
+
try {
|
|
6
|
+
r.Instant.fromEpochMilliseconds(0).toZonedDateTimeISO(t);
|
|
7
|
+
} catch {
|
|
8
|
+
n(`Unknown or invalid timezone: "${t}". Expected an IANA timezone name (e.g. "America/New_York") or UTC offset (e.g. "+05:30").`, e);
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
}
|
|
12
|
+
function a(e, a) {
|
|
13
|
+
let o = a.tz ?? (e instanceof r.ZonedDateTime ? e.timeZoneId : void 0);
|
|
14
|
+
return o || n("This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.", t), i(o);
|
|
15
|
+
}
|
|
16
|
+
function o(e, a) {
|
|
17
|
+
if (a.tz) return i(a.tz);
|
|
18
|
+
let o;
|
|
19
|
+
for (let t of e) {
|
|
20
|
+
if (!(t instanceof r.ZonedDateTime)) continue;
|
|
21
|
+
let e = t.timeZoneId;
|
|
22
|
+
if (!o) {
|
|
23
|
+
o = e;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
o !== e && n("Comparison received ZonedDateTime inputs with different time zones. Pass options.tz explicitly.");
|
|
27
|
+
}
|
|
28
|
+
return o || n("This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.", t), o;
|
|
29
|
+
}
|
|
30
|
+
function s(e, t) {
|
|
31
|
+
return r.Instant.compare(e, t) <= 0 ? [e, t] : [t, e];
|
|
32
|
+
}
|
|
33
|
+
var c = /* @__PURE__ */ new Set([
|
|
34
|
+
"day",
|
|
35
|
+
"month",
|
|
36
|
+
"week",
|
|
37
|
+
"year"
|
|
38
|
+
]), l = 30.4375 * 864e5;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { c as CALENDAR_UNITS, l as MS_PER_MONTH, o as inferSharedTimeZone, a as inferTimeZone, s as normalizeRange, i as validateTz };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=_tz.js.map
|
package/dist/_tz.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_tz.js","names":[],"sources":["../src/_tz.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\n\nimport type { CalendarUnit, TimeInput } from './types';\n\nimport { TempoInvalidTzError, TempoMissingTzError, fail } from './errors';\n\n// ─── Timezone validation ──────────────────────────────────────────────────────\n\nexport function validateTz(tz: string): string {\n try {\n Temporal.Instant.fromEpochMilliseconds(0).toZonedDateTimeISO(tz);\n } catch {\n fail(\n `Unknown or invalid timezone: \"${tz}\". Expected an IANA timezone name (e.g. \"America/New_York\") or UTC offset (e.g. \"+05:30\").`,\n TempoInvalidTzError,\n );\n }\n\n return tz;\n}\n\n// ─── Timezone inference ───────────────────────────────────────────────────────\n\nexport function inferTimeZone(input: TimeInput, options: { tz?: string }): string {\n const tz = options.tz ?? (input instanceof Temporal.ZonedDateTime ? input.timeZoneId : undefined);\n\n if (!tz)\n fail('This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.', TempoMissingTzError);\n\n return validateTz(tz);\n}\n\nexport function inferSharedTimeZone(inputs: TimeInput[], options: { tz?: string }): string {\n if (options.tz) return validateTz(options.tz);\n\n let inferred: string | undefined;\n\n for (const input of inputs) {\n if (!(input instanceof Temporal.ZonedDateTime)) continue;\n\n const tz = input.timeZoneId;\n\n if (!inferred) {\n inferred = tz;\n continue;\n }\n\n if (inferred !== tz) {\n fail('Comparison received ZonedDateTime inputs with different time zones. Pass options.tz explicitly.');\n }\n }\n\n if (!inferred)\n fail('This operation requires a timezone. Pass options.tz or use a ZonedDateTime input.', TempoMissingTzError);\n\n return inferred;\n}\n\n// ─── Range normalization ──────────────────────────────────────────────────────\n\nexport function normalizeRange(start: Temporal.Instant, end: Temporal.Instant): [Temporal.Instant, Temporal.Instant] {\n return Temporal.Instant.compare(start, end) <= 0 ? [start, end] : [end, start];\n}\n\n// ─── Shared constants ─────────────────────────────────────────────────────────\n\n/** Units that require timezone-aware context for calendar-accurate operations. */\nexport const CALENDAR_UNITS = new Set<CalendarUnit>(['day', 'month', 'week', 'year']);\n\n/** Approximate millisecond constants for threshold arithmetic. */\nexport const MS_PER_MONTH = 30.4375 * 86_400_000; // 365.25 / 12 days × 86400 s × 1000\n"],"mappings":";;;AAQA,SAAgB,EAAW,GAAoB;CAC7C,IAAI;EACF,EAAS,QAAQ,sBAAsB,CAAC,CAAC,CAAC,mBAAmB,CAAE;CACjE,QAAQ;EACN,EACE,iCAAiC,EAAG,6FACpC,CACF;CACF;CAEA,OAAO;AACT;AAIA,SAAgB,EAAc,GAAkB,GAAkC;CAChF,IAAM,IAAK,EAAQ,OAAO,aAAiB,EAAS,gBAAgB,EAAM,aAAa,KAAA;CAKvF,OAHK,KACH,EAAK,qFAAqF,CAAmB,GAExG,EAAW,CAAE;AACtB;AAEA,SAAgB,EAAoB,GAAqB,GAAkC;CACzF,IAAI,EAAQ,IAAI,OAAO,EAAW,EAAQ,EAAE;CAE5C,IAAI;CAEJ,KAAK,IAAM,KAAS,GAAQ;EAC1B,IAAI,EAAE,aAAiB,EAAS,gBAAgB;EAEhD,IAAM,IAAK,EAAM;EAEjB,IAAI,CAAC,GAAU;GACb,IAAW;GACX;EACF;EAEA,AAAI,MAAa,KACf,EAAK,iGAAiG;CAE1G;CAKA,OAHK,KACH,EAAK,qFAAqF,CAAmB,GAExG;AACT;AAIA,SAAgB,EAAe,GAAyB,GAA6D;CACnH,OAAO,EAAS,QAAQ,QAAQ,GAAO,CAAG,KAAK,IAAI,CAAC,GAAO,CAAG,IAAI,CAAC,GAAK,CAAK;AAC/E;AAKA,IAAa,oBAAiB,IAAI,IAAkB;CAAC;CAAO;CAAS;CAAQ;AAAM,CAAC,GAGvE,IAAe,UAAU"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./_tz.cjs"),t=require("./_floor.cjs");require("@js-temporal/polyfill");var n={day:{days:1},hour:{hours:1},minute:{minutes:1},month:{months:1},week:{weeks:1},year:{years:1}};function r(n,r,i={}){let a=e.inferTimeZone(n,i);return t.floorToUnit(n,r,{tz:a,weekStartsOn:i.weekStartsOn}).toZonedDateTimeISO(a)}function i(r,i,a={}){let o=e.inferTimeZone(r,a);return t.floorToUnit(r,i,{tz:o,weekStartsOn:a.weekStartsOn}).toZonedDateTimeISO(o).add(n[i]).subtract({nanoseconds:1})}exports.endOf=i,exports.startOf=r;
|
|
2
|
+
//# sourceMappingURL=boundary.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary.cjs","names":[],"sources":["../src/boundary.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\n\nimport type { BoundaryOptions, BoundaryUnit, TimeInput } from './types';\n\nimport { floorToUnit } from './_floor';\nimport { inferTimeZone } from './_tz';\n\n// ─── Boundary step durations ──────────────────────────────────────────────────\n\nconst BOUNDARY_STEP: Record<BoundaryUnit, Temporal.DurationLike> = {\n day: { days: 1 },\n hour: { hours: 1 },\n minute: { minutes: 1 },\n month: { months: 1 },\n week: { weeks: 1 },\n year: { years: 1 },\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Returns the start of the given `unit` in the inferred or explicit timezone.\n *\n * @example\n * ```ts\n * startOf(parseInstant('2026-03-21T10:15:30Z'), 'day', { tz: 'UTC' })\n * // 2026-03-21T00:00:00+00:00[UTC]\n *\n * startOf(instant, 'week', { tz: 'UTC', weekStartsOn: 1 })\n * // Monday of the current week\n * ```\n */\nexport function startOf(input: TimeInput, unit: BoundaryUnit, options: BoundaryOptions = {}): Temporal.ZonedDateTime {\n const tz = inferTimeZone(input, options);\n\n return floorToUnit(input, unit, { tz, weekStartsOn: options.weekStartsOn }).toZonedDateTimeISO(tz);\n}\n\n/**\n * Returns the last nanosecond of the given `unit` (exactly 1 ns before the next unit starts).\n *\n * @example\n * ```ts\n * endOf(parseInstant('2026-03-21T10:15:30Z'), 'day', { tz: 'UTC' })\n * // 2026-03-21T23:59:59.999999999+00:00[UTC]\n * ```\n */\nexport function endOf(input: TimeInput, unit: BoundaryUnit, options: BoundaryOptions = {}): Temporal.ZonedDateTime {\n const tz = inferTimeZone(input, options);\n const startInstant = floorToUnit(input, unit, { tz, weekStartsOn: options.weekStartsOn });\n\n return startInstant.toZonedDateTimeISO(tz).add(BOUNDARY_STEP[unit]).subtract({ nanoseconds: 1 });\n}\n"],"mappings":"wFASA,IAAM,EAA6D,CACjE,IAAK,CAAE,KAAM,CAAE,EACf,KAAM,CAAE,MAAO,CAAE,EACjB,OAAQ,CAAE,QAAS,CAAE,EACrB,MAAO,CAAE,OAAQ,CAAE,EACnB,KAAM,CAAE,MAAO,CAAE,EACjB,KAAM,CAAE,MAAO,CAAE,CACnB,EAgBA,SAAgB,EAAQ,EAAkB,EAAoB,EAA2B,CAAC,EAA2B,CACnH,IAAM,EAAK,EAAA,cAAc,EAAO,CAAO,EAEvC,OAAO,EAAA,YAAY,EAAO,EAAM,CAAE,KAAI,aAAc,EAAQ,YAAa,CAAC,CAAC,CAAC,mBAAmB,CAAE,CACnG,CAWA,SAAgB,EAAM,EAAkB,EAAoB,EAA2B,CAAC,EAA2B,CACjH,IAAM,EAAK,EAAA,cAAc,EAAO,CAAO,EAGvC,OAFqB,EAAA,YAAY,EAAO,EAAM,CAAE,KAAI,aAAc,EAAQ,YAAa,CAEhF,CAAA,CAAa,mBAAmB,CAAE,CAAC,CAAC,IAAI,EAAc,EAAK,CAAC,CAAC,SAAS,CAAE,YAAa,CAAE,CAAC,CACjG"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
+
import type { BoundaryOptions, BoundaryUnit, TimeInput } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Returns the start of the given `unit` in the inferred or explicit timezone.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* startOf(parseInstant('2026-03-21T10:15:30Z'), 'day', { tz: 'UTC' })
|
|
9
|
+
* // 2026-03-21T00:00:00+00:00[UTC]
|
|
10
|
+
*
|
|
11
|
+
* startOf(instant, 'week', { tz: 'UTC', weekStartsOn: 1 })
|
|
12
|
+
* // Monday of the current week
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function startOf(input: TimeInput, unit: BoundaryUnit, options?: BoundaryOptions): Temporal.ZonedDateTime;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the last nanosecond of the given `unit` (exactly 1 ns before the next unit starts).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* endOf(parseInstant('2026-03-21T10:15:30Z'), 'day', { tz: 'UTC' })
|
|
22
|
+
* // 2026-03-21T23:59:59.999999999+00:00[UTC]
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function endOf(input: TimeInput, unit: BoundaryUnit, options?: BoundaryOptions): Temporal.ZonedDateTime;
|
|
26
|
+
//# sourceMappingURL=boundary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary.d.ts","sourceRoot":"","sources":["../src/boundary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAkBxE;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAAC,aAAa,CAInH;AAED;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAAC,aAAa,CAKjH"}
|
package/dist/boundary.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { inferTimeZone as e } from "./_tz.js";
|
|
2
|
+
import { floorToUnit as t } from "./_floor.js";
|
|
3
|
+
import "@js-temporal/polyfill";
|
|
4
|
+
//#region src/boundary.ts
|
|
5
|
+
var n = {
|
|
6
|
+
day: { days: 1 },
|
|
7
|
+
hour: { hours: 1 },
|
|
8
|
+
minute: { minutes: 1 },
|
|
9
|
+
month: { months: 1 },
|
|
10
|
+
week: { weeks: 1 },
|
|
11
|
+
year: { years: 1 }
|
|
12
|
+
};
|
|
13
|
+
function r(n, r, i = {}) {
|
|
14
|
+
let a = e(n, i);
|
|
15
|
+
return t(n, r, {
|
|
16
|
+
tz: a,
|
|
17
|
+
weekStartsOn: i.weekStartsOn
|
|
18
|
+
}).toZonedDateTimeISO(a);
|
|
19
|
+
}
|
|
20
|
+
function i(r, i, a = {}) {
|
|
21
|
+
let o = e(r, a);
|
|
22
|
+
return t(r, i, {
|
|
23
|
+
tz: o,
|
|
24
|
+
weekStartsOn: a.weekStartsOn
|
|
25
|
+
}).toZonedDateTimeISO(o).add(n[i]).subtract({ nanoseconds: 1 });
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { i as endOf, r as startOf };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=boundary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary.js","names":[],"sources":["../src/boundary.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\n\nimport type { BoundaryOptions, BoundaryUnit, TimeInput } from './types';\n\nimport { floorToUnit } from './_floor';\nimport { inferTimeZone } from './_tz';\n\n// ─── Boundary step durations ──────────────────────────────────────────────────\n\nconst BOUNDARY_STEP: Record<BoundaryUnit, Temporal.DurationLike> = {\n day: { days: 1 },\n hour: { hours: 1 },\n minute: { minutes: 1 },\n month: { months: 1 },\n week: { weeks: 1 },\n year: { years: 1 },\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Returns the start of the given `unit` in the inferred or explicit timezone.\n *\n * @example\n * ```ts\n * startOf(parseInstant('2026-03-21T10:15:30Z'), 'day', { tz: 'UTC' })\n * // 2026-03-21T00:00:00+00:00[UTC]\n *\n * startOf(instant, 'week', { tz: 'UTC', weekStartsOn: 1 })\n * // Monday of the current week\n * ```\n */\nexport function startOf(input: TimeInput, unit: BoundaryUnit, options: BoundaryOptions = {}): Temporal.ZonedDateTime {\n const tz = inferTimeZone(input, options);\n\n return floorToUnit(input, unit, { tz, weekStartsOn: options.weekStartsOn }).toZonedDateTimeISO(tz);\n}\n\n/**\n * Returns the last nanosecond of the given `unit` (exactly 1 ns before the next unit starts).\n *\n * @example\n * ```ts\n * endOf(parseInstant('2026-03-21T10:15:30Z'), 'day', { tz: 'UTC' })\n * // 2026-03-21T23:59:59.999999999+00:00[UTC]\n * ```\n */\nexport function endOf(input: TimeInput, unit: BoundaryUnit, options: BoundaryOptions = {}): Temporal.ZonedDateTime {\n const tz = inferTimeZone(input, options);\n const startInstant = floorToUnit(input, unit, { tz, weekStartsOn: options.weekStartsOn });\n\n return startInstant.toZonedDateTimeISO(tz).add(BOUNDARY_STEP[unit]).subtract({ nanoseconds: 1 });\n}\n"],"mappings":";;;;AASA,IAAM,IAA6D;CACjE,KAAK,EAAE,MAAM,EAAE;CACf,MAAM,EAAE,OAAO,EAAE;CACjB,QAAQ,EAAE,SAAS,EAAE;CACrB,OAAO,EAAE,QAAQ,EAAE;CACnB,MAAM,EAAE,OAAO,EAAE;CACjB,MAAM,EAAE,OAAO,EAAE;AACnB;AAgBA,SAAgB,EAAQ,GAAkB,GAAoB,IAA2B,CAAC,GAA2B;CACnH,IAAM,IAAK,EAAc,GAAO,CAAO;CAEvC,OAAO,EAAY,GAAO,GAAM;EAAE;EAAI,cAAc,EAAQ;CAAa,CAAC,CAAC,CAAC,mBAAmB,CAAE;AACnG;AAWA,SAAgB,EAAM,GAAkB,GAAoB,IAA2B,CAAC,GAA2B;CACjH,IAAM,IAAK,EAAc,GAAO,CAAO;CAGvC,OAFqB,EAAY,GAAO,GAAM;EAAE;EAAI,cAAc,EAAQ;CAAa,CAEhF,CAAA,CAAa,mBAAmB,CAAE,CAAC,CAAC,IAAI,EAAc,EAAK,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,CAAC;AACjG"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./_tz.cjs"),t=require("./_convert.cjs");let n=require("@js-temporal/polyfill");var r=new WeakMap;function i(e){let t=r.get(e);if(t)return t;let n=Object.keys(e).map(t=>({key:t,ms:o(e[t])})).sort((e,t)=>e.ms-t.ms);return r.set(e,n),n}function a(e,r,a={},o=n.Temporal.Now.instant()){let s=t.toInstant(e,a).epochMilliseconds-o.epochMilliseconds;for(let{key:e,ms:t}of i(r))if(s<=t)return e;return null}function o(t){let r=n.Temporal.Duration.from(t);return(r.years??0)*12*e.MS_PER_MONTH+(r.months??0)*e.MS_PER_MONTH+(r.weeks??0)*7*864e5+(r.days??0)*864e5+(r.hours??0)*36e5+(r.minutes??0)*6e4+(r.seconds??0)*1e3+(r.milliseconds??0)+(r.microseconds??0)/1e3+(r.nanoseconds??0)/1e6}var s=[{field:`years`,unit:`year`},{field:`months`,unit:`month`},{field:`weeks`,unit:`week`},{field:`days`,unit:`day`},{field:`hours`,unit:`hour`},{field:`minutes`,unit:`minute`},{field:`seconds`,unit:`second`},{field:`milliseconds`,unit:`millisecond`}];function c(e,t){return n.Temporal.ZonedDateTime.compare(e,t)<=0?t.since(e,{largestUnit:`year`}):e.since(t,{largestUnit:`year`})}function l(e){for(let{field:t,unit:n}of s){let r=Math.abs(e[t]);if(r>0)return{unit:n,value:r}}return{unit:`millisecond`,value:0}}function u(r,i,a={}){let o=i??n.Temporal.Now.instant();if(!a.tz&&r instanceof n.Temporal.Instant&&o instanceof n.Temporal.Instant)return l(c(r.toZonedDateTimeISO(`UTC`),o.toZonedDateTimeISO(`UTC`)));let s=e.inferSharedTimeZone([r,o],a);return l(c(t.toZoned(r,{tz:s}),t.toZoned(o,{tz:s})))}exports.expires=a,exports.timeDiff=u;
|
|
2
|
+
//# sourceMappingURL=classify.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.cjs","names":[],"sources":["../src/classify.ts"],"sourcesContent":["import { Temporal } from '@js-temporal/polyfill';\n\nimport type { TimeDiffResult, TimeDiffUnit, TimeInput, TimeOptions } from './types';\n\nimport { toInstant, toZoned } from './_convert';\nimport { inferSharedTimeZone, MS_PER_MONTH } from './_tz';\n\n// ─── Threshold sort cache ─────────────────────────────────────────────────────\n\ntype SortedThreshold<K extends string> = { key: K; ms: number }[];\n\nconst THRESHOLD_SORT_CACHE = new WeakMap<object, SortedThreshold<string>>();\n\nfunction getSortedThresholds<K extends string>(thresholds: Record<K, Temporal.DurationLike>): SortedThreshold<K> {\n const cached = THRESHOLD_SORT_CACHE.get(thresholds);\n\n if (cached) return cached as SortedThreshold<K>;\n\n const sorted = (Object.keys(thresholds) as K[])\n .map((key) => ({ key, ms: durationToMs(thresholds[key]) }))\n .sort((a, b) => a.ms - b.ms);\n\n THRESHOLD_SORT_CACHE.set(thresholds, sorted);\n\n return sorted;\n}\n\n// ─── expires ─────────────────────────────────────────────────────────────────\n\n/**\n * Classifies a date into a user-defined bucket by comparing diff = date − now\n * against the provided thresholds (sorted ascending). Returns the key of the\n * first threshold the diff falls within, or `null` if no threshold matches.\n *\n * Thresholds accept negative durations to classify past dates. The function\n * requires `options.tz` when input is a `PlainDate` or `PlainDateTime`.\n *\n * **Performance:** threshold objects are cached by reference in a `WeakMap`. Define\n * the threshold record at module scope (not inline) so sorting is performed only once\n * per unique object.\n *\n * @example\n * ```ts\n * expires(expiresAt, {\n * longExpired: { days: -30 }, // more than 30 days in the past\n * expired: { days: 0 }, // any past date\n * critical: { days: 3 }, // within 3 days\n * warning: { days: 14 }, // within 14 days\n * safe: { years: 100 }, // catch-all for far future\n * })\n * // → 'longExpired' | 'expired' | 'critical' | 'warning' | 'safe' | null\n * ```\n */\nexport function expires<K extends string>(\n date: TimeInput,\n thresholds: Record<K, Temporal.DurationLike>,\n options: TimeOptions = {},\n now = Temporal.Now.instant(),\n): K | null {\n const dateMs = toInstant(date, options).epochMilliseconds;\n const nowMs = now.epochMilliseconds;\n\n // diff is positive for future dates, negative for past dates (date − now)\n const diffMs = dateMs - nowMs;\n\n for (const { key, ms } of getSortedThresholds(thresholds)) {\n if (diffMs <= ms) return key;\n }\n\n return null;\n}\n\n/** Converts a `DurationLike` to approximate milliseconds for threshold comparison. */\nfunction durationToMs(duration: Temporal.DurationLike): number {\n const d = Temporal.Duration.from(duration);\n\n // Use approximate conversions — thresholds are human-defined boundaries, not calendar-precise.\n return (\n (d.years ?? 0) * 12 * MS_PER_MONTH +\n (d.months ?? 0) * MS_PER_MONTH +\n (d.weeks ?? 0) * 7 * 86_400_000 +\n (d.days ?? 0) * 86_400_000 +\n (d.hours ?? 0) * 3_600_000 +\n (d.minutes ?? 0) * 60_000 +\n (d.seconds ?? 0) * 1_000 +\n (d.milliseconds ?? 0) +\n (d.microseconds ?? 0) / 1_000 +\n (d.nanoseconds ?? 0) / 1_000_000\n );\n}\n\n// ─── timeDiff ─────────────────────────────────────────────────────────────────\n\nconst UNIT_ORDER: ReadonlyArray<{ field: keyof Temporal.Duration; unit: TimeDiffUnit }> = [\n { field: 'years', unit: 'year' },\n { field: 'months', unit: 'month' },\n { field: 'weeks', unit: 'week' },\n { field: 'days', unit: 'day' },\n { field: 'hours', unit: 'hour' },\n { field: 'minutes', unit: 'minute' },\n { field: 'seconds', unit: 'second' },\n { field: 'milliseconds', unit: 'millisecond' },\n];\n\nfunction sinceZoned(a: Temporal.ZonedDateTime, b: Temporal.ZonedDateTime): Temporal.Duration {\n return Temporal.ZonedDateTime.compare(a, b) <= 0\n ? b.since(a, { largestUnit: 'year' })\n : a.since(b, { largestUnit: 'year' });\n}\n\nfunction pickLargestUnit(duration: Temporal.Duration): TimeDiffResult {\n for (const { field, unit } of UNIT_ORDER) {\n const value = Math.abs(duration[field] as number);\n\n if (value > 0) return { unit, value };\n }\n\n return { unit: 'millisecond', value: 0 };\n}\n\n/**\n * Returns the absolute calendar-accurate difference between two dates as a\n * structured `{ unit, value }` in the largest meaningful unit.\n *\n * When `b` is omitted, the current instant is used.\n * Requires `options.tz` when inputs are `PlainDate`, `PlainDateTime`, or plain `Instant` with\n * calendar-unit precision. Throws when timezone cannot be inferred from inputs.\n *\n * @example\n * ```ts\n * timeDiff(\n * parseInstant('2026-01-01T00:00:00Z'),\n * parseInstant('2027-03-15T00:00:00Z'),\n * )\n * // { unit: 'year', value: 1 }\n * ```\n */\nexport function timeDiff(a: TimeInput, b?: TimeInput, options: TimeOptions = {}): TimeDiffResult {\n const end: TimeInput = b ?? Temporal.Now.instant();\n\n // Fast path: two Instants with no explicit tz — project to UTC for calendar-accurate units.\n // Instants are absolute and timezone-independent; UTC is the canonical calendar context.\n if (!options.tz && a instanceof Temporal.Instant && end instanceof Temporal.Instant) {\n return pickLargestUnit(sinceZoned(a.toZonedDateTimeISO('UTC'), end.toZonedDateTimeISO('UTC')));\n }\n\n // Plain inputs or calendar-accurate comparison require a timezone.\n const tz = inferSharedTimeZone([a, end], options);\n\n return pickLargestUnit(sinceZoned(toZoned(a, { tz }), toZoned(end, { tz })));\n}\n"],"mappings":"gGAWA,IAAM,EAAuB,IAAI,QAEjC,SAAS,EAAsC,EAAkE,CAC/G,IAAM,EAAS,EAAqB,IAAI,CAAU,EAElD,GAAI,EAAQ,OAAO,EAEnB,IAAM,EAAU,OAAO,KAAK,CAAU,CAAC,CACpC,IAAK,IAAS,CAAE,MAAK,GAAI,EAAa,EAAW,EAAI,CAAE,EAAE,CAAC,CAC1D,MAAM,EAAG,IAAM,EAAE,GAAK,EAAE,EAAE,EAI7B,OAFA,EAAqB,IAAI,EAAY,CAAM,EAEpC,CACT,CA4BA,SAAgB,EACd,EACA,EACA,EAAuB,CAAC,EACxB,EAAM,EAAA,SAAS,IAAI,QAAQ,EACjB,CAKV,IAAM,EAJS,EAAA,UAAU,EAAM,CAAO,CAAC,CAAC,kBAC1B,EAAI,kBAKlB,IAAK,GAAM,CAAE,MAAK,QAAQ,EAAoB,CAAU,EACtD,GAAI,GAAU,EAAI,OAAO,EAG3B,OAAO,IACT,CAGA,SAAS,EAAa,EAAyC,CAC7D,IAAM,EAAI,EAAA,SAAS,SAAS,KAAK,CAAQ,EAGzC,OACG,EAAE,OAAS,GAAK,GAAK,EAAA,cACrB,EAAE,QAAU,GAAK,EAAA,cACjB,EAAE,OAAS,GAAK,EAAI,OACpB,EAAE,MAAQ,GAAK,OACf,EAAE,OAAS,GAAK,MAChB,EAAE,SAAW,GAAK,KAClB,EAAE,SAAW,GAAK,KAClB,EAAE,cAAgB,IAClB,EAAE,cAAgB,GAAK,KACvB,EAAE,aAAe,GAAK,GAE3B,CAIA,IAAM,EAAoF,CACxF,CAAE,MAAO,QAAS,KAAM,MAAO,EAC/B,CAAE,MAAO,SAAU,KAAM,OAAQ,EACjC,CAAE,MAAO,QAAS,KAAM,MAAO,EAC/B,CAAE,MAAO,OAAQ,KAAM,KAAM,EAC7B,CAAE,MAAO,QAAS,KAAM,MAAO,EAC/B,CAAE,MAAO,UAAW,KAAM,QAAS,EACnC,CAAE,MAAO,UAAW,KAAM,QAAS,EACnC,CAAE,MAAO,eAAgB,KAAM,aAAc,CAC/C,EAEA,SAAS,EAAW,EAA2B,EAA8C,CAC3F,OAAO,EAAA,SAAS,cAAc,QAAQ,EAAG,CAAC,GAAK,EAC3C,EAAE,MAAM,EAAG,CAAE,YAAa,MAAO,CAAC,EAClC,EAAE,MAAM,EAAG,CAAE,YAAa,MAAO,CAAC,CACxC,CAEA,SAAS,EAAgB,EAA6C,CACpE,IAAK,GAAM,CAAE,QAAO,UAAU,EAAY,CACxC,IAAM,EAAQ,KAAK,IAAI,EAAS,EAAgB,EAEhD,GAAI,EAAQ,EAAG,MAAO,CAAE,OAAM,OAAM,CACtC,CAEA,MAAO,CAAE,KAAM,cAAe,MAAO,CAAE,CACzC,CAmBA,SAAgB,EAAS,EAAc,EAAe,EAAuB,CAAC,EAAmB,CAC/F,IAAM,EAAiB,GAAK,EAAA,SAAS,IAAI,QAAQ,EAIjD,GAAI,CAAC,EAAQ,IAAM,aAAa,EAAA,SAAS,SAAW,aAAe,EAAA,SAAS,QAC1E,OAAO,EAAgB,EAAW,EAAE,mBAAmB,KAAK,EAAG,EAAI,mBAAmB,KAAK,CAAC,CAAC,EAI/F,IAAM,EAAK,EAAA,oBAAoB,CAAC,EAAG,CAAG,EAAG,CAAO,EAEhD,OAAO,EAAgB,EAAW,EAAA,QAAQ,EAAG,CAAE,IAAG,CAAC,EAAG,EAAA,QAAQ,EAAK,CAAE,IAAG,CAAC,CAAC,CAAC,CAC7E"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
2
|
+
import type { TimeDiffResult, TimeInput, TimeOptions } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Classifies a date into a user-defined bucket by comparing diff = date − now
|
|
5
|
+
* against the provided thresholds (sorted ascending). Returns the key of the
|
|
6
|
+
* first threshold the diff falls within, or `null` if no threshold matches.
|
|
7
|
+
*
|
|
8
|
+
* Thresholds accept negative durations to classify past dates. The function
|
|
9
|
+
* requires `options.tz` when input is a `PlainDate` or `PlainDateTime`.
|
|
10
|
+
*
|
|
11
|
+
* **Performance:** threshold objects are cached by reference in a `WeakMap`. Define
|
|
12
|
+
* the threshold record at module scope (not inline) so sorting is performed only once
|
|
13
|
+
* per unique object.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* expires(expiresAt, {
|
|
18
|
+
* longExpired: { days: -30 }, // more than 30 days in the past
|
|
19
|
+
* expired: { days: 0 }, // any past date
|
|
20
|
+
* critical: { days: 3 }, // within 3 days
|
|
21
|
+
* warning: { days: 14 }, // within 14 days
|
|
22
|
+
* safe: { years: 100 }, // catch-all for far future
|
|
23
|
+
* })
|
|
24
|
+
* // → 'longExpired' | 'expired' | 'critical' | 'warning' | 'safe' | null
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function expires<K extends string>(date: TimeInput, thresholds: Record<K, Temporal.DurationLike>, options?: TimeOptions, now?: Temporal.Instant): K | null;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the absolute calendar-accurate difference between two dates as a
|
|
30
|
+
* structured `{ unit, value }` in the largest meaningful unit.
|
|
31
|
+
*
|
|
32
|
+
* When `b` is omitted, the current instant is used.
|
|
33
|
+
* Requires `options.tz` when inputs are `PlainDate`, `PlainDateTime`, or plain `Instant` with
|
|
34
|
+
* calendar-unit precision. Throws when timezone cannot be inferred from inputs.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* timeDiff(
|
|
39
|
+
* parseInstant('2026-01-01T00:00:00Z'),
|
|
40
|
+
* parseInstant('2027-03-15T00:00:00Z'),
|
|
41
|
+
* )
|
|
42
|
+
* // { unit: 'year', value: 1 }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function timeDiff(a: TimeInput, b?: TimeInput, options?: TimeOptions): TimeDiffResult;
|
|
46
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../src/classify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,KAAK,EAAE,cAAc,EAAgB,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AA2BpF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EACtC,IAAI,EAAE,SAAS,EACf,UAAU,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC5C,OAAO,GAAE,WAAgB,EACzB,GAAG,mBAAyB,GAC3B,CAAC,GAAG,IAAI,CAYV;AAkDD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,GAAE,WAAgB,GAAG,cAAc,CAa/F"}
|
package/dist/classify.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { MS_PER_MONTH as e, inferSharedTimeZone as t } from "./_tz.js";
|
|
2
|
+
import { toInstant as n, toZoned as r } from "./_convert.js";
|
|
3
|
+
import { Temporal as i } from "@js-temporal/polyfill";
|
|
4
|
+
//#region src/classify.ts
|
|
5
|
+
var a = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
function o(e) {
|
|
7
|
+
let t = a.get(e);
|
|
8
|
+
if (t) return t;
|
|
9
|
+
let n = Object.keys(e).map((t) => ({
|
|
10
|
+
key: t,
|
|
11
|
+
ms: c(e[t])
|
|
12
|
+
})).sort((e, t) => e.ms - t.ms);
|
|
13
|
+
return a.set(e, n), n;
|
|
14
|
+
}
|
|
15
|
+
function s(e, t, r = {}, a = i.Now.instant()) {
|
|
16
|
+
let s = n(e, r).epochMilliseconds - a.epochMilliseconds;
|
|
17
|
+
for (let { key: e, ms: n } of o(t)) if (s <= n) return e;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function c(t) {
|
|
21
|
+
let n = i.Duration.from(t);
|
|
22
|
+
return (n.years ?? 0) * 12 * e + (n.months ?? 0) * e + (n.weeks ?? 0) * 7 * 864e5 + (n.days ?? 0) * 864e5 + (n.hours ?? 0) * 36e5 + (n.minutes ?? 0) * 6e4 + (n.seconds ?? 0) * 1e3 + (n.milliseconds ?? 0) + (n.microseconds ?? 0) / 1e3 + (n.nanoseconds ?? 0) / 1e6;
|
|
23
|
+
}
|
|
24
|
+
var l = [
|
|
25
|
+
{
|
|
26
|
+
field: "years",
|
|
27
|
+
unit: "year"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
field: "months",
|
|
31
|
+
unit: "month"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
field: "weeks",
|
|
35
|
+
unit: "week"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
field: "days",
|
|
39
|
+
unit: "day"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
field: "hours",
|
|
43
|
+
unit: "hour"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
field: "minutes",
|
|
47
|
+
unit: "minute"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
field: "seconds",
|
|
51
|
+
unit: "second"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
field: "milliseconds",
|
|
55
|
+
unit: "millisecond"
|
|
56
|
+
}
|
|
57
|
+
];
|
|
58
|
+
function u(e, t) {
|
|
59
|
+
return i.ZonedDateTime.compare(e, t) <= 0 ? t.since(e, { largestUnit: "year" }) : e.since(t, { largestUnit: "year" });
|
|
60
|
+
}
|
|
61
|
+
function d(e) {
|
|
62
|
+
for (let { field: t, unit: n } of l) {
|
|
63
|
+
let r = Math.abs(e[t]);
|
|
64
|
+
if (r > 0) return {
|
|
65
|
+
unit: n,
|
|
66
|
+
value: r
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
unit: "millisecond",
|
|
71
|
+
value: 0
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function f(e, n, a = {}) {
|
|
75
|
+
let o = n ?? i.Now.instant();
|
|
76
|
+
if (!a.tz && e instanceof i.Instant && o instanceof i.Instant) return d(u(e.toZonedDateTimeISO("UTC"), o.toZonedDateTimeISO("UTC")));
|
|
77
|
+
let s = t([e, o], a);
|
|
78
|
+
return d(u(r(e, { tz: s }), r(o, { tz: s })));
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { s as expires, f as timeDiff };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=classify.js.map
|