Functional Weave
Code in Rust

dates.recurrence@1.0.0

README.md

1,855 bytes · view raw

# dates.recurrence

The next `count` dates of a schedule, from a start date you pass. It never
reads the clock: "the next three payment dates" is a question about a date,
and the date is an argument.

The rule kinds:

- `monthly-on-day`: day `day` of every `interval`-th month, clamped to the
  month's last day. Monthly on the 31st is 31 Jan, 28 Feb, 31 Mar, 30 Apr: each
  date is clamped from the rule's day, never from the previous date, so a
  short February does not drag every later month to the 28th. That drift is
  the classic bug of adding one month to the last date.
- `last-weekday-of-month`: the last `weekday` (ISO, 1 = Monday, 5 = Friday) of
  every `interval`-th month: the last Friday is payday in many UK payrolls.
- `every-days` / `every-weeks`: the start date, then every `interval` days or
  weeks after it.
- `yearly`: the start date's month and day every `interval` years, computed as
  start + 12k months with dates.add-months, so a 29 February anniversary is 28
  February in ordinary years and 29 February again in leap years.

Where the schedule starts. For every-days, every-weeks and yearly, the start
date is the first occurrence. For the two monthly kinds the start date is a
lower bound: the first occurrence is the first matching date on or after it
(in the start month if it has not passed yet, otherwise the next month), and
the interval counts from that month. Quarterly on the 15th from 20 January is
15 February, 15 May, 15 August.

Dates are returned ascending, and count 0 gives an empty list. A rule that
sets a field its kind does not use (a weekday on a monthly-on-day rule) is an
error rather than silently ignored, since it almost always means the wrong
kind was chosen. A schedule that would run past 9999-12-31 is an error.
Business-day adjustment of the dates is a separate step
(dates.add-business-days).