---
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)