Skip to content
tempo logoTempoDate & Time
Temporal-powered parsing, timezone conversion, arithmetic (DST-safe), and Intl formatting for modern TypeScript.
v0.0.14.0 KB gzip Browser · Node.js · SSR · Deno
nownowInstantparsePlainDateparsePlainDateTimeparseInstantparseZonedparseDateisValid +26 more →

Why Tempo?

Manual date handling breaks at daylight-saving boundaries, timezone edges, and DST transitions.

ts
// Before — fragile, loses timezone context
const reminder = new Date(meeting.getTime() - 15 * 60_000);

// After — DST-safe, handles transitions correctly
const reminder = shift(meeting, { minutes: -15 });
FeatureTempodate-fnsDay.jsNative Date
Bundle size4.0 KB~10 kB~3 kB0 kB
DST-safe math (Temporal)ManualManual
Timezone aware Full supportPartial
Immutable
Format presets ('short', 'medium', 'long', etc.)
Type inference Full TypeScriptPartialPartial

Use Tempo when you need reliable timezone handling, DST-safe arithmetic, and clean Temporal-based APIs without heavy dependencies.

Consider alternatives when you need extensive locale data (date-fns).

Installation

sh
pnpm add @vielzeug/tempo
sh
npm install @vielzeug/tempo
sh
yarn add @vielzeug/tempo

Quick Start

ts
import { format, formatInstant, parsePlainDateTime, shift, toInstant, toZoned } from '@vielzeug/tempo';

// Parse a wall-clock string (no timezone attached)
const localMeeting = parsePlainDateTime('2026-03-21T10:30:00');

// Convert to an absolute instant using the user's timezone
const meetingInstant = toInstant(localMeeting, { tz: 'America/New_York' });

// Project to a zoned view and subtract 15 minutes (DST-safe)
const meetingNY = toZoned(meetingInstant, { tz: 'America/New_York' });
const reminder = shift(meetingNY, { minutes: -15 });

// Format for display
const text = format(reminder, { pattern: 'short', locale: 'en-US', tz: 'America/New_York' });

// Format for APIs/logs (stable UTC instant string)
const stable = formatInstant(reminder);

No Temporal.* imports needed. Tempo re-exports Temporal and provides parseInstant, parseZoned, parsePlainDateTime, parsePlainDate, nowInstant, and now as drop-in replacements for every common Temporal constructor.

Features

  • Zero Temporal importsparseInstant(), parseZoned(), parsePlainDateTime(), parsePlainDate(), nowInstant(), now() replace every common Temporal.* constructor; import only from @vielzeug/tempo
  • DST-safe arithmeticshift() handles transitions correctly; always returns ZonedDateTime (call .toInstant() if needed)
  • Timezone conversiontoZoned(), toInstant() with full timezone support; invalid timezone strings produce descriptive [tempo] errors
  • Formatting split by intentformat() for UI (with presets and intl escape hatch), formatInstant() for UTC strings, formatZoned() for zoned strings
  • Relative and range formattingformatRelative() for UX copy, formatRange() / formatRangeParts() for localized time spans, formatParts() for custom rendering
  • Range + comparison helperswithin(), clamp(), isBefore(), isAfter(), isSame() with calendar-unit and week-start support
  • Boundary helpersstartOf() and endOf() for day/week/month/year-style snapping
  • Duration toolsdifference(), parseDuration(), formatDuration()
  • Expiry classificationexpires() for flexible threshold-based TTL bucketing; classify() for combined bucket + diff in one call; timeDiff() for structured time differences; humanize() for human-readable output
  • Recurrence generationrecurrence() for lazily generating repeating dates (daily/weekly/monthly/yearly); dateRange() for step-based date sequences; timezone inferred from ZonedDateTime inputs
  • Intl integration — formatting respects locale and calendar systems
  • Polyfilled Temporal — works in runtimes without native support via @js-temporal/polyfill
  • 4.0 KB gzipped

Documentation

See Also

  • Spell — schema validation with a similar v namespace pattern; combine with Tempo's date validators for typed form fields that accept date strings
  • Rune — structured logger; use Tempo to format timestamps consistently in log entries and audit trails