Skip to content

parseDateTimeWithPattern

parseDateTimeWithPattern(value: string, pattern: string, locale?: string | string[], options?: TwoDigitYearOptions): string

Parse a datetime string against a caller-supplied token pattern (e.g. "MM/dd/yyyy HH:mm:ss", "dd-MMM-yyyy h:mm a") and return it as an ISO PlainDateTime string.

  • Decoding, not display. This is for consuming a known, fixed producer format — a CSV column, a legacy API field, a partially-typed form value — not for generating locale-correct output. For display, use formatDateTime/formatDateToParts, which order fields per locale instead of hard-coding a field order (a token formatter, the inverse of this function, is deliberately out of scope for GMT).

  • Accepts the full combined date + time token set (unlike parseDateWithPattern/parseTimeWithPattern, which each reject the other’s tokens). ### Token table

    TokenFieldWidthRangeNotes
    yyyyyear4 digits0000–9999
    yyyear2 digits00–99Needs options.yearWindow; returns "" without it
    MMmonth2 digits01–12
    Mmonth1-2 digits1–12
    MMMMmonth name (long)locale—getLocaleMonthNames(locale, "long")
    MMMmonth name (short)locale—getLocaleMonthNames(locale, "short")
    ddday2 digits01–31
    dday1-2 digits1–31
    HHhour (24h)2 digits00–23
    Hhour (24h)1-2 digits0–23
    hhhour (12h)2 digits01–12needs a to resolve to 24h — see below
    hhour (12h)1-2 digits1–12same
    mmminute2 digits00–59
    mminute1-2 digits0–59
    sssecond2 digits00–59
    ssecond1-2 digits0–59
    SSSmillisecond3 digits000–999
    EEEEweekday name (long)locale—consumed, NOT cross-validated against the date (see below)
    EEEweekday name (short)locale—same
    ameridiemlocale—getLocaleMeridiems(locale) → [AM-label, PM-label]
    GGGGera name (long)locale—BCE label ⇒ final year = 1 − parsed year
    GGera name (short)locale—same resolution
  • Literal text follows the Unicode UTS #35 date format pattern rules. Text in 'single quotes' is a literal, letters included. Two adjacent quotes '' are one literal ', inside or outside quoted text: "yyyy''MM" reads "2024'03", and "'o''clock'" reads "o'clock". A quote that never closes makes the pattern malformed (""). Any other character that is not a letter (/ - , space :, digits and so on) is a literal with no quoting.

  • MMMM/MMM/EEEE/EEE/a/GGGG/GG are locale-aware. If locale is omitted and the pattern uses any of them, GMT defaults to "en-US" rather than returning "" for every caller who didn’t have another locale in mind.

  • EEEE/EEE are matched against the locale’s weekday names but the result is not cross-checked against the constructed date — e.g. "Monday, 2024-03-15 14:00" parses successfully against "EEEE, yyyy-MM-dd HH:mm" even though 2024-03-15 is actually a Friday. This is a deliberate scope limit (no second weekday-index-to-ISO-dayOfWeek mapping layer).

  • h/hh resolve to 24-hour using a matched a token: if the label is the PM label and hour !== 12, add 12; if the AM label and hour === 12, set hour to 0. With no a token in the pattern at all, the pattern is still valid but the hour stays ambiguous — GMT resolves it as if AM had matched (12 → 0, otherwise unchanged).

  • Adjacent variable-width numeric tokens with no literal separator (e.g. "Mdyyyy") are inherently ambiguous: each becomes a greedy \d{1,2}/etc. in sequence, so the split between fields depends on regex backtracking, not on any documented rule. Prefer zero-padded tokens (MM, dd) or an explicit separator for reliable parsing.

  • A shape-valid match does not by itself prove a real date/time: extracted fields are always handed to Temporal.PlainDateTime.from(fields, { overflow: "reject" }) — the regex only proves the shape, Temporal proves the value is real.

  • Fields absent from pattern default the way Temporal.PlainDateTime.from defaults an omitted field (time fields default to 0; year/month/day are still required for a valid result).

  • A yy token needs the caller’s hundred-year window, options.yearWindow: no standard says which century a two-digit year belongs to, and a cut-off built into a library is right today and wrong later. Without a valid window a yy pattern returns "". A pattern with no yy never reads the option, so a "rolling" window reads no clock for it. A two-digit year is a legacy form; a four-digit year is the one to ask a data source for.

ParameterTypeDescription
valuestringThe string to decode (e.g. “03/15/2024 14:30:00”)
patternstringThe token pattern describing value’s shape (e.g. “MM/dd/yyyy HH:mm:ss”)
localestring | string[]Optional BCP 47 locale tag, or a preference list of tags, for name-based tokens (default “en-US”)

options

The hundred-year window a yy token resolves in

Type: TwoDigitYearOptions.

OptionTypeDefaultDescription
yearWindow?number | "rolling"None. A value with a two-digit year returns the sentinel: no standard says which century it belongs to, so the caller states it.The hundred-year window a two-digit year falls in. A number is the first year of a fixed window, an integer from 0 to 9900: 2000 reads 00–99 as 2000–2099; 1950 reads 50–99 as 1950–1999 and 00–49 as 2000–2049. A fixed window gives the same answer in any year, so it is the form for stored data, archives and anything re-processed later. "rolling" is the hundred years around today: from 50 years before the current UTC calendar year to 49 years after it (in 2026, 1976–2075). It is the form for live messages and never needs maintenance, but it gives a different answer for the same stored value as years pass. Any other value returns the function’s sentinel.

ISO PlainDateTime string, or "" on no match, malformed pattern, or invalid input

import { parseDateTimeWithPattern } from "@northguild/gmt/plain/parse";
parseDateTimeWithPattern("03/15/2024 14:30:00", "MM/dd/yyyy HH:mm:ss") // "2024-03-15T14:30:00"
output 

plain/parse/parseDateTimeWithPattern.ts