Type Formats

Date and Time Formats

Date strings, native Dates and Temporal values, with fixed or relative bounds.

Date Representations

A date comes in three shapes, each with its own format family. All three take the same min / max / gt / lt bounds, written in the type's own ISO form: a PlainDate takes '2020-01-01', an Instant takes '2020-01-01T00:00:00Z'.

  • String-encoded: TF.StringDate, TF.StringTime, TF.StringDateTime check an ISO string in a fixed layout ('2020-01-01', '08:30').
  • Native Date: TF.Date checks a real JS Date; TF.DateFuture / TF.DatePast are ready-made (a Date that's >= now / <= now).
  • TC39 Temporal: TFT.PlainDate, TFT.ZonedDateTime, TFT.Instant, TFT.PlainTime, TFT.PlainDateTime, TFT.PlainYearMonth check an actual Temporal.* instance. Import them from @mionjs/run-types/formats/temporal (as TFT), so apps that don't use Temporal never pull in its lib.
import type * as TF from '@mionjs/run-types/formats';
import type * as TFT from '@mionjs/run-types/formats/temporal';

// The same "2020 or later" bound, two representations:
type DateString = TF.StringDate<{min: '2020-01-01'}>; // an ISO string
type PlainDate = TFT.PlainDate<{min: '2020-01-01'}>;  // a Temporal.PlainDate
Temporal values are checked with instanceof, not by shape. Temporal.PlainMonthDay and Temporal.Duration have no min/max ordering, so they take no bounds. DataOnly<Temporal.PlainDate> stays a Temporal.PlainDate.

Date Format Builders

With builders, use TF.date(...) and the TFT.* temporal namespace.

import * as TF from '@mionjs/run-types/formats';
import * as TFT from '@mionjs/run-types/formats/temporal';

TF.date({max: 'now'});               // a Date in the past
TFT.plainDate({min: '2020-01-01'});  // a Temporal.PlainDate, 2020 on
TFT.instant({min: 'now-PT1H'});      // an Instant within the last hour

Relative Date Bounds

Write a bound as now, or now plus or minus an ISO-8601 duration such as P1Y2M10D (1 year, 2 months, 10 days), P1W (1 week) or PT12H30M (12 hours, 30 minutes). The build checks that the units fit the field.

import type * as TF from '@mionjs/run-types/formats';
import {createValidateFn} from '@mionjs/run-types';

// `now` is the current time each time a value is validated
// A birth date in the past, no more than 120 years ago.
type BirthDate = TF.StringDate<{min: 'now-P120Y'; max: 'now'}>;

// A meeting that starts within the next 30 days.
type StartsSoon = TF.StringDateTime<{min: 'now'; max: 'now+P30D'}>;

const isBirthDate = createValidateFn<BirthDate>();
const startsSoon = createValidateFn<StartsSoon>();

isBirthDate('1990-05-20'); // true
isBirthDate('1850-01-01'); // false, more than 120 years ago

export {isBirthDate, startsSoon};
FamilyRelative units
Date-only (TF.stringDate, TFT.plainDate, TFT.plainYearMonth)date: Y M W D
Time-only (TF.stringTime, TFT.plainTime, TFT.instant)time: H M S
Date + time (TF.stringDateTime, native TF.date, TFT.plainDateTime, TFT.zonedDateTime)both
min / max are inclusive, gt / lt exclusive. Use one or the other per edge, as on a number format.
Copyright © 2026