Version v3.0.0 Size 3.8 KB gzip
Why Tempo?
Date/time bugs come from treating an instant and a wall-clock value as interchangeable. Tempo requires an explicit parse target and requires timeZone whenever a wall-clock value becomes an instant.
ts
// Before
const reminder = new Date(meeting.getTime() - 15 * 60_000);
// After
import { parse, shift } from '@vielzeug/tempo';
const localMeeting = parse('2026-03-21T10:30:00', { as: 'plainDateTime' });
const reminder = shift(localMeeting, { minutes: -15 }, { timeZone: 'America/New_York' });| Feature | Tempo | date-fns | Native Date |
|---|---|---|---|
| Bundle size | 3.8 KB | ~10 kB | 0 kB |
| Zero dependencies | @js-temporal/polyfill | ||
| Explicit wall-time conversion | Manual | ||
| DST-safe arithmetic | Manual | Manual | |
| Localized formatting | Intl |
Use Tempo when you need Temporal values, explicit timezone rules, and DST-safe operations.
Consider native Date when your data is only elapsed milliseconds and you do not need calendar or timezone behavior.
Installation
sh
pnpm add @vielzeug/temposh
npm install @vielzeug/temposh
yarn add @vielzeug/tempoQuick Start
Parse a wall-clock input explicitly, attach its timezone, then format it for a user.
ts
import { format, parse, shift } from '@vielzeug/tempo';
const localMeeting = parse('2026-03-21T10:30:00', { as: 'plainDateTime' });
const reminder = shift(localMeeting, { minutes: -15 }, { timeZone: 'America/New_York' });
const text = format(reminder, {
locale: 'en-US',
pattern: 'short',
});Features
parse()— Requires an explicit ISO target: instant, zoned date-time, plain date-time, or plain date.toInstant()/inTimeZone()— Convert wall-clock and absolute values with explicit timezone semantics.shift()/difference()— Apply DST-safe arithmetic across instant, zoned, and wall-clock inputs.contains()/clamp()— Compare normalized ranges with optional calendar-unit precision.startOf()/endOf()— Resolve timezone-aware calendar boundaries, including configurable week starts.dateRange()/recurrence()— Generate validated, lazy zoned calendar sequences.format()/formatRelative()/formatDuration()— Render localized values throughIntl, including calendar-aware relative months and years.classifyExpiry()— Classify fixed elapsed-time thresholds without month or year approximation.