Skip to content
clockworktz

Guides

Parsing

clockworktz parses ISO 8601 strictly. Strings without an offset are read in an explicit defaultTimezone (UTC unless you say otherwise), never in the timezone of the host. That rules out the classic bug where new Date('2024-07-15T14:00:00') means different instants on different machines.

import { parse } from 'clockworktz'

parse('2024-07-15T14:00:00+05:45')
// { date: 2024-07-15T08:15:00.000Z, hasTimezone: true, original: '2024-07-15T14:00:00+05:45', isValid: true }

parse('2024-07-15T14:00:00', { defaultTimezone: 'Asia/Tokyo' })
// { date: 2024-07-15T05:00:00.000Z, hasTimezone: false, original: '2024-07-15T14:00:00', isValid: true }

What's accepted

Input Read as
2024-07-15T14:00:00Z Instant (UTC)
2024-07-15T14:00:00+05:45 Instant with offset
2024-07-15T14:00:00-0330 Instant with basic offset
2024-07-15T14:00:00+00:19:32 Instant with an offset that has seconds
2024-07-15T14:00-03 Instant with an hours-only offset
2024-07-15 14:00+05:45 A space instead of T, seconds optional
2024-07-15T14:00:00.250 Wall time in defaultTimezone
2024-07-15 14:00 Wall time, space and no seconds
2024-07-15 Midnight wall time in defaultTimezone

Fractions of a second may have 1 to 9 digits; the result keeps millisecond precision. Wall times read in a zone with DST use compatible disambiguation. Use interpretLocal if you need a different strategy.

ParseOptions

Option Default Description
strict true Only accept ISO 8601. Non-strict also accepts other formats the platform can parse, but only when they carry their own offset
defaultTimezone 'UTC' Zone used to read strings without an offset

Functions

parse(input, options?)

Returns a ParseResult:

interface ParseResult {
  date: Date
  hasTimezone: boolean // the input had its own offset
  original: string
  isValid: boolean
}

In strict mode an invalid input throws TimezoneError; with strict: false it returns isValid: false and an invalid Date instead.

parseDate(input, options?)

Like parse, but returns only the Date:

parseDate('2024-07-15 14:00:00Z') // 2024-07-15T14:00:00.000Z

safeParse(input, options?) and isParseable(input, options?)

Never throw. Both always parse non-strictly (any strict option is ignored), so they accept ISO strings and other formats that carry their own offset. An unparseable input gives a result with isValid: false (the return type also allows null, so use optional chaining):

import { safeParse, isParseable } from 'clockworktz'

safeParse('next tuesday')?.isValid // false

safeParse('Mon, 15 Jul 2024 14:00:00 GMT')?.date
// 2024-07-15T14:00:00.000Z

isParseable('2024-07-15T14:00:00Z') // true
isParseable('15/07/2024') // false

parseAny(input, formats?)

Tries each set of options in turn and returns the first valid result. By default it tries strict ISO, then non-strict:

parseAny('Mon, 15 Jul 2024 14:00:00 GMT').date // 2024-07-15T14:00:00.000Z

Throws TimezoneError if nothing matches.

parseRange(input, options?)

Splits on to, –, — or - (or your own separator) and parses both ends:

parseRange('2024-01-01 to 2024-01-31')
// { start: 2024-01-01T00:00:00.000Z, end: 2024-01-31T00:00:00.000Z }

Throws TimezoneError if there aren't exactly two parts or start is after end.

parseTime(input)

Parses a time of day, H:mm, HH:mm, HH:mm:ss or HH:mm:ss.SSS:

parseTime('09:30') // { hour: 9, minute: 30, second: 0, millisecond: 0 }
parseTime('9:30:15.250') // { hour: 9, minute: 30, second: 15, millisecond: 250 }

Fractional seconds must have three digits.

parseOffset(input)

Parses an offset to minutes. Seconds give a fractional result:

import { parseOffset } from 'clockworktz'

parseOffset('+05:45') // 345
parseOffset('-0330') // -210
parseOffset('+5:30') // 330
parseOffset('+00:19:32') // 19.533333333333335
parseOffset('Z') // 0
parseOffset('UTC') // 0

parseComponents(input)

Splits a wall-time string into fields without attaching a zone:

parseComponents('2024-07-15T14:00:00.250')
// { year: 2024, month: 7, day: 15, hour: 14, minute: 0, second: 0, millisecond: 250 }

normalize(input)

Turns any DateInput (Date, epoch milliseconds or string) into a Date, throwing TimezoneError for invalid input. Strings go through non-strict parsing, so they must carry an offset.

isValidISOString(input) and isValidLocalISOString(input)

Check the two string shapes without parsing them:

import { isValidISOString, isValidLocalISOString } from 'clockworktz'

isValidISOString('2024-07-15T14:00:00Z') // true: has an offset
isValidISOString('2024-07-15T14:00:00') // false
isValidLocalISOString('2024-07-15T14:00:00') // true: wall time

Validation

Dates are validated, not rolled over. 2024-02-30 is an error, not 1 March:

interpretLocal('2024-02-30T10:00:00', 'UTC')
// throws TimezoneError: Invalid local time "2024-02-30T10:00:00"