new-site/scripts/workers/business_days.py
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

80 lines
3.1 KiB
Python

"""
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')}")