Dates & durations
Relative dates against an explicit now, two-way.
Prefer the live demos? Open this section in the interactive docs.
Relative dates parse against an explicit now for reproducible results, and the humanizer output always re-parses within one grain. Import from @pascal-app/lingo/date.
import {
parseDate,
parseDateRange,
humanizeDate,
humanizeDateRange,
parseDuration,
humanizeDuration,
} from "@pascal-app/lingo/date"
parseDate("three days ago", { now }).date // exact Date, grain "day"
parseDate("17h30", { now }) // 17:30 today (grain "minute")
parseDate("quarter past 5", { now }) // 05:15
humanizeDate(d, { now }) // "3 days ago" — re-parseable
// Timezones: exposed by default; opt in to resolve the instant
parseDate("3pm EST", { now }).zone // { source: "abbrev", offsetMinutes: -300 }
parseDate("3pm EST", { now, applyZone: true }) // the real UTC instant, not host-local
// Time slots, two-way
const slot = parseDateRange("2pm to 4pm", { now }) // { start, end } endpoints
parseDateRange("9-5", { now }) // workday shift → 09:00–17:00
humanizeDateRange(slot) // "2:00 PM to 4:00 PM"
parseDuration("1h30").duration.base // 5400 (seconds)
humanizeDuration(5400, { style: "natural" }) // "an hour and a half"Times of day read the way people write them — 17h, 5 o’clock, quarter past 5, 5.30pm, midi/minuit, 0900 hours. A trailing timezone is detected and exposed on .zone while the civil wall-clock is kept; pass applyZone: true to resolve the real UTC instant (offsets, abbreviations, and IANA names resolve DST-correctly via Intl). parseDateRange turns a slot like 2pm to 4pm, between 9am and 5pm, or the 9-5 workday shift into { start, end } endpoints, and humanizeDateRange renders it back.
Reference-dependent input needs an explicit now, so a queued job parses the same date every time. Fully absolute dates never require it. Browse the shorthand it reads under Catalog → Date shorthand and Time slots.