API Overview
| Symbol | Purpose | Execution mode | Common gotcha |
|---|---|---|---|
parse() | Parse ISO text to an explicit Temporal kind | Sync | as is required |
isValid() | Narrow an unknown runtime value to TimeInput | Sync | Does not parse strings |
toInstant() | Resolve a value as an absolute instant | Sync | Plain values require timeZone |
inTimeZone() | Project a value to a zone | Sync | Preserves instant, changes wall-clock fields |
shift() / difference() | Timezone-aware arithmetic | Sync | Calendar operations require a timezone |
isBefore() / isAfter() / isSame() | Compare absolute values or calendar units | Sync | Plain values require timeZone |
contains() / clamp() | Compare normalized ranges | Sync | Bounds normalize automatically |
startOf() / endOf() | Resolve calendar boundaries | Sync | Weeks default to Monday |
dateRange() / recurrence() | Generate lazy zoned sequences | Sync | Steps and intervals must advance |
classifyExpiry() | Classify fixed elapsed-time thresholds | Sync | Months and years are rejected |
format() family | Localized and machine formatting | Sync | Use timeZone, not tz |
Package Entry Point
| Import | Purpose |
|---|---|
@vielzeug/tempo | Tempo utilities, errors, types, and shared Temporal namespace |
Core Functions
parse(input, { as })
parse(input: string, options: { as: 'instant' }): Temporal.Instant;
parse(input: string, options: { as: 'zonedDateTime' }): Temporal.ZonedDateTime;
parse(input: string, options: { as: 'plainDateTime' }): Temporal.PlainDateTime;
parse(input: string, options: { as: 'plainDate' }): Temporal.PlainDate;Parses an ISO 8601 string as the requested temporal kind.
Parameters
| Parameter | Type | Description |
|---|---|---|
input | string | ISO 8601 input |
options.as | ParseAs | Required result kind |
Returns: Requested Temporal value.
Example:
import { parse } from '@vielzeug/tempo';
const instant = parse('2026-03-21T10:15:30Z', { as: 'instant' });isValid(value)
isValid(value: unknown): value is TimeInput;Returns whether value is a Tempo-supported Temporal value. It does not parse ISO strings.
Example:
import { isValid, parse } from '@vielzeug/tempo';
const value: unknown = parse('2026-03-21T10:15:30Z', { as: 'instant' });
const valid = isValid(value); // truenow({ timeZone }) / nowInstant()
now(options: { timeZone: string }): Temporal.ZonedDateTime;
nowInstant(): Temporal.Instant;Returns current zoned or absolute time.
Example:
import { now, nowInstant } from '@vielzeug/tempo';
now({ timeZone: 'Europe/Berlin' });
nowInstant();toInstant(input, options?) / inTimeZone(input, timeZone)
toInstant(input: AbsoluteTime): Temporal.Instant;
toInstant(input: WallTime, options: { timeZone: string } & DisambiguationOptions): Temporal.Instant;
inTimeZone(input: TimeInput, timeZone: string): Temporal.ZonedDateTime;toInstant() resolves wall-clock values. inTimeZone() projects a value into a requested timezone.
Example:
import { inTimeZone, parse, toInstant } from '@vielzeug/tempo';
const local = parse('2026-11-01T01:30:00', { as: 'plainDateTime' });
const instant = toInstant(local, { disambiguation: 'later', timeZone: 'America/New_York' });
inTimeZone(instant, 'Europe/Berlin');Arithmetic and Comparison
shift(input, duration, options?)
shift(input: Temporal.ZonedDateTime, duration: Temporal.DurationLike, options?: ShiftOptions): Temporal.ZonedDateTime;
shift(input: Exclude<TimeInput, Temporal.ZonedDateTime>, duration: Temporal.DurationLike, options: ShiftOptions & { timeZone: string }): Temporal.ZonedDateTime;Adds a duration through zoned calendar rules.
Returns: Temporal.ZonedDateTime.
difference({ start, end, ...options })
difference(input: DifferenceInput): Temporal.Duration;Returns the signed duration from start to end with optional Temporal rounding.
Returns: Temporal.Duration.
isBefore() / isAfter() / isSame()
isBefore(a: TimeInput, b: TimeInput, options?: CompareOptions): boolean;
isAfter(a: TimeInput, b: TimeInput, options?: CompareOptions): boolean;
isSame(a: TimeInput, b: TimeInput, options?: CompareOptions): boolean;Compare absolute instants or their containing calendar units.
Returns: A boolean comparison result.
contains({ value, start, end, ...options }) / clamp({ value, start, end, ...options })
contains(input: ContainsInput): boolean;
clamp(input: ClampInput & { value: Temporal.ZonedDateTime }): Temporal.ZonedDateTime;
clamp(input: ClampInput): Temporal.Instant;Normalizes reversed bounds, then checks or constrains the value.
Returns: contains() returns boolean; clamp() returns the normalized value or nearest bound.
startOf() / endOf()
startOf(input: TimeInput, unit: BoundaryUnit, options?: BoundaryOptions): Temporal.ZonedDateTime;
endOf(input: TimeInput, unit: BoundaryUnit, options?: BoundaryOptions): Temporal.ZonedDateTime;Returns the first or last nanosecond of a timezone-aware calendar unit.
Returns: Temporal.ZonedDateTime.
Calendar Sequences
dateRange(start, end, step, options?)
dateRange(start: TimeInput, end: TimeInput, step: Temporal.DurationLike, options?: TimeZoneOptions): Generator<Temporal.ZonedDateTime>;Returns an inclusive lazy zoned range and rejects steps that do not advance time.
Returns: Generator<Temporal.ZonedDateTime>.
recurrence(start, rule, options?)
recurrence(start: TimeInput, rule: RecurrenceRule, options?: TimeZoneOptions): Generator<Temporal.ZonedDateTime>;Returns a count- or date-limited lazy recurrence. Intervals must be positive safe integers; counts must be non-negative safe integers.
Returns: Generator<Temporal.ZonedDateTime>.
Formatting
format(input, options?)
format(input: TimeInput, options?: FormatOptions): string;Formats a value through Intl.DateTimeFormat.
Example:
import { format, parse } from '@vielzeug/tempo';
format(parse('2026-03-21T10:15:30Z', { as: 'instant' }), {
locale: 'en-GB',
pattern: 'short',
timeZone: 'UTC',
});formatInstant() / formatZoned() / formatRelative() / formatDuration()
formatInstant(input: TimeInput, options?: TimeZoneOptions): string;
formatZoned(input: TimeInput, options?: TimeZoneOptions): string;
formatRelative(input: RelativeTimeInput, options?: RelativeFormatOptions): string;
formatDuration(input: string | Temporal.DurationLike, options?: DurationFormatOptions): string;formatInstant() produces UTC transport text (timeZone needed for wall-time input, ignored for Instant). formatZoned() produces zoned ISO text (timeZone required for non-ZonedDateTime input). formatRelative() uses fixed units for short spans and complete calendar months or years in the requested or inferred timezone. Zoned inputs with different zones require options.timeZone. formatDuration() falls back to English when Intl.DurationFormat is unavailable.
formatParts() / formatRange() / formatRangeParts()
formatParts(input: TimeInput, options?: FormatOptions): Intl.DateTimeFormatPart[];
formatRange(start: TimeInput, end: TimeInput, options?: FormatOptions): string;
formatRangeParts(
start: TimeInput,
end: TimeInput,
options?: FormatOptions,
): ReturnType<Intl.DateTimeFormat['formatRangeToParts']>;Return Intl parts or localized range strings using FormatOptions.
parseDuration() / humanize()
parseDuration(input: string | Temporal.DurationLike): Temporal.Duration;
humanize(diff: TimeDiffResult, options?: { locale?: Intl.LocalesArgument }): string;humanize() localizes numbers only. Unit names remain English.
Classification
classifyExpiry({ value, thresholds, relativeTo?, timeZone? })
classifyExpiry<K extends string>(input: ClassifyExpiryInput<K>): K | null;Classifies an expiry against fixed elapsed-time thresholds in milliseconds or larger units. Months and years throw TempoInvalidInputError.
timeDiff(a, b?, options?)
timeDiff(a: TimeInput, b?: TimeInput, options?: TimeZoneOptions): TimeDiffResult;Returns absolute calendar difference in its largest meaningful unit. Uses Temporal's calendar-aware .since() for correct month and year handling.
Types
type AbsoluteTime = Temporal.Instant | Temporal.ZonedDateTime;
type WallTime = Temporal.PlainDate | Temporal.PlainDateTime;
type TimeInput = AbsoluteTime | WallTime;
type RelativeTimeInput = AbsoluteTime;
type ParseAs = 'instant' | 'plainDate' | 'plainDateTime' | 'zonedDateTime';
type Disambiguation = 'compatible' | 'earlier' | 'later' | 'reject';
type CalendarUnit = 'day' | 'month' | 'week' | 'year';
type BoundaryUnit = 'day' | 'hour' | 'minute' | 'month' | 'week' | 'year';
type WeekStartDay = 1 | 2 | 3 | 4 | 5 | 6 | 7;
type FormatPattern = 'date-only' | 'long' | 'medium' | 'short' | 'time-only';
type TempoUnit = 'day' | 'hour' | 'microsecond' | 'millisecond' | 'minute' | 'month' | 'nanosecond' | 'second' | 'week' | 'year';
type FixedDuration = Pick<Temporal.DurationLike, 'days' | 'hours' | 'microseconds' | 'milliseconds' | 'minutes' | 'nanoseconds' | 'seconds' | 'weeks'>;
type ExpiryThresholds<K extends string> = Record<K, FixedDuration>;
type TimeDiffUnit = Exclude<TempoUnit, 'microsecond' | 'nanosecond'>;
type TimeDiffResult = { unit: TimeDiffUnit; value: number };
interface TimeZoneOptions { timeZone?: string }
interface DisambiguationOptions { disambiguation?: Disambiguation }
interface ShiftOptions extends DisambiguationOptions, TimeZoneOptions {}
interface DifferenceInput extends DisambiguationOptions, TimeZoneOptions {
end: TimeInput;
largestUnit?: Temporal.DateTimeUnit;
roundingIncrement?: number;
roundingMode?: Temporal.RoundingMode;
smallestUnit?: Temporal.DateTimeUnit;
start: TimeInput;
}
interface BoundaryOptions extends TimeZoneOptions { weekStartsOn?: WeekStartDay }
interface CompareOptions extends TimeZoneOptions { unit?: BoundaryUnit; weekStartsOn?: WeekStartDay }
interface ContainsInput extends CompareOptions { end: TimeInput; start: TimeInput; value: TimeInput }
interface ClampInput extends CompareOptions { end: TimeInput; start: TimeInput; value: TimeInput }
type FormatOptions =
| { intl: Intl.DateTimeFormatOptions; locale?: Intl.LocalesArgument; pattern?: never; timeZone?: string }
| { intl?: never; locale?: Intl.LocalesArgument; pattern?: FormatPattern; timeZone?: string };
interface RelativeFormatOptions extends TimeZoneOptions {
base?: RelativeTimeInput;
locale?: Intl.LocalesArgument;
numeric?: Intl.RelativeTimeFormatNumeric;
style?: Intl.RelativeTimeFormatStyle;
}
interface DurationFormatOptions {
locale?: Intl.LocalesArgument;
style?: 'digital' | 'long' | 'narrow' | 'short';
}
interface ClassifyExpiryInput<K extends string> extends TimeZoneOptions {
value: TimeInput;
thresholds: ExpiryThresholds<K>;
relativeTo?: Temporal.Instant;
}
type RecurrenceRule = { frequency: 'daily' | 'monthly' | 'weekly' | 'yearly'; interval?: number } &
({ count: number; until?: TimeInput } | { count?: number; until: TimeInput });Errors
| Error | Trigger | Notable properties |
|---|---|---|
TempoError | Base Tempo error | instanceof TempoError narrows every subtype |
TempoInvalidInputError | Invalid parse, duration, or fixed-threshold input | Extends TempoError |
TempoInvalidTzError | Invalid IANA zone or offset | Extends TempoError |
TempoMissingTzError | Wall time without required timeZone | Extends TempoError |
TempoUnsupportedInputError | Non-Temporal input passed to conversion | Extends TempoError |
Every Tempo error thrown from parse(), parseDuration(), and timezone validation carries the original Temporal error on its cause property (ES2022). Inspect err.cause for root-cause details when debugging.