3.7 KiB
recurrence-helpers
Purpose
recurrence-helpers provides the internal constants and low-level date arithmetic utilities shared across the recurrence sub-modules of @suaveplan/datetime. It contains the frequency/day-name mappings used by the RRULE parser and formatter, and the addDays, addMonths, addYears, daysInMonth, toRRuleDate, and fromRRuleDate helpers used by the candidate generators.
This module is an internal implementation detail. All public API is exposed through recurrence.ts. Direct imports are supported but not part of the documented public surface.
Features
FREQ_MAP— maps RRULEFREQstrings toFrequencyvaluesFREQ_RRULE— mapsFrequencyvalues back to RRULE stringsDAY_NAME/DAY_INDEX— bidirectional weekday ↔ abbreviation mapstoRRuleDate— format aDateasYYYYMMDDfromRRuleDate— parseYYYYMMDD(orYYYYMMDDTHHmmssZ) into aDatedaysInMonth— days in a 1-indexed month (local copy for recurrence module independence)addDays/addMonths/addYears— safe date arithmetic with month-end clamping
Usage: Basic Example
import { toRRuleDate, fromRRuleDate, addDays } from "./recurrence-helpers.js";
const d = new Date(2024, 0, 31);
console.log(toRRuleDate(d)); // "20240131"
const parsed = fromRRuleDate("20241231");
console.log(parsed.getMonth()); // 11 (December)
const next = addDays(d, 7);
console.log(next.getDate()); // 7 (Feb 7 2024)
Usage: Advanced Example
import { addMonths, addYears, daysInMonth } from "./recurrence-helpers.js";
// Month-end clamping: Jan 31 + 1 month = Feb 29 (leap year 2024)
const jan31 = new Date(2024, 0, 31);
const feb = addMonths(jan31, 1);
console.log(feb.getDate()); // 29
// Year addition uses addMonths internally
const twoYearsLater = addYears(jan31, 2);
console.log(twoYearsLater.getFullYear()); // 2026
// Days in February 2024 (leap year)
console.log(daysInMonth(2024, 2)); // 29
API Reference
FREQ_MAP: Record<string, Frequency>
Maps uppercase RRULE frequency strings ("DAILY", "WEEKLY", "MONTHLY", "YEARLY") to their Frequency type values.
FREQ_RRULE: Record<Frequency, string>
Inverse of FREQ_MAP. Maps Frequency type values back to uppercase RRULE strings.
DAY_NAME: Record<Weekday, string>
Maps weekday indices (0–6) to two-letter RRULE abbreviations ("SU" … "SA").
DAY_INDEX: Record<string, Weekday>
Maps RRULE day abbreviations to weekday indices.
toRRuleDate(d: Date): string
Format d as YYYYMMDD with zero-padded year, month, and day.
fromRRuleDate(s: string): Date
Parse s (either YYYYMMDD or YYYYMMDDTHHmmssZ) into a local-midnight Date. The time and timezone suffix are stripped.
daysInMonth(year: number, month: number): number
Returns the number of days in the given 1-indexed month. Mirrors the implementation in calendar-core to keep this module self-contained.
addDays(d: Date, n: number): Date
Add n days to d using millisecond arithmetic.
addMonths(d: Date, n: number): Date
Add n months with day clamping to the last valid day of the resulting month.
addYears(d: Date, n: number): Date
Add n years by delegating to addMonths(d, n * 12).
Implementation Notes
addMonths is the source of truth for month-end clamping in the recurrence engine. It handles month overflow (month > 12 rolls the year forward) and clamps the day to daysInMonth(year, month) using Math.min, ensuring that e.g. Jan 31 + 1 month → Feb 28/29 rather than Mar 2/3.
daysInMonth is intentionally duplicated from calendar-core rather than imported, to keep recurrence-helpers self-contained and free of cross-category imports.