import 'frequency.dart'; /// Calculate the next due date from the current due date and interval config. /// /// For calendar-anchored intervals, [anchorDay] is the original day-of-month. /// Day-count intervals use pure arithmetic (Duration). /// Calendar-anchored intervals use month arithmetic with clamping. DateTime calculateNextDueDate({ required DateTime currentDueDate, required IntervalType intervalType, required int intervalDays, int? anchorDay, }) { switch (intervalType) { // Day-count: pure arithmetic case IntervalType.daily: return currentDueDate.add(const Duration(days: 1)); case IntervalType.everyNDays: return currentDueDate.add(Duration(days: intervalDays)); case IntervalType.weekly: return currentDueDate.add(const Duration(days: 7)); case IntervalType.biweekly: return currentDueDate.add(const Duration(days: 14)); // Calendar-anchored: month arithmetic with clamping case IntervalType.monthly: return _addMonths(currentDueDate, 1, anchorDay); case IntervalType.everyNMonths: return _addMonths(currentDueDate, intervalDays, anchorDay); case IntervalType.quarterly: return _addMonths(currentDueDate, 3, anchorDay); case IntervalType.yearly: return _addMonths(currentDueDate, 12, anchorDay); } } /// Add months with day-of-month clamping. /// [anchorDay] remembers the original day for correct clamping. DateTime _addMonths(DateTime date, int months, int? anchorDay) { final targetMonth = date.month + months; final targetYear = date.year + (targetMonth - 1) ~/ 12; final normalizedMonth = ((targetMonth - 1) % 12) + 1; final day = anchorDay ?? date.day; // Last day of target month: day 0 of next month final lastDay = DateTime(targetYear, normalizedMonth + 1, 0).day; final clampedDay = day > lastDay ? lastDay : day; return DateTime(targetYear, normalizedMonth, clampedDay); } /// Catch-up: if next due is in the past, keep adding intervals until future/today. DateTime catchUpToPresent({ required DateTime nextDue, required DateTime today, required IntervalType intervalType, required int intervalDays, int? anchorDay, }) { while (nextDue.isBefore(today)) { nextDue = calculateNextDueDate( currentDueDate: nextDue, intervalType: intervalType, intervalDays: intervalDays, anchorDay: anchorDay, ); } return nextDue; }