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 });| Feature | Tempo | date-fns | Day.js | Native Date |
|---|---|---|---|---|
| Bundle size | 4.0 KB | ~10 kB | ~3 kB | 0 kB |
| DST-safe math | Manual | Manual | ||
| Timezone aware | Partial | |||
| Immutable | ||||
| Format presets | 'short', 'medium', 'long', etc.) | |||
| Type inference | Partial | Partial |
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/temposh
npm install @vielzeug/temposh
yarn add @vielzeug/tempoQuick 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-exportsTemporaland providesparseInstant,parseZoned,parsePlainDateTime,parsePlainDate,nowInstant, andnowas drop-in replacements for every common Temporal constructor.
Features
- Zero Temporal imports —
parseInstant(),parseZoned(),parsePlainDateTime(),parsePlainDate(),nowInstant(),now()replace every commonTemporal.*constructor; import only from@vielzeug/tempo - DST-safe arithmetic —
shift()handles transitions correctly; always returnsZonedDateTime(call.toInstant()if needed) - Timezone conversion —
toZoned(),toInstant()with full timezone support; invalid timezone strings produce descriptive[tempo]errors - Formatting split by intent —
format()for UI (with presets andintlescape hatch),formatInstant()for UTC strings,formatZoned()for zoned strings - Relative and range formatting —
formatRelative()for UX copy,formatRange()/formatRangeParts()for localized time spans,formatParts()for custom rendering - Range + comparison helpers —
within(),clamp(),isBefore(),isAfter(),isSame()with calendar-unit and week-start support - Boundary helpers —
startOf()andendOf()for day/week/month/year-style snapping - Duration tools —
difference(),parseDuration(),formatDuration() - Expiry classification —
expires()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 generation —
recurrence()for lazily generating repeating dates (daily/weekly/monthly/yearly);dateRange()for step-based date sequences; timezone inferred fromZonedDateTimeinputs - Intl integration — formatting respects locale and calendar systems
- Polyfilled Temporal — works in runtimes without native support via
@js-temporal/polyfill - 4.0 KB gzipped