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.