@ultimat3/time 1.0.0 → 1.2.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/src/errors.js DELETED
@@ -1,78 +0,0 @@
1
- /**
2
- * The X_* error codes owned by @ultimat3/time.
3
- * DST ambiguity is a real state of the world, so it gets a code instead of a guess.
4
- */
5
- import { UltimateError } from '@ultimat3/core';
6
- export const TIME_ERROR_CODES = [
7
- 'X_TIMEZONE_INVALID',
8
- 'X_CRON_INVALID',
9
- 'X_DURATION_INVALID',
10
- 'X_DST_AMBIGUOUS',
11
- 'X_DST_NONEXISTENT',
12
- 'X_INSTANT_INVALID',
13
- 'X_SCHEDULE_INVALID',
14
- ];
15
- export class TimeError extends UltimateError {
16
- constructor(init) {
17
- super({
18
- code: init.code,
19
- cause: init.cause,
20
- fix: init.fix,
21
- docs: `https://ultimate.dev/errors/${init.code}`,
22
- });
23
- }
24
- }
25
- /**
26
- * A wall-clock field outside its range. Separate from `X_DST_*`, which are about times that
27
- * are legitimately absent or doubled — this one is a spec the caller got wrong.
28
- */
29
- export function scheduleInvalid(field, value, range) {
30
- return new TimeError({
31
- code: 'X_SCHEDULE_INVALID',
32
- cause: `${field} must be ${range}, got ${String(value)}`,
33
- fix: `pass an integer in ${range} for ${field} — wall-clock fields are not wrapped or clamped, because a silently shifted schedule is worse than a failed one`,
34
- });
35
- }
36
- export function timezoneInvalid(zone) {
37
- return new TimeError({
38
- code: 'X_TIMEZONE_INVALID',
39
- cause: `"${zone}" is not an IANA timezone name`,
40
- fix: 'use an IANA identifier such as Europe/Berlin, America/New_York or UTC — never an abbreviation like CET or a numeric offset',
41
- });
42
- }
43
- export function cronInvalid(expression, reason) {
44
- return new TimeError({
45
- code: 'X_CRON_INVALID',
46
- cause: `cron "${expression}": ${reason}`,
47
- fix: "use 5 fields (m h dom mon dow) or 6 with seconds, e.g. '0 3 * * *' for 03:00 daily, '*/15 * * * *' every 15 minutes, '0 9 * * MON-FRI' weekday mornings",
48
- });
49
- }
50
- export function durationInvalid(input) {
51
- return new TimeError({
52
- code: 'X_DURATION_INVALID',
53
- cause: `"${input}" is not a duration`,
54
- fix: "use a unit-suffixed duration: '90s', '2h30m', '3d', '1w', '250ms' — or an ISO-8601 form like 'PT2H30M'",
55
- });
56
- }
57
- export function dstAmbiguous(wall, zone) {
58
- return new TimeError({
59
- code: 'X_DST_AMBIGUOUS',
60
- cause: `${wall} happens twice in ${zone} (the fall-back hour repeats)`,
61
- fix: "pass { overlap: 'first' } for the pre-transition instant or { overlap: 'second' } for the post-transition one",
62
- });
63
- }
64
- export function dstNonexistent(wall, zone) {
65
- return new TimeError({
66
- code: 'X_DST_NONEXISTENT',
67
- cause: `${wall} never happens in ${zone} (the spring-forward gap skips it)`,
68
- fix: "pass { gap: 'next' } to shift forward past the gap or { gap: 'previous' } to shift back before it",
69
- });
70
- }
71
- export function instantInvalid(input) {
72
- return new TimeError({
73
- code: 'X_INSTANT_INVALID',
74
- cause: `"${input}" is not a valid instant`,
75
- fix: 'pass an ISO-8601 timestamp with an offset or Z, e.g. 2026-03-14T09:00:00Z',
76
- });
77
- }
78
- //# sourceMappingURL=errors.js.map
package/src/errors.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,oBAAoB;IACpB,gBAAgB;IAChB,oBAAoB;IACpB,iBAAiB;IACjB,mBAAmB;IACnB,mBAAmB;IACnB,oBAAoB;CACZ,CAAC;AAIX,MAAM,OAAO,SAAU,SAAQ,aAAa;IAC1C,YAAY,IAAyD;QACnE,KAAK,CAAC;YACJ,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,IAAI,EAAE,+BAA+B,IAAI,CAAC,IAAI,EAAE;SACjD,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,KAAc,EAAE,KAAa;IAC1E,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,GAAG,KAAK,YAAY,KAAK,SAAS,MAAM,CAAC,KAAK,CAAC,EAAE;QACxD,GAAG,EAAE,sBAAsB,KAAK,QAAQ,KAAK,iHAAiH;KAC/J,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,IAAI,IAAI,gCAAgC;QAC/C,GAAG,EAAE,4HAA4H;KAClI,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,UAAkB,EAAE,MAAc;IAC5D,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,SAAS,UAAU,MAAM,MAAM,EAAE;QACxC,GAAG,EAAE,yJAAyJ;KAC/J,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,IAAI,KAAK,qBAAqB;QACrC,GAAG,EAAE,wGAAwG;KAC9G,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAY;IACrD,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,GAAG,IAAI,qBAAqB,IAAI,+BAA+B;QACtE,GAAG,EAAE,+GAA+G;KACrH,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY;IACvD,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,GAAG,IAAI,qBAAqB,IAAI,oCAAoC;QAC3E,GAAG,EAAE,mGAAmG;KACzG,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,IAAI,SAAS,CAAC;QACnB,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,IAAI,KAAK,0BAA0B;QAC1C,GAAG,EAAE,2EAA2E;KACjF,CAAC,CAAC;AACL,CAAC"}
package/src/format.d.ts DELETED
@@ -1,54 +0,0 @@
1
- /**
2
- * `Intl.DateTimeFormat` at the edge. Every function takes an explicit `zone` and
3
- * `locale` — there is no ambient default and no `toLocaleString()` without options,
4
- * because "the server's timezone" is never the answer to "what time is it for the user".
5
- */
6
- import { type Instant } from './instant';
7
- import { type TimeZone } from './zones';
8
- export type DateTimeStyle = 'short' | 'medium' | 'long' | 'full';
9
- export interface FormatContext {
10
- locale: string;
11
- /** IANA zone. Required, always. */
12
- zone: TimeZone;
13
- }
14
- export interface FormatDateTimeOptions extends FormatContext {
15
- /** Sets both date and time style; `dateStyle`/`timeStyle` override it. */
16
- style?: DateTimeStyle;
17
- dateStyle?: DateTimeStyle;
18
- timeStyle?: DateTimeStyle;
19
- hour12?: boolean;
20
- }
21
- /** `14 Mar 2026, 09:00` in `en-GB` / `Europe/Berlin`. */
22
- export declare function formatDateTime(at: Instant, options: FormatDateTimeOptions): string;
23
- export declare function formatDate(at: Instant, options: FormatContext & {
24
- style?: DateTimeStyle;
25
- }): string;
26
- export declare function formatTime(at: Instant, options: FormatContext & {
27
- style?: DateTimeStyle;
28
- hour12?: boolean;
29
- }): string;
30
- /**
31
- * `14 Mar 2026, 09:00 (GMT+1)` — the offset made visible.
32
- * Built with `timeZoneName: 'shortOffset'` + `formatToParts` so the offset is appended
33
- * in a fixed position instead of wherever the locale pattern happens to put it.
34
- */
35
- export declare function formatWithOffset(at: Instant, options: FormatDateTimeOptions): string;
36
- /** ISO-8601 date parts in a zone, for `<input type="date">` and CSV columns. */
37
- export declare function formatIsoDate(at: Instant, zone: TimeZone): string;
38
- export interface FormatRelativeOptions extends Omit<FormatContext, 'zone'> {
39
- /** The reference point. Pass `now(clock)` — never let this default to a live clock. */
40
- now: Instant;
41
- numeric?: 'always' | 'auto';
42
- style?: 'long' | 'short' | 'narrow';
43
- }
44
- /** `in 3 days` / `2 hours ago`, picking the largest unit that fits. */
45
- export declare function formatRelative(at: Instant, options: FormatRelativeOptions): string;
46
- /** `14–16 Mar 2026` — one call, so the locale decides how to collapse the range. */
47
- export declare function formatRange(from: Instant, to: Instant, options: FormatDateTimeOptions): string;
48
- /**
49
- * `Intl` renders `November 5, 2011`, never `5th of November`. When a design asks for the
50
- * ordinal, build it from `Intl.PluralRules` with `type: 'ordinal'` — English-only by
51
- * nature, which is why it is a helper and not the default date format.
52
- */
53
- export declare function ordinal(value: number, locale?: string): string;
54
- //# sourceMappingURL=format.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAgB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,0EAA0E;IAC1E,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,yDAAyD;AACzD,wBAAgB,cAAc,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAQlF;AAED,wBAAgB,UAAU,CACxB,EAAE,EAAE,OAAO,EACX,OAAO,EAAE,aAAa,GAAG;IAAE,KAAK,CAAC,EAAE,aAAa,CAAA;CAAE,GACjD,MAAM,CAKR;AAED,wBAAgB,UAAU,CACxB,EAAE,EAAE,OAAO,EACX,OAAO,EAAE,aAAa,GAAG;IAAE,KAAK,CAAC,EAAE,aAAa,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GACnE,MAAM,CAMR;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAuBpF;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAQjE;AAED,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC;IACxE,uFAAuF;IACvF,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;CACrC;AAYD,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAalF;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAY9F;AAWD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAO,GAAG,MAAM,CAG5D"}
package/src/format.js DELETED
@@ -1,133 +0,0 @@
1
- /**
2
- * `Intl.DateTimeFormat` at the edge. Every function takes an explicit `zone` and
3
- * `locale` — there is no ambient default and no `toLocaleString()` without options,
4
- * because "the server's timezone" is never the answer to "what time is it for the user".
5
- */
6
- import { differenceMs } from './instant';
7
- import { assertTimeZone } from './zones';
8
- /** `14 Mar 2026, 09:00` in `en-GB` / `Europe/Berlin`. */
9
- export function formatDateTime(at, options) {
10
- const style = options.style ?? 'medium';
11
- return formatterFor(options.locale, {
12
- timeZone: assertTimeZone(options.zone),
13
- dateStyle: options.dateStyle ?? style,
14
- timeStyle: options.timeStyle ?? (style === 'full' || style === 'long' ? 'medium' : style),
15
- ...(options.hour12 === undefined ? {} : { hour12: options.hour12 }),
16
- }).format(at);
17
- }
18
- export function formatDate(at, options) {
19
- return formatterFor(options.locale, {
20
- timeZone: assertTimeZone(options.zone),
21
- dateStyle: options.style ?? 'medium',
22
- }).format(at);
23
- }
24
- export function formatTime(at, options) {
25
- return formatterFor(options.locale, {
26
- timeZone: assertTimeZone(options.zone),
27
- timeStyle: options.style ?? 'short',
28
- ...(options.hour12 === undefined ? {} : { hour12: options.hour12 }),
29
- }).format(at);
30
- }
31
- /**
32
- * `14 Mar 2026, 09:00 (GMT+1)` — the offset made visible.
33
- * Built with `timeZoneName: 'shortOffset'` + `formatToParts` so the offset is appended
34
- * in a fixed position instead of wherever the locale pattern happens to put it.
35
- */
36
- export function formatWithOffset(at, options) {
37
- const style = options.style ?? 'medium';
38
- // `timeZoneName` is a component option, and Intl forbids mixing those with dateStyle /
39
- // timeStyle — so the components are spelled out here instead.
40
- const parts = formatterFor(options.locale, {
41
- timeZone: assertTimeZone(options.zone),
42
- year: 'numeric',
43
- month: style === 'short' ? 'numeric' : style === 'medium' ? 'short' : 'long',
44
- day: 'numeric',
45
- hour: '2-digit',
46
- minute: '2-digit',
47
- ...(options.hour12 === undefined ? { hourCycle: 'h23' } : { hour12: options.hour12 }),
48
- timeZoneName: 'shortOffset',
49
- }).formatToParts(at);
50
- const offset = parts.find((part) => part.type === 'timeZoneName')?.value ?? '';
51
- const text = parts
52
- .filter((part) => part.type !== 'timeZoneName')
53
- .map((part) => part.value)
54
- .join('')
55
- .replace(/[\s,]+$/u, '')
56
- .trim();
57
- return offset === '' ? text : `${text} (${offset})`;
58
- }
59
- /** ISO-8601 date parts in a zone, for `<input type="date">` and CSV columns. */
60
- export function formatIsoDate(at, zone) {
61
- const parts = formatterFor('en-CA', {
62
- timeZone: assertTimeZone(zone),
63
- year: 'numeric',
64
- month: '2-digit',
65
- day: '2-digit',
66
- }).format(at);
67
- return parts.replace(/\//g, '-');
68
- }
69
- const RELATIVE_UNITS = [
70
- ['year', 31_536_000_000],
71
- ['month', 2_592_000_000],
72
- ['week', 604_800_000],
73
- ['day', 86_400_000],
74
- ['hour', 3_600_000],
75
- ['minute', 60_000],
76
- ['second', 1000],
77
- ];
78
- /** `in 3 days` / `2 hours ago`, picking the largest unit that fits. */
79
- export function formatRelative(at, options) {
80
- const delta = differenceMs(options.now, at);
81
- const formatter = new Intl.RelativeTimeFormat(options.locale, {
82
- numeric: options.numeric ?? 'auto',
83
- style: options.style ?? 'long',
84
- });
85
- const magnitude = Math.abs(delta);
86
- for (const [unit, ms] of RELATIVE_UNITS) {
87
- if (magnitude >= ms) {
88
- return formatter.format(Math.trunc(delta / ms), unit);
89
- }
90
- }
91
- return formatter.format(0, 'second');
92
- }
93
- /** `14–16 Mar 2026` — one call, so the locale decides how to collapse the range. */
94
- export function formatRange(from, to, options) {
95
- const style = options.style ?? 'medium';
96
- const formatter = formatterFor(options.locale, {
97
- timeZone: assertTimeZone(options.zone),
98
- dateStyle: options.dateStyle ?? style,
99
- ...(options.timeStyle === undefined ? {} : { timeStyle: options.timeStyle }),
100
- });
101
- // `formatRange` is ES2021; fall back to two formatted endpoints on older engines.
102
- if (typeof formatter.formatRange === 'function')
103
- return formatter.formatRange(from, to);
104
- return `${formatter.format(from)} – ${formatter.format(to)}`;
105
- }
106
- const ORDINAL_SUFFIX = {
107
- one: 'st',
108
- two: 'nd',
109
- few: 'rd',
110
- other: 'th',
111
- zero: 'th',
112
- many: 'th',
113
- };
114
- /**
115
- * `Intl` renders `November 5, 2011`, never `5th of November`. When a design asks for the
116
- * ordinal, build it from `Intl.PluralRules` with `type: 'ordinal'` — English-only by
117
- * nature, which is why it is a helper and not the default date format.
118
- */
119
- export function ordinal(value, locale = 'en') {
120
- const category = new Intl.PluralRules(locale, { type: 'ordinal' }).select(value);
121
- return `${value}${ORDINAL_SUFFIX[category]}`;
122
- }
123
- const cache = new Map();
124
- function formatterFor(locale, options) {
125
- const key = `${locale}|${JSON.stringify(options)}`;
126
- const cached = cache.get(key);
127
- if (cached !== undefined)
128
- return cached;
129
- const formatter = new Intl.DateTimeFormat(locale, options);
130
- cache.set(key, formatter);
131
- return formatter;
132
- }
133
- //# sourceMappingURL=format.js.map
package/src/format.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"format.js","sourceRoot":"","sources":["format.ts"],"names":[],"mappings":"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"}
package/src/index.d.ts DELETED
@@ -1,12 +0,0 @@
1
- /** Public surface of @ultimat3/time. Explicit exports only. */
2
- export { addBusinessDays, type BusinessCalendar, businessDaysBetween, type IsoWeekday, isBusinessDay, isHoliday, isWeekend, nextBusinessDay, WEEKEND_FRI_SAT, WEEKEND_SAT_SUN, WEEKEND_SUN_ONLY, } from './business';
3
- export { attachTimeZone, configureTime, currentTimeZone, resolveTimeZone, TIMEZONE_HEADER, type TimeConfig, type TimeZoneResolution, type TimeZoneSourceName, type TimeZoneSources, timeConfig, timeZoneOf, } from './context';
4
- export { type CronExpression, type CronPhrases, DEFAULT_CRON_PHRASES, describeCron, firedSince, isValidCron, matchesCron, nextCronOccurrence, nextCronOccurrenceMs, nextCronOccurrences, parseCron, } from './cron';
5
- export { DAY, type FormatDurationOptions, formatDuration, formatDurationIso, HOUR, MINUTE, MS, parseDuration, SECOND, toMs, toSeconds, WEEK, } from './duration';
6
- export { cronInvalid, dstAmbiguous, dstNonexistent, durationInvalid, instantInvalid, TIME_ERROR_CODES, TimeError, type TimeErrorCode, timezoneInvalid, } from './errors';
7
- export { type DateTimeStyle, type FormatContext, type FormatDateTimeOptions, type FormatRelativeOptions, formatDate, formatDateTime, formatIsoDate, formatRange, formatRelative, formatTime, formatWithOffset, ordinal, } from './format';
8
- export { addMs, compareInstants, differenceMs, EPOCH, fromEpochMs, fromEpochSeconds, fromIso, type Instant, instant, isAfter, isBefore, isInstant, now, subtractMs, toEpochMs, toIso, toIsoDateUtc, } from './instant';
9
- export { type LocalSlot, nextLocalSlot, nextLocalSlots, nextWeeklySlot, type WeeklySlot, } from './schedule';
10
- export { addDaysInZone, endOfDay, type FromZonedOptions, type FromZonedResult, fromZoned, fromZonedDetailed, type GapPolicy, isoDateInZone, isSameLocalDay, type OverlapPolicy, startOfDay, toZoned, type WallClock, type ZonedDateTime, type ZonedResolution, } from './zoned';
11
- export { assertTimeZone, isValidTimeZone, observesDst, offsetAt, offsetLabel, type TimeZone, UTC, type ZoneParts, zoneAbbrev, zonePartsAt, } from './zones';
12
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,OAAO,EACL,eAAe,EACf,KAAK,gBAAgB,EACrB,mBAAmB,EACnB,KAAK,UAAU,EACf,aAAa,EACb,SAAS,EACT,SAAS,EACT,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,EACd,aAAa,EACb,eAAe,EACf,eAAe,EACf,eAAe,EACf,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,UAAU,EACV,UAAU,GACX,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,oBAAoB,EACpB,YAAY,EACZ,UAAU,EACV,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,SAAS,GACV,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,GAAG,EACH,KAAK,qBAAqB,EAC1B,cAAc,EACd,iBAAiB,EACjB,IAAI,EACJ,MAAM,EACN,EAAE,EACF,aAAa,EACb,MAAM,EACN,IAAI,EACJ,SAAS,EACT,IAAI,GACL,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,cAAc,EACd,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,SAAS,EACT,KAAK,aAAa,EAClB,eAAe,GAChB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,UAAU,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,OAAO,GACR,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,EACL,eAAe,EACf,YAAY,EACZ,KAAK,EACL,WAAW,EACX,gBAAgB,EAChB,OAAO,EACP,KAAK,OAAO,EACZ,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,EACH,UAAU,EACV,SAAS,EACT,KAAK,EACL,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,SAAS,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,SAAS,EACT,iBAAiB,EACjB,KAAK,SAAS,EACd,aAAa,EACb,cAAc,EACd,KAAK,aAAa,EAClB,UAAU,EACV,OAAO,EACP,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,cAAc,EACd,eAAe,EACf,WAAW,EACX,QAAQ,EACR,WAAW,EACX,KAAK,QAAQ,EACb,GAAG,EACH,KAAK,SAAS,EACd,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC"}
package/src/index.js DELETED
@@ -1,12 +0,0 @@
1
- /** Public surface of @ultimat3/time. Explicit exports only. */
2
- export { addBusinessDays, businessDaysBetween, isBusinessDay, isHoliday, isWeekend, nextBusinessDay, WEEKEND_FRI_SAT, WEEKEND_SAT_SUN, WEEKEND_SUN_ONLY, } from './business';
3
- export { attachTimeZone, configureTime, currentTimeZone, resolveTimeZone, TIMEZONE_HEADER, timeConfig, timeZoneOf, } from './context';
4
- export { DEFAULT_CRON_PHRASES, describeCron, firedSince, isValidCron, matchesCron, nextCronOccurrence, nextCronOccurrenceMs, nextCronOccurrences, parseCron, } from './cron';
5
- export { DAY, formatDuration, formatDurationIso, HOUR, MINUTE, MS, parseDuration, SECOND, toMs, toSeconds, WEEK, } from './duration';
6
- export { cronInvalid, dstAmbiguous, dstNonexistent, durationInvalid, instantInvalid, TIME_ERROR_CODES, TimeError, timezoneInvalid, } from './errors';
7
- export { formatDate, formatDateTime, formatIsoDate, formatRange, formatRelative, formatTime, formatWithOffset, ordinal, } from './format';
8
- export { addMs, compareInstants, differenceMs, EPOCH, fromEpochMs, fromEpochSeconds, fromIso, instant, isAfter, isBefore, isInstant, now, subtractMs, toEpochMs, toIso, toIsoDateUtc, } from './instant';
9
- export { nextLocalSlot, nextLocalSlots, nextWeeklySlot, } from './schedule';
10
- export { addDaysInZone, endOfDay, fromZoned, fromZonedDetailed, isoDateInZone, isSameLocalDay, startOfDay, toZoned, } from './zoned';
11
- export { assertTimeZone, isValidTimeZone, observesDst, offsetAt, offsetLabel, UTC, zoneAbbrev, zonePartsAt, } from './zones';
12
- //# sourceMappingURL=index.js.map
package/src/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,OAAO,EACL,eAAe,EAEf,mBAAmB,EAEnB,aAAa,EACb,SAAS,EACT,SAAS,EACT,eAAe,EACf,eAAe,EACf,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,cAAc,EACd,aAAa,EACb,eAAe,EACf,eAAe,EACf,eAAe,EAKf,UAAU,EACV,UAAU,GACX,MAAM,WAAW,CAAC;AACnB,OAAO,EAGL,oBAAoB,EACpB,YAAY,EACZ,UAAU,EACV,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,SAAS,GACV,MAAM,QAAQ,CAAC;AAChB,OAAO,EACL,GAAG,EAEH,cAAc,EACd,iBAAiB,EACjB,IAAI,EACJ,MAAM,EACN,EAAE,EACF,aAAa,EACb,MAAM,EACN,IAAI,EACJ,SAAS,EACT,IAAI,GACL,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,cAAc,EACd,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,SAAS,EAET,eAAe,GAChB,MAAM,UAAU,CAAC;AAClB,OAAO,EAKL,UAAU,EACV,cAAc,EACd,aAAa,EACb,WAAW,EACX,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,OAAO,GACR,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,EACL,eAAe,EACf,YAAY,EACZ,KAAK,EACL,WAAW,EACX,gBAAgB,EAChB,OAAO,EAEP,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,EACH,UAAU,EACV,SAAS,EACT,KAAK,EACL,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAEL,aAAa,EACb,cAAc,EACd,cAAc,GAEf,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,aAAa,EACb,QAAQ,EAGR,SAAS,EACT,iBAAiB,EAEjB,aAAa,EACb,cAAc,EAEd,UAAU,EACV,OAAO,GAIR,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,cAAc,EACd,eAAe,EACf,WAAW,EACX,QAAQ,EACR,WAAW,EAEX,GAAG,EAEH,UAAU,EACV,WAAW,GACZ,MAAM,SAAS,CAAC"}
package/src/instant.d.ts DELETED
@@ -1,38 +0,0 @@
1
- /**
2
- * An `Instant` is a point on the UTC timeline. **All storage is UTC** — columns are
3
- * `timestamptz`, wire format is ISO-8601 with `Z`, and no zone is attached to a stored
4
- * value. Zones exist only at the edge, in `format.ts` and `zoned.ts`.
5
- */
6
- import { type Clock } from '@ultimat3/core';
7
- declare const instantBrand: unique symbol;
8
- /** A `Date` that has been proven valid and is documented as UTC. */
9
- export type Instant = Date & {
10
- readonly [instantBrand]: 'utc';
11
- };
12
- /** Wrap a `Date` from an untrusted source (a DB driver, a parsed payload). */
13
- export declare function instant(value: Date): Instant;
14
- /** ISO-8601 in, `Instant` out. An offset or `Z` is required — a bare local string is a bug. */
15
- export declare function fromIso(iso: string): Instant;
16
- /** The only serialization: `2026-03-14T09:00:00.000Z`. */
17
- export declare function toIso(at: Instant): string;
18
- /** Date-only ISO form in UTC. Use `formatIsoDate` when you need it in a zone. */
19
- export declare function toIsoDateUtc(at: Instant): string;
20
- export declare function fromEpochMs(ms: number): Instant;
21
- export declare function toEpochMs(at: Instant): number;
22
- export declare function fromEpochSeconds(seconds: number): Instant;
23
- /**
24
- * The clock is always injected. Tests freeze time by passing a fake clock; nothing in
25
- * the framework calls `Date.now()` directly.
26
- */
27
- export declare function now(clock?: Clock): Instant;
28
- export declare function addMs(at: Instant, ms: number): Instant;
29
- export declare function subtractMs(at: Instant, ms: number): Instant;
30
- /** Signed milliseconds from `from` to `to`. */
31
- export declare function differenceMs(from: Instant, to: Instant): number;
32
- export declare function isBefore(left: Instant, right: Instant): boolean;
33
- export declare function isAfter(left: Instant, right: Instant): boolean;
34
- export declare function compareInstants(left: Instant, right: Instant): -1 | 0 | 1;
35
- export declare function isInstant(value: unknown): value is Instant;
36
- export declare const EPOCH: Instant;
37
- export {};
38
- //# sourceMappingURL=instant.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"instant.d.ts","sourceRoot":"","sources":["instant.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,KAAK,EAAe,MAAM,gBAAgB,CAAC;AAGzD,OAAO,CAAC,MAAM,YAAY,EAAE,OAAO,MAAM,CAAC;AAE1C,oEAAoE;AACpE,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAAE,QAAQ,CAAC,CAAC,YAAY,CAAC,EAAE,KAAK,CAAA;CAAE,CAAC;AAEhE,8EAA8E;AAC9E,wBAAgB,OAAO,CAAC,KAAK,EAAE,IAAI,GAAG,OAAO,CAG5C;AAED,+FAA+F;AAC/F,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAI5C;AAED,0DAA0D;AAC1D,wBAAgB,KAAK,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAEzC;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAEhD;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAG/C;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAE7C;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;GAGG;AACH,wBAAgB,GAAG,CAAC,KAAK,GAAE,KAAmB,GAAG,OAAO,CAEvD;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,+CAA+C;AAC/C,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,OAAO,GAAG,MAAM,CAE/D;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAE9D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAGzE;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAE1D;AAED,eAAO,MAAM,KAAK,EAAE,OAAgC,CAAC"}
package/src/instant.js DELETED
@@ -1,76 +0,0 @@
1
- /**
2
- * An `Instant` is a point on the UTC timeline. **All storage is UTC** — columns are
3
- * `timestamptz`, wire format is ISO-8601 with `Z`, and no zone is attached to a stored
4
- * value. Zones exist only at the edge, in `format.ts` and `zoned.ts`.
5
- */
6
- import { systemClock } from '@ultimat3/core';
7
- import { instantInvalid } from './errors';
8
- /** Wrap a `Date` from an untrusted source (a DB driver, a parsed payload). */
9
- export function instant(value) {
10
- if (Number.isNaN(value.getTime()))
11
- throw instantInvalid(String(value));
12
- return value;
13
- }
14
- /** ISO-8601 in, `Instant` out. An offset or `Z` is required — a bare local string is a bug. */
15
- export function fromIso(iso) {
16
- const parsed = new Date(iso);
17
- if (Number.isNaN(parsed.getTime()))
18
- throw instantInvalid(iso);
19
- return parsed;
20
- }
21
- /** The only serialization: `2026-03-14T09:00:00.000Z`. */
22
- export function toIso(at) {
23
- return at.toISOString();
24
- }
25
- /** Date-only ISO form in UTC. Use `formatIsoDate` when you need it in a zone. */
26
- export function toIsoDateUtc(at) {
27
- return at.toISOString().slice(0, 10);
28
- }
29
- export function fromEpochMs(ms) {
30
- if (!Number.isFinite(ms))
31
- throw instantInvalid(String(ms));
32
- return new Date(ms);
33
- }
34
- export function toEpochMs(at) {
35
- return at.getTime();
36
- }
37
- export function fromEpochSeconds(seconds) {
38
- return fromEpochMs(seconds * 1000);
39
- }
40
- /**
41
- * The clock is always injected. Tests freeze time by passing a fake clock; nothing in
42
- * the framework calls `Date.now()` directly.
43
- */
44
- export function now(clock = systemClock) {
45
- return fromEpochMs(epochMsOf(clock.now()));
46
- }
47
- export function addMs(at, ms) {
48
- return fromEpochMs(at.getTime() + ms);
49
- }
50
- export function subtractMs(at, ms) {
51
- return fromEpochMs(at.getTime() - ms);
52
- }
53
- /** Signed milliseconds from `from` to `to`. */
54
- export function differenceMs(from, to) {
55
- return to.getTime() - from.getTime();
56
- }
57
- export function isBefore(left, right) {
58
- return left.getTime() < right.getTime();
59
- }
60
- export function isAfter(left, right) {
61
- return left.getTime() > right.getTime();
62
- }
63
- export function compareInstants(left, right) {
64
- if (left.getTime() < right.getTime())
65
- return -1;
66
- return left.getTime() > right.getTime() ? 1 : 0;
67
- }
68
- export function isInstant(value) {
69
- return value instanceof Date && !Number.isNaN(value.getTime());
70
- }
71
- export const EPOCH = new Date(0);
72
- /** Tolerates a `Clock` whose `now()` returns either a `Date` or epoch milliseconds. */
73
- function epochMsOf(value) {
74
- return typeof value === 'number' ? value : value.getTime();
75
- }
76
- //# sourceMappingURL=instant.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"instant.js","sourceRoot":"","sources":["instant.ts"],"names":[],"mappings":"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"}
package/src/schedule.d.ts DELETED
@@ -1,24 +0,0 @@
1
- /**
2
- * "Send at 09:00 local" — the scheduling primitive digests, reminders and drip campaigns
3
- * are built from. Local means the *recipient's* local, and it survives DST.
4
- */
5
- import type { Instant } from './instant';
6
- import { type TimeZone } from './zones';
7
- export interface LocalSlot {
8
- zone: TimeZone;
9
- /** 0–23, wall clock in `zone`. */
10
- hour: number;
11
- /** 0–59. */
12
- minute?: number;
13
- second?: number;
14
- }
15
- export declare function nextLocalSlot(slot: LocalSlot, after: Instant): Instant;
16
- /** The next `count` daily slots — a preview for the schedule screen. */
17
- export declare function nextLocalSlots(slot: LocalSlot, after: Instant, count: number): Instant[];
18
- export interface WeeklySlot extends LocalSlot {
19
- /** ISO weekday: 1 = Monday … 7 = Sunday. */
20
- weekday: number;
21
- }
22
- /** Next `weekday` at the local time, strictly after `after`. */
23
- export declare function nextWeeklySlot(slot: WeeklySlot, after: Instant): Instant;
24
- //# sourceMappingURL=schedule.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["schedule.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,QAAQ,CAAC;IACf,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,YAAY;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAgBD,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CA6BtE;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,CAQxF;AAED,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAQxE"}
package/src/schedule.js DELETED
@@ -1,67 +0,0 @@
1
- /**
2
- * "Send at 09:00 local" — the scheduling primitive digests, reminders and drip campaigns
3
- * are built from. Local means the *recipient's* local, and it survives DST.
4
- */
5
- import { scheduleInvalid, timezoneInvalid } from './errors';
6
- import { fromZoned, toZoned } from './zoned';
7
- import { assertTimeZone } from './zones';
8
- /**
9
- * Next instant matching the local `HH:mm` in `zone`, strictly after `after`.
10
- *
11
- * Walks forward day by day on the *local* calendar rather than adding 86 400 000 ms, so
12
- * a 23- or 25-hour day does not shift the slot. If the slot falls in a spring-forward
13
- * gap, `{ gap: 'next' }` picks the first existing local time instead of skipping the day.
14
- */
15
- /** Wall-clock fields are never wrapped or clamped — a shifted schedule beats no schedule. */
16
- function assertWallField(field, value, max) {
17
- if (!Number.isInteger(value) || value < 0 || value > max) {
18
- throw scheduleInvalid(field, value, `an integer 0-${String(max)}`);
19
- }
20
- }
21
- export function nextLocalSlot(slot, after) {
22
- assertTimeZone(slot.zone);
23
- assertWallField('slot.hour', slot.hour, 23);
24
- const minute = slot.minute ?? 0;
25
- const second = slot.second ?? 0;
26
- // Validated too: a minute of 90 would otherwise roll into the next hour and ship a
27
- // schedule an hour off, which is exactly the class of bug this package exists to prevent.
28
- assertWallField('slot.minute', minute, 59);
29
- assertWallField('slot.second', second, 59);
30
- const start = toZoned(after, slot.zone);
31
- for (let dayOffset = 0; dayOffset < 4; dayOffset += 1) {
32
- const candidate = fromZoned({
33
- year: start.year,
34
- month: start.month,
35
- day: start.day + dayOffset,
36
- hour: slot.hour,
37
- minute,
38
- second,
39
- }, slot.zone, { gap: 'next', overlap: 'first' });
40
- if (candidate.getTime() > after.getTime())
41
- return candidate;
42
- }
43
- // Four local days always contain the slot; reaching here means the zone data is broken.
44
- throw timezoneInvalid(slot.zone);
45
- }
46
- /** The next `count` daily slots — a preview for the schedule screen. */
47
- export function nextLocalSlots(slot, after, count) {
48
- const slots = [];
49
- let cursor = after;
50
- for (let index = 0; index < count; index += 1) {
51
- cursor = nextLocalSlot(slot, cursor);
52
- slots.push(cursor);
53
- }
54
- return slots;
55
- }
56
- /** Next `weekday` at the local time, strictly after `after`. */
57
- export function nextWeeklySlot(slot, after) {
58
- let cursor = after;
59
- for (let index = 0; index < 8; index += 1) {
60
- const candidate = nextLocalSlot(slot, cursor);
61
- if (toZoned(candidate, slot.zone).weekday === slot.weekday)
62
- return candidate;
63
- cursor = candidate;
64
- }
65
- throw timezoneInvalid(slot.zone);
66
- }
67
- //# sourceMappingURL=schedule.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"schedule.js","sourceRoot":"","sources":["schedule.ts"],"names":[],"mappings":"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"}
package/src/zoned.d.ts DELETED
@@ -1,80 +0,0 @@
1
- /**
2
- * The two conversions everything else is built on: instant → wall clock, and wall clock →
3
- * instant. `fromZoned` is the DST-correct one, and it is the reason this package exists.
4
- */
5
- import { type Instant } from './instant';
6
- import { type TimeZone } from './zones';
7
- /** A local date and time with no zone attached — meaningless until paired with one. */
8
- export interface WallClock {
9
- year: number;
10
- month: number;
11
- day: number;
12
- hour: number;
13
- minute: number;
14
- second?: number;
15
- millisecond?: number;
16
- }
17
- export interface ZonedDateTime {
18
- year: number;
19
- month: number;
20
- day: number;
21
- hour: number;
22
- minute: number;
23
- second: number;
24
- millisecond: number;
25
- zone: TimeZone;
26
- /** Minutes east of UTC in effect at this instant. */
27
- offsetMinutes: number;
28
- /** ISO weekday: 1 = Monday … 7 = Sunday. */
29
- weekday: number;
30
- }
31
- /** What to do with a wall-clock time that does not exist (spring forward). */
32
- export type GapPolicy = 'next' | 'previous' | 'throw';
33
- /** What to do with a wall-clock time that happens twice (fall back). */
34
- export type OverlapPolicy = 'first' | 'second' | 'throw';
35
- export interface FromZonedOptions {
36
- gap?: GapPolicy;
37
- overlap?: OverlapPolicy;
38
- }
39
- export type ZonedResolution = 'exact' | 'gap' | 'overlap';
40
- export interface FromZonedResult {
41
- instant: Instant;
42
- /** Which case the wall-clock time fell into — log it when scheduling. */
43
- resolution: ZonedResolution;
44
- offsetMinutes: number;
45
- }
46
- /** Instant → the zone's wall clock. */
47
- export declare function toZoned(at: Instant, zone: TimeZone): ZonedDateTime;
48
- /**
49
- * Wall clock + zone → instant, DST-correct.
50
- *
51
- * Algorithm (search and verify — never `offset * 3600`):
52
- * 1. Read the wall-clock fields as if they were UTC. Call that `target`.
53
- * 2. Collect the candidate offsets in force around `target` — 24h before, at, and 24h
54
- * after. Any DST transition puts both of its offsets in that set.
55
- * 3. For each distinct offset `o`, the candidate instant is `target - o`.
56
- * 4. Verify each candidate by converting it *back* with `toZoned`. A candidate that does
57
- * not reproduce the requested wall clock is not a real instant for it.
58
- * - two distinct verified candidates → the hour repeats (overlap)
59
- * - exactly one → normal case
60
- * - none → the hour was skipped (gap)
61
- *
62
- * Milliseconds ride along untouched: every real offset is a whole number of minutes.
63
- */
64
- export declare function fromZonedDetailed(wall: WallClock, zone: TimeZone, options?: FromZonedOptions): FromZonedResult;
65
- /** The common form: just the instant. */
66
- export declare function fromZoned(wall: WallClock, zone: TimeZone, options?: FromZonedOptions): Instant;
67
- /** Midnight local, as an instant. DST-correct: on a spring-forward day it still exists. */
68
- export declare function startOfDay(at: Instant, zone: TimeZone): Instant;
69
- /** The last millisecond of the local day. */
70
- export declare function endOfDay(at: Instant, zone: TimeZone): Instant;
71
- /**
72
- * Add calendar days in a zone, keeping the wall-clock time. Adding one day is 23, 24 or
73
- * 25 hours depending on the transition — which is why this is not `+ 86_400_000`.
74
- */
75
- export declare function addDaysInZone(at: Instant, days: number, zone: TimeZone): Instant;
76
- /** `2026-03-14` for the given instant in the given zone. */
77
- export declare function isoDateInZone(at: Instant, zone: TimeZone): string;
78
- /** True when both instants fall on the same local calendar day. */
79
- export declare function isSameLocalDay(left: Instant, right: Instant, zone: TimeZone): boolean;
80
- //# sourceMappingURL=zoned.d.ts.map