package litigationplanner import "time" // ApplyDuration is the unified date-arithmetic helper used by every // calculator path (proceeding-tree, trigger-event, CalculateRule single- // rule). Phase 3 Slice 4 (t-paliad-185) replaced the prior split // between addDuration (proceeding-tree, no timing / working_days) and // ApplyDurationOnCalendar (Pipeline-C, full support) with this single // helper. // // Returns (raw, adjusted, didAdjust, reason): // // - raw: the date strictly implied by the rule before rollover. // - adjusted: post-rollover for calendar units. 'working_days' lands // on a working day by construction so raw == adjusted there. // - didAdjust: true iff rollover moved the date. // - reason: populated when didAdjust is true; nil otherwise. // // timing='before' negates the sign. timing='after' (or any other value // including the empty string) keeps it positive — preserves the pre- // Slice-4 behaviour for proceeding-tree rules whose Timing field is // sometimes NULL (mig 003 defaults to 'after' but legacy callers pass // r.Timing dereferenced). func ApplyDuration( base time.Time, value int, unit, timing, country, regime string, holidays HolidayCalendar, ) (raw, adjusted time.Time, didAdjust bool, reason *AdjustmentReason) { sign := 1 if timing == "before" { sign = -1 } switch unit { case "days": raw = base.AddDate(0, 0, sign*value) case "weeks": raw = base.AddDate(0, 0, sign*value*7) case "months": raw = base.AddDate(0, sign*value, 0) case "working_days": raw = AddWorkingDays(base, sign*value, country, regime, holidays) // Working-day arithmetic lands on a working day by construction // — the per-step skip loop in AddWorkingDays already passes over // weekends and holidays. No post-rollover required. return raw, raw, false, nil default: raw = base } adjusted, _, didAdjust, reason = holidays.AdjustForNonWorkingDaysWithReason(raw, country, regime) return raw, adjusted, didAdjust, reason } // AddWorkingDays advances from `from` by `n` working days, skipping // weekends and holidays applicable to the given country/regime. Negative // n walks backward. n=0 keeps the input date as-is (caller decides // whether to roll forward via AdjustForNonWorkingDays). // // Bounded by an inner 30-step skip per advance — vacation runs in our // holiday tables are < 14 consecutive days, so 30 is a safety margin. func AddWorkingDays(from time.Time, n int, country, regime string, holidays HolidayCalendar) time.Time { if n == 0 { return from } step := 1 if n < 0 { step = -1 n = -n } cur := from for i := 0; i < n; i++ { cur = cur.AddDate(0, 0, step) for j := 0; j < 30 && holidays.IsNonWorkingDay(cur, country, regime); j++ { cur = cur.AddDate(0, 0, step) } } return cur }