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>
80 lines
3.1 KiB
Python
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')}")
|