Initial Commit
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# 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 RRULE `FREQ` strings to `Frequency` values
|
||||
- `FREQ_RRULE` — maps `Frequency` values back to RRULE strings
|
||||
- `DAY_NAME` / `DAY_INDEX` — bidirectional weekday ↔ abbreviation maps
|
||||
- `toRRuleDate` — format a `Date` as `YYYYMMDD`
|
||||
- `fromRRuleDate` — parse `YYYYMMDD` (or `YYYYMMDDTHHmmssZ`) into a `Date`
|
||||
- `daysInMonth` — 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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.
|
||||
Reference in New Issue
Block a user