Date Ranges and Recurrence
Problem
Building calendar UIs, generating report periods, or scheduling recurring events requires iterating over sequences of dates. Manual while loops with date arithmetic are verbose and error-prone across DST boundaries.
Solution
Use dateRange() for step-based sequences between two dates. Use recurrence() for calendar-rule-based repetition (daily/weekly/monthly/yearly with optional interval, count, and until boundary). Both return lazy generators — safe for large ranges.
Calendar Grid (Month View)
ts
import { dateRange, parseZoned } from '@vielzeug/tempo';
const start = parseZoned('2026-03-01T00:00:00[America/New_York]');
const end = parseZoned('2026-03-31T00:00:00[America/New_York]');
// ZonedDateTime inputs — tz inferred, no options needed
const days = [...dateRange(start, end, { days: 1 })];
// [Mar 1, Mar 2, ..., Mar 31] — all in America/New_YorkLazy Iteration with Early Exit
ts
import { dateRange, parseZoned } from '@vielzeug/tempo';
const rangeStart = parseZoned('2026-01-01T00:00:00[UTC]');
const rangeEnd = parseZoned('2026-12-31T00:00:00[UTC]');
// Process only until a condition is met — the generator is never fully materialized
for (const day of dateRange(rangeStart, rangeEnd, { days: 1 })) {
if (isHoliday(day)) break;
schedule(day);
}Weekly Meetings with a Deadline
ts
import { parseZoned, recurrence } from '@vielzeug/tempo';
const meetingStart = parseZoned('2026-01-05T09:00:00[Europe/Berlin]');
const deadline = parseZoned('2026-06-30T00:00:00[Europe/Berlin]');
// ZonedDateTime start — tz inferred automatically
for (const meeting of recurrence(meetingStart, { frequency: 'weekly', until: deadline })) {
calendar.add(meeting);
}Bi-Weekly Sprint Planning (Count-Based)
ts
import { parseZoned, recurrence } from '@vielzeug/tempo';
const sprintStart = parseZoned('2026-01-05T10:00:00[UTC]');
// 6 sprints, every 2 weeks
const sprints = [...recurrence(sprintStart, { frequency: 'weekly', interval: 2, count: 6 })];
// Jan 5, Jan 19, Feb 2, Feb 16, Mar 2, Mar 16Quarterly Reports
ts
import { parseZoned, recurrence } from '@vielzeug/tempo';
const quarterStart = parseZoned('2026-01-01T00:00:00[UTC]');
const quarters = [...recurrence(quarterStart, { frequency: 'monthly', interval: 3, count: 4 })];
// Jan 1, Apr 1, Jul 1, Oct 1Plain Inputs Require tz
ts
import { dateRange, parsePlainDate, recurrence } from '@vielzeug/tempo';
// PlainDate inputs — must pass tz explicitly
const days = [...dateRange(parsePlainDate('2026-03-01'), parsePlainDate('2026-03-31'), { days: 1 }, { tz: 'UTC' })];Pitfalls
dateRange()requiresstepto advance the date forward — passing{ days: 0 }or a negative step throws aRangeError.recurrence()throws immediately at call time (not lazily) when neithercountnoruntilis provided.- When
startandendareZonedDateTimevalues in different timezones,dateRange()silently re-projectsendintostart's timezone. Passoptions.tzexplicitly to override. - Generators are lazy — spreading a large range into an array (
[...dateRange(...)]) materializes all values at once. For very large ranges, preferfor...ofwith abreak.