datetime/billing_cycle_utils library

Monthly billing-anniversary math with end-of-month clamping — roadmap #609.

Answers "when does the subscription bill this month?", "what is the next billing date on/after some instant?", and "which [start, end) cycle is a given day inside?" for a fixed monthly anchor day (1..31).

The hard case is an anchor past a short month's length: anchor 31 cannot bill on February 31. The rule here is LAST-DAY CLAMPING — the anchor is clamped down to the month's final day (Feb -> 28 or 29, April -> 30), so the bill always lands on a real date and never silently rolls into the next month the way DateTime(2024, 2, 31) would. All values are local date-only (the time component is dropped); callers working in a single zone get stable results.

Functions

billingDateInMonth(int year, int month, int anchorDay) DateTime
The billing date for anchorDay within month of year, with the anchor CLAMPED to the month's length so it is always a real date.
billingSchedule(DateTime start, int anchorDay, int count) List<DateTime>
count consecutive monthly billing dates beginning with the next billing date on/after start, each clamped to its month's length.
currentCycle(DateTime on, int anchorDay) → ({DateTime end, DateTime start})
The half-open [start, end) billing cycle that contains on for anchorDay.
nextBillingDate(DateTime from, int anchorDay) DateTime
The next billing date strictly on or after the date part of from for anchorDay.