Basic Usage
Parse ISO input with a declared target. Convert plain values with timeZone before treating them as an instant.
import { format, inTimeZone, parse, shift, toInstant } from '@vielzeug/tempo';
const local = parse('2026-03-21T10:15:30', { as: 'plainDateTime' });
const instant = toInstant(local, { timeZone: 'America/New_York' });
const reminder = shift(instant, { minutes: -15 }, { timeZone: 'America/New_York' });
format(inTimeZone(reminder, 'America/New_York'), { locale: 'en-US', pattern: 'short' });Parse ISO Values
Choose the value your boundary actually represents. Tempo does not auto-detect ISO strings.
import { parse } from '@vielzeug/tempo';
const occurredAt = parse('2026-03-21T10:15:30Z', { as: 'instant' });
const meeting = parse('2026-03-21T10:15:30+01:00[Europe/Berlin]', { as: 'zonedDateTime' });
const localStart = parse('2026-03-21T10:15:30', { as: 'plainDateTime' });
const birthday = parse('2026-03-21', { as: 'plainDate' });Convert Timezones
Use inTimeZone() to project an absolute value. Use toInstant() only when resolving a wall-clock value.
import { inTimeZone, parse, toInstant } from '@vielzeug/tempo';
const local = parse('2026-11-01T01:30:00', { as: 'plainDateTime' });
const firstOccurrence = toInstant(local, {
disambiguation: 'earlier',
timeZone: 'America/New_York',
});
const berlin = inTimeZone(firstOccurrence, 'Europe/Berlin');Calculate and Compare
Use object inputs for operations with multiple time values.
import { clamp, contains, difference, parse } from '@vielzeug/tempo';
const start = parse('2026-03-21T10:00:00Z', { as: 'instant' });
const end = parse('2026-03-21T12:00:00Z', { as: 'instant' });
const value = parse('2026-03-21T13:00:00Z', { as: 'instant' });
const duration = difference({ end, largestUnit: 'hour', start });
const isScheduled = contains({ end, start, value });
const bounded = clamp({ end, start, value });Classify Expiry
Use fixed elapsed-time thresholds in milliseconds or larger units. Handle null as the unclassified state instead of adding a far-future catch-all.
import { classifyExpiry, parse } from '@vielzeug/tempo';
const status = classifyExpiry({
relativeTo: parse('2026-06-01T00:00:00Z', { as: 'instant' }),
thresholds: {
expired: { days: 0 },
critical: { days: 3 },
warning: { days: 14 },
},
value: parse('2026-06-04T00:00:00Z', { as: 'instant' }),
});
const label = status ?? 'safe';Format Values
Use format() for UI, formatInstant() for transport, and formatZoned() for a zoned ISO string.
import { format, formatInstant, formatRelative, formatZoned, parse } from '@vielzeug/tempo';
const instant = parse('2026-03-21T10:15:30Z', { as: 'instant' });
format(instant, { locale: 'en-GB', pattern: 'short', timeZone: 'UTC' });
formatInstant(instant);
formatZoned(instant, { timeZone: 'Europe/Berlin' });
formatRelative(instant, { base: parse('2026-03-21T09:15:30Z', { as: 'instant' }) });Generate Calendar Sequences
Use zoned inputs for date sequences so the timezone is inferred.
import { dateRange, parse, recurrence } from '@vielzeug/tempo';
const start = parse('2026-03-01T00:00:00[UTC]', { as: 'zonedDateTime' });
const end = parse('2026-03-31T00:00:00[UTC]', { as: 'zonedDateTime' });
const days = [...dateRange(start, end, { days: 1 })];
const meetings = [...recurrence(start, { count: 4, frequency: 'weekly' })];Testing
Pin the reference instant for deterministic expiry tests.
import { classifyExpiry, parse } from '@vielzeug/tempo';
const relativeTo = parse('2026-06-01T00:00:00Z', { as: 'instant' });
const value = parse('2026-05-31T00:00:00Z', { as: 'instant' });
classifyExpiry({ relativeTo, thresholds: { expired: { days: 0 } }, value });Framework Integration
Pass ISO strings through component props. Parse and format at the rendering boundary.
import { format, parse } from '@vielzeug/tempo';
const label = format(parse(iso, { as: 'instant' }), { locale: 'en-US', pattern: 'medium', timeZone: 'UTC' });<script setup lang="ts">
import { format, parse } from '@vielzeug/tempo';
const props = defineProps<{ iso: string }>();
const label = format(parse(props.iso, { as: 'instant' }), { locale: 'en-US', pattern: 'medium', timeZone: 'UTC' });
</script><script lang="ts">
import { format, parse } from '@vielzeug/tempo';
export let iso: string;
$: label = format(parse(iso, { as: 'instant' }), { locale: 'en-US', pattern: 'medium', timeZone: 'UTC' });
</script>Working with Other Vielzeug Libraries
With Rune
Write stable UTC timestamps to structured logs.
import { formatInstant, nowInstant } from '@vielzeug/tempo';
logger.info({ timestamp: formatInstant(nowInstant()) }, 'server started');With Vault
Calculate an explicit instant before storing an expiring record.
import { now, shift } from '@vielzeug/tempo';
const expiresAt = shift(now({ timeZone: 'UTC' }), { minutes: 30 }).toInstant();Best Practices
- Parse each string with its actual temporal meaning.
- Pass
timeZonewhen converting a plain date or plain date-time. - Use
disambiguationfor DST overlap and gap handling. - Pass named fields to
difference(),contains(),clamp(), andclassifyExpiry(). - Use fixed duration units for expiry thresholds.
- Store instants for transport and database values.
- Use
formatInstant()for machine output andformat()for user-facing text.