[ {"name": "monthly on the 31st clamps each month from the 31st: March is back to 31, not stuck at 28", "args": [{"kind": "monthly-on-day", "interval": 1, "day": 31, "weekday": null}, "2026-01-01", 5], "expect": ["2026-01-31", "2026-02-28", "2026-03-31", "2026-04-30", "2026-05-31"]}, {"name": "monthly on the 31st in a leap year gives 29 February", "args": [{"kind": "monthly-on-day", "interval": 1, "day": 31, "weekday": null}, "2024-01-15", 3], "expect": ["2024-01-31", "2024-02-29", "2024-03-31"]}, {"name": "the start date counts when it is itself an occurrence", "args": [{"kind": "monthly-on-day", "interval": 1, "day": 15, "weekday": null}, "2026-01-15", 2], "expect": ["2026-01-15", "2026-02-15"]}, {"name": "a day already passed in the start month moves the first occurrence to the next month", "args": [{"kind": "monthly-on-day", "interval": 1, "day": 15, "weekday": null}, "2026-01-20", 3], "expect": ["2026-02-15", "2026-03-15", "2026-04-15"]}, {"name": "quarterly on the 15th counts its interval from the first occurrence", "args": [{"kind": "monthly-on-day", "interval": 3, "day": 15, "weekday": null}, "2026-01-20", 3], "expect": ["2026-02-15", "2026-05-15", "2026-08-15"]}, {"name": "monthly on the 30th starting 28 February: the clamped 28th is on or after the start", "args": [{"kind": "monthly-on-day", "interval": 1, "day": 30, "weekday": null}, "2026-02-28", 3], "expect": ["2026-02-28", "2026-03-30", "2026-04-30"]}, {"name": "monthly on the 1st across a year end", "args": [{"kind": "monthly-on-day", "interval": 1, "day": 1, "weekday": null}, "2026-11-02", 3], "expect": ["2026-12-01", "2027-01-01", "2027-02-01"]}, {"name": "last Friday of each month from 1 September 2026", "args": [{"kind": "last-weekday-of-month", "interval": 1, "day": null, "weekday": 5}, "2026-09-01", 4], "expect": ["2026-09-25", "2026-10-30", "2026-11-27", "2026-12-25"]}, {"name": "last Friday, starting after September's has passed", "args": [{"kind": "last-weekday-of-month", "interval": 1, "day": null, "weekday": 5}, "2026-09-26", 2], "expect": ["2026-10-30", "2026-11-27"]}, {"name": "last Sunday when the month ends on a Sunday is the last day itself", "args": [{"kind": "last-weekday-of-month", "interval": 1, "day": null, "weekday": 7}, "2026-05-01", 3], "expect": ["2026-05-31", "2026-06-28", "2026-07-26"]}, {"name": "last Friday every three months", "args": [{"kind": "last-weekday-of-month", "interval": 3, "day": null, "weekday": 5}, "2026-09-26", 3], "expect": ["2026-10-30", "2027-01-29", "2027-04-30"]}, {"name": "every two weeks from a Wednesday", "args": [{"kind": "every-weeks", "interval": 2, "day": null, "weekday": null}, "2026-09-23", 3], "expect": ["2026-09-23", "2026-10-07", "2026-10-21"]}, {"name": "every ten days across the end of February", "args": [{"kind": "every-days", "interval": 10, "day": null, "weekday": null}, "2026-02-25", 3], "expect": ["2026-02-25", "2026-03-07", "2026-03-17"]}, {"name": "yearly from 29 February: 28 February in ordinary years, 29 February again in 2028", "args": [{"kind": "yearly", "interval": 1, "day": null, "weekday": null}, "2024-02-29", 5], "expect": ["2024-02-29", "2025-02-28", "2026-02-28", "2027-02-28", "2028-02-29"]}, {"name": "every two years", "args": [{"kind": "yearly", "interval": 2, "day": null, "weekday": null}, "2026-09-23", 3], "expect": ["2026-09-23", "2028-09-23", "2030-09-23"]}, {"name": "a count of zero is an empty schedule", "args": [{"kind": "monthly-on-day", "interval": 1, "day": 31, "weekday": null}, "2026-01-01", 0], "expect": []}, {"name": "a negative count is an error", "args": [{"kind": "every-days", "interval": 1, "day": null, "weekday": null}, "2026-01-01", -1], "expectError": "count must be a non-negative integer"}, {"name": "an interval of zero is an error", "args": [{"kind": "every-weeks", "interval": 0, "day": null, "weekday": null}, "2026-01-01", 3], "expectError": "interval must be a positive integer"}, {"name": "monthly-on-day without a day is an error", "args": [{"kind": "monthly-on-day", "interval": 1, "day": null, "weekday": null}, "2026-01-01", 3], "expectError": "day must be 1-31 for monthly-on-day"}, {"name": "day 32 is an error", "args": [{"kind": "monthly-on-day", "interval": 1, "day": 32, "weekday": null}, "2026-01-01", 3], "expectError": "day must be 1-31 for monthly-on-day"}, {"name": "a weekday on a monthly-on-day rule is an error, not ignored", "args": [{"kind": "monthly-on-day", "interval": 1, "day": 15, "weekday": 5}, "2026-01-01", 3], "expectError": "weekday does not apply to monthly-on-day"}, {"name": "weekday 8 is an error", "args": [{"kind": "last-weekday-of-month", "interval": 1, "day": null, "weekday": 8}, "2026-01-01", 3], "expectError": "weekday must be 1-7 for last-weekday-of-month"}, {"name": "a day on an every-days rule is an error", "args": [{"kind": "every-days", "interval": 1, "day": 3, "weekday": null}, "2026-01-01", 3], "expectError": "day does not apply to every-days"}, {"name": "an unknown kind is an error", "args": [{"kind": "fortnightly", "interval": 1, "day": null, "weekday": null}, "2026-01-01", 3], "expectError": "unknown recurrence kind \"fortnightly\""}, {"name": "a schedule running past 9999 is an error", "args": [{"kind": "every-days", "interval": 1, "day": null, "weekday": null}, "9999-12-30", 3], "expectError": "outside the supported range"} ]