UNPKG

@burglekitt/gmt

Version:

Temporal-based date and time utilities with timezone support and polyfill integration

438 lines (316 loc) 14.7 kB
--- name: validate-dates description: > Validate date/time strings, timezone identifiers, intervals, or ranges. Use isValidDate, isValidTime, isValidDateTime, isValidDateInterval, isValidTimeInterval, isValidDateTimeInterval for scalar and interval validation. Use hasDaylightSaving to check whether an IANA timezone observes daylight saving time. Use getDstTransitions to enumerate DST transition instants for a timezone in a given year. Use intervalContains*, intervalUnion*, and splitIntervalByUnit* for interval containment, merging, and splitting. All validation functions return false on invalid input; interval union returns null on disjoint or invalid input; split functions return [] on invalid input. sources: - 'burglekitt/gmt:packages/gmt/src/plain/validate/index.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/validate/index.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/intervalContainsDate.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/intervalContainsTime.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/intervalContainsDateTime.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/intervalUnionDate.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/intervalUnionTime.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/intervalUnionDateTime.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/splitIntervalByUnitDate.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/splitIntervalByUnitTime.ts' - 'burglekitt/gmt:packages/gmt/src/plain/interval/splitIntervalByUnitDateTime.ts' - 'burglekitt/gmt:packages/gmt/src/utc/validate/index.ts' - 'burglekitt/gmt:packages/gmt/src/utc/interval/validate/index.ts' - 'burglekitt/gmt:packages/gmt/src/utc/interval/intervalContainsUtc.ts' - 'burglekitt/gmt:packages/gmt/src/utc/interval/intervalUnionUtc.ts' - 'burglekitt/gmt:packages/gmt/src/utc/interval/splitIntervalByUnitUtc.ts' - 'burglekitt/gmt:packages/gmt/src/unix/validate/index.ts' - 'burglekitt/gmt:packages/gmt/src/unix/interval/validate/index.ts' - 'burglekitt/gmt:packages/gmt/src/unix/interval/intervalContainsUnix.ts' - 'burglekitt/gmt:packages/gmt/src/unix/interval/intervalUnionUnix.ts' - 'burglekitt/gmt:packages/gmt/src/unix/interval/splitIntervalByUnitUnix.ts' - 'burglekitt/gmt:packages/gmt/src/zoned/validate/index.ts' - 'burglekitt/gmt:packages/gmt/src/zoned/validate/hasDaylightSaving.ts' - 'burglekitt/gmt:packages/gmt/src/zoned/get/getDstTransitions.ts' - 'burglekitt/gmt:packages/gmt/src/zoned/interval/validate/index.ts' - 'burglekitt/gmt:packages/gmt/src/zoned/interval/intervalContainsZoned.ts' - 'burglekitt/gmt:packages/gmt/src/zoned/interval/intervalUnionZoned.ts' - 'burglekitt/gmt:packages/gmt/src/zoned/interval/splitIntervalByUnitZoned.ts' metadata: type: core library: '@burglekitt/gmt' library_version: '1.14.1' --- # Validate Dates Use this skill when you need to validate date, time, or datetime strings before processing. ## Setup ```ts import { isValidDate, isValidTime, isValidDateTime } from "@burglekitt/gmt"; import { isValidTimeZone, isValidZonedDateTime } from "@burglekitt/gmt/zoned"; ``` ## Core Patterns ### Validate ISO date string ```ts const valid = isValidDate("2024-03-15"); // true const invalid = isValidDate("2024-02-30"); // false (invalid day) const invalidFormat = isValidDate("invalid"); // false ``` ### Validate ISO time string ```ts import { isValidTime } from "@burglekitt/gmt"; const valid = isValidTime("14:30:45"); // true const invalid = isValidTime("25:00:00"); // false (invalid hour) const invalidFormat = isValidTime("not a time"); // false ``` ### Validate ISO datetime string ```ts import { isValidDateTime } from "@burglekitt/gmt"; const valid = isValidDateTime("2024-03-15T14:30:45"); // true const invalid = isValidDateTime("2024-02-30T14:30:45"); // false ``` ### Validate IANA timezone ```ts import { isValidTimeZone } from "@burglekitt/gmt/zoned"; const valid = isValidTimeZone("America/New_York"); // true const invalid = isValidTimeZone("Invalid/Zone"); // false ``` ### Validate zoned datetime string ```ts import { isValidZonedDateTime } from "@burglekitt/gmt/zoned"; const valid = isValidZonedDateTime("2024-03-15T14:30:45[America/New_York]"); // true const invalid = isValidZonedDateTime("2024-02-30T14:30:45[America/New_York]"); // false // E5 (issue #78): a [u-ca=...] calendar annotation is always invalid here — calendar-system // awareness is plain/ PlainDate only. Same rejection applies to isValidZonedInterval below and // every zoned/interval/* function. const rejected = isValidZonedDateTime( "2024-03-15T14:30:45-04:00[America/New_York][u-ca=hebrew]", ); // false ``` ### Check daylight saving time ```ts import { hasDaylightSaving } from "@burglekitt/gmt/zoned"; const hasDst = hasDaylightSaving("America/New_York"); // true const noDst = hasDaylightSaving("Asia/Tokyo"); // false const invalid = hasDaylightSaving("Invalid/Zone"); // false ``` ### List DST transition instants ```ts import { getDstTransitions } from "@burglekitt/gmt/zoned"; const transitions = getDstTransitions("America/New_York", 2024); // [ // { instant: "2024-03-10T07:00:00Z", offsetBefore: "-05:00", offsetAfter: "-04:00" }, // { instant: "2024-11-03T06:00:00Z", offsetBefore: "-04:00", offsetAfter: "-05:00" } // ] const noTransitions = getDstTransitions("Asia/Tokyo", 2024); // [] ``` ### Validate date duration unit ```ts import { isValidDateUnit } from "@burglekitt/gmt"; const valid = isValidDateUnit("day"); // true const valid = isValidDateUnit("month"); // true const valid = isValidDateUnit("year"); // true const invalid = isValidDateUnit("invalid"); // false ``` ### Validate time duration unit ```ts import { isValidTimeUnit } from "@burglekitt/gmt"; const valid = isValidTimeUnit("hour"); // true const valid = isValidTimeUnit("minute"); // true const invalid = isValidTimeUnit("invalid"); // false ``` ### Validate date interval ```ts import { isValidDateInterval } from "@burglekitt/gmt"; const valid = isValidDateInterval("2024-01-01", "2024-12-31"); // true const invalid = isValidDateInterval("2024-12-31", "2024-01-01"); // false (start > end) const invalid = isValidDateInterval("not-a-date", "2024-12-31"); // false // E5 (issue #78): accepts GMT calendar-annotated PlainDate strings, and start/end may carry // different calendars — ordering is calendar-independent. const mixedCalendars = isValidDateInterval( "5785-01-01[u-ca=hebrew]", "2024-12-31", ); // true ``` ### Validate time interval ```ts import { isValidTimeInterval } from "@burglekitt/gmt"; const valid = isValidTimeInterval("09:00:00", "17:00:00"); // true const invalid = isValidTimeInterval("25:00:00", "17:00:00"); // false (invalid start) ``` ### Validate datetime interval ```ts import { isValidDateTimeInterval } from "@burglekitt/gmt"; const valid = isValidDateTimeInterval("2024-01-01T09:00:00", "2024-12-31T17:00:00"); // true const invalid = isValidDateTimeInterval("2024-12-31T17:00:00", "2024-01-01T09:00:00"); // false ``` ### Validate UTC interval ```ts import { isValidUtcInterval } from "@burglekitt/gmt/utc"; const valid = isValidUtcInterval("2024-01-01T09:00:00Z", "2024-12-31T17:00:00Z"); // true const invalid = isValidUtcInterval("2024-12-31T17:00:00Z", "2024-01-01T09:00:00Z"); // false ``` ### Validate Unix interval ```ts import { isValidUnixInterval } from "@burglekitt/gmt/unix"; const valid = isValidUnixInterval("1704067200", "1704067800"); // true (seconds) const validMs = isValidUnixInterval("1704067200000", "1704067800000"); // true (milliseconds) const invalid = isValidUnixInterval("1704067800", "1704067200"); // false ``` ### Validate zoned interval ```ts import { isValidZonedInterval } from "@burglekitt/gmt/zoned"; const valid = isValidZonedInterval( "2024-01-01T09:00:00+00:00[UTC]", "2024-12-31T17:00:00+00:00[UTC]" ); // true const invalid = isValidZonedInterval( "2024-12-31T17:00:00+00:00[UTC]", "2024-01-01T09:00:00+00:00[UTC]" ); // false ``` ### Check point-in-interval (3-arg) ```ts import { intervalContainsDate } from "@burglekitt/gmt"; const inside = intervalContainsDate("2024-01-01", "2024-12-31", "2024-06-15"); // true const onBoundary = intervalContainsDate("2024-01-01", "2024-12-31", "2024-01-01"); // true const outside = intervalContainsDate("2024-01-01", "2024-12-31", "2025-01-01"); // false ``` ### Check interval-in-interval (4-arg) ```ts import { intervalContainsDate } from "@burglekitt/gmt"; const inside = intervalContainsDate("2024-01-01", "2024-12-31", "2024-03-01", "2024-09-01"); // true const equal = intervalContainsDate("2024-01-01", "2024-12-31", "2024-01-01", "2024-12-31"); // true const partial = intervalContainsDate("2024-01-01", "2024-12-31", "2024-06-15", "2025-01-01"); // false ``` ### Check time interval containment ```ts import { intervalContainsTime } from "@burglekitt/gmt"; const inside = intervalContainsTime("09:00:00", "17:00:00", "12:00:00"); // true const inner = intervalContainsTime("09:00:00", "17:00:00", "10:00:00", "16:00:00"); // true ``` ### Check datetime interval containment ```ts import { intervalContainsDateTime } from "@burglekitt/gmt"; const inside = intervalContainsDateTime("2024-01-01T10:00:00", "2024-12-31T23:59:59", "2024-06-15T12:00:00"); // true const inner = intervalContainsDateTime("2024-01-01T10:00:00", "2024-12-31T23:59:59", "2024-03-01T00:00:00", "2024-09-01T00:00:00"); // true ``` ### Check UTC interval containment ```ts import { intervalContainsUtc } from "@burglekitt/gmt/utc"; const inside = intervalContainsUtc("2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z", "2024-06-15T12:00:00Z"); // true const inner = intervalContainsUtc("2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z", "2024-03-01T00:00:00Z", "2024-09-01T00:00:00Z"); // true ``` ### Check Unix interval containment ```ts import { intervalContainsUnix } from "@burglekitt/gmt/unix"; const inside = intervalContainsUnix(0, 1700000000, 170000000); // true const inner = intervalContainsUnix(0, 1700000000, 100000, 1000000); // true const stringInput = intervalContainsUnix("0", "1700000000", "170000000"); // true ``` ### Check zoned interval containment ```ts import { intervalContainsZoned } from "@burglekitt/gmt/zoned"; const inside = intervalContainsZoned("2024-01-01T00:00:00+00:00[UTC]", "2024-12-31T23:59:59+00:00[UTC]", "2024-06-15T12:00:00+00:00[UTC]"); // true const inner = intervalContainsZoned("2024-01-01T00:00:00+00:00[UTC]", "2024-12-31T23:59:59+00:00[UTC]", "2024-03-01T00:00:00+00:00[UTC]", "2024-09-01T00:00:00+00:00[UTC]"); // true ``` ### Merge overlapping or adjacent intervals (union) ```ts import { intervalUnionDate } from "@burglekitt/gmt"; const merged = intervalUnionDate("2024-01-01", "2024-06-30", "2024-04-01", "2024-12-31"); // { start: "2024-01-01", end: "2024-12-31" } const adjacent = intervalUnionDate("2024-01-01", "2024-06-30", "2024-06-30", "2024-12-31"); // { start: "2024-01-01", end: "2024-12-31" } — adjacent intervals ARE merged const disjoint = intervalUnionDate("2024-01-01", "2024-06-30", "2024-07-01", "2024-12-31"); // null — gap between intervals ``` `intervalUnionTime`, `intervalUnionDateTime`, `intervalUnionUtc`, `intervalUnionUnix`, and `intervalUnionZoned` follow the same pattern across their respective types. Unix returns `{ start: number; end: number } | null`; all others return `{ start: string; end: string } | null`. ### Split interval into sub-intervals by unit ```ts import { splitIntervalByUnitDate } from "@burglekitt/gmt"; const slices = splitIntervalByUnitDate("2024-01-01", "2024-01-10", "day", 2); // [ // { start: "2024-01-01", end: "2024-01-03" }, // { start: "2024-01-03", end: "2024-01-05" }, // { start: "2024-01-05", end: "2024-01-07" }, // { start: "2024-01-07", end: "2024-01-09" }, // { start: "2024-01-09", end: "2024-01-10" } // ] ``` `splitIntervalByUnitTime`, `splitIntervalByUnitDateTime`, `splitIntervalByUnitUtc`, `splitIntervalByUnitUnix`, and `splitIntervalByUnitZoned` follow the same pattern. The final sub-interval is trimmed so its `end` never exceeds the original `end`. All split functions return `[]` on invalid input (wrong type, malformed strings, leap seconds, inverted intervals, non-positive amount, unsupported unit, or a unit that has no effect on the target type). ## Common Mistakes ### HIGH Not validating before parsing Wrong: ```ts const date = Temporal.PlainDate.from(input); // may throw ``` Correct: ```ts import { isValidDate } from "@burglekitt/gmt"; if (!isValidDate(input)) { throw new Error("Invalid date"); } const date = Temporal.PlainDate.from(input); ``` Source: AGENTS.md — Always validate before parsing ### HIGH Not validating timezone before use Wrong: ```ts const zoned = Temporal.ZonedDateTime.from("2024-03-15T14:30:45[Invalid/Zone]"); // may throw ``` Correct: ```ts import { isValidTimeZone } from "@burglekitt/gmt/zoned"; if (!isValidTimeZone("America/New_York")) { throw new Error("Invalid timezone"); } const zoned = Temporal.ZonedDateTime.from("2024-03-15T14:30:45[America/New_York]"); ``` Source: packages/gmt/src/zoned/validate/isValidTimeZone.ts — Validates IANA timezone ### MEDIUM Using try-catch for validation Wrong: ```ts let valid = false; try { Temporal.PlainDate.from(input); valid = true; } catch { valid = false; } ``` Correct: ```ts import { isValidDate } from "@burglekitt/gmt"; const valid = isValidDate(input); ``` Source: AGENTS.md — Use validation functions, not exceptions for flow ### MEDIUM Confusing range validators with interval validators Wrong: ```ts import { isValidDateRange } from "@burglekitt/gmt"; isValidDateRange("2024-01-01", "2024-12-31"); // false — expects { value1, value2 } ``` Correct: ```ts import { isValidDateInterval } from "@burglekitt/gmt"; const valid = isValidDateInterval("2024-01-01", "2024-12-31"); // true ``` Source: `plain/validate/isValidDateRange.ts` — range validators take `{ value1, value2, options? }`; interval validators take `(start, end)` positional args ### MEDIUM Assuming intervals accept reversed bounds Wrong: ```ts import { isValidDateInterval } from "@burglekitt/gmt"; const valid = isValidDateInterval("2024-12-31", "2024-01-01"); // false — start must be <= end ``` Correct: ```ts const valid = isValidDateInterval("2024-01-01", "2024-12-31"); // true ``` Source: `plain/interval/validate/isValidDateInterval.ts` — interval validators enforce `start <= end` ## References - [Full validate API](references/validate-api.md) - [Temporal validation patterns](https://tc39.es/proposal-temporal/docs/iso.html)