""" Business day calculations for the standard-vs-expedited delay system. Used by canada_crtc.py and formation_worker.py to insert 3-business-day delays between pipeline steps for standard service. Expedited orders skip these delays. Skips weekends + US/Canada federal holidays. """ from __future__ import annotations from datetime import datetime, timedelta, date # US + Canada federal holidays (2026 + 2027 — extend as needed) HOLIDAYS: set[date] = { # 2026 date(2026, 1, 1), # New Year's Day (US + CA) date(2026, 1, 19), # MLK Day (US) date(2026, 2, 16), # Presidents Day (US) / Family Day (CA) date(2026, 5, 18), # Victoria Day (CA) date(2026, 5, 25), # Memorial Day (US) date(2026, 7, 1), # Canada Day (CA) date(2026, 7, 3), # Independence Day observed (US) date(2026, 9, 7), # Labor Day (US + CA) date(2026, 10, 12), # Columbus Day (US) / Thanksgiving (CA) date(2026, 11, 11), # Veterans Day / Remembrance Day date(2026, 11, 26), # Thanksgiving (US) date(2026, 12, 25), # Christmas (US + CA) date(2026, 12, 26), # Boxing Day (CA) # 2027 date(2027, 1, 1), # New Year's Day date(2027, 1, 18), # MLK Day date(2027, 2, 15), # Presidents Day / Family Day date(2027, 5, 24), # Victoria Day date(2027, 5, 31), # Memorial Day date(2027, 7, 1), # Canada Day date(2027, 7, 5), # Independence Day observed date(2027, 9, 6), # Labor Day date(2027, 10, 11), # Columbus Day / Thanksgiving (CA) date(2027, 11, 11), # Veterans Day / Remembrance Day date(2027, 11, 25), # Thanksgiving (US) date(2027, 12, 24), # Christmas Eve (observed) date(2027, 12, 27), # Boxing Day (observed) } def is_business_day(d: date) -> bool: """A weekday that isn't a holiday.""" return d.weekday() < 5 and d not in HOLIDAYS def business_days_from_now(n: int, base: datetime | None = None) -> datetime: """Return a datetime n business days from base (default: now). Skips weekends + holidays. n=3 starting Friday → next Wednesday. """ if n <= 0: return base or datetime.now() cur = base or datetime.now() added = 0 while added < n: cur = cur + timedelta(days=1) if is_business_day(cur.date()): added += 1 return cur if __name__ == "__main__": # Quick smoke test from datetime import datetime as dt cases = [ (dt(2026, 4, 13), 3, "Mon → Thu"), # Mon Apr 13 + 3 = Thu Apr 16 (dt(2026, 4, 17), 3, "Fri → Wed"), # Fri Apr 17 + 3 = Wed Apr 22 (skip Sat/Sun) (dt(2026, 5, 22), 3, "Fri → Wed (Victoria Day)"), # Fri May 22 + 3 = Wed May 27 (skip Mon May 25 = Memorial Day, but May 18 was Victoria — wait, this skips Memorial Day) (dt(2026, 12, 24), 3, "Wed → Wed (Christmas + Boxing Day)"), # skip Fri 25, Sat 26 (Boxing observed), Sun 27 → next Mon 28, Tue 29, Wed 30 ] for base, n, label in cases: result = business_days_from_now(n, base) print(f" {label}: {base.strftime('%a %Y-%m-%d')} + {n} biz days = {result.strftime('%a %Y-%m-%d')}")