/** * UTC month arithmetic for leave grants. Uses `setUTCMonth` so the stored calendar-day values stay * stable regardless of runtime timezone (grant/expiry dates are pure calendar dates), and so * end-of-month/overflow behaviour (e.g. Jan 31 + 1 month) is defined by a single implementation * rather than reinvented per caller. Kept as the primitive behind `computeGrantExpirationDate`. */ export function addMonths(date: Date, months: number): Date { const result = new Date(date); result.setUTCMonth(result.getUTCMonth() + months); return result; } /** * A LeaveGrant's `expirationDate` is defined as `grantedDate + expirationMonths` (the effective * AccrualPlan's `expirationMonths`; see createAccrualPlan). The anniversary batch * (runAnniversaryLeaveGrants) writes grants with exactly this rule, and grantLeave only validates * `expirationDate > grantedDate` — it does not recompute it. A consumer resolving `expirationDate` * for a manual STATUTORY grant must therefore match this arithmetic or its grants silently diverge * from the batch (off-by-a-day expiry sweeps, FIFO windows, anniversary idempotency). This is the * shared source of truth so callers reference it instead of reimplementing month math. */ export function computeGrantExpirationDate(grantedDate: Date, expirationMonths: number): Date { return addMonths(grantedDate, expirationMonths); }