dodil/erp-brick-purchasing
DODIL ERP brick: purchasing
src/brick_purchasing/infrastructure/business_time.py
"""'Today' is the date in business_timezone — ONE value per bucket (shared.yaml conventions.business_timezone).
The read sits behind this one small module so it can move to the platform later without touching a use case. Until the platform holds the
single value, the dated setting `business_timezone` in pur_settings is where this brick reads it. Business time is `work.now()` (a demo
may move the clock); audit stamps use real time.
Which zone is in force is decided in BUSINESS time, not UTC: walking the dated rows in order, a row takes over once the business date in
the zone BEFORE it has reached the row's valid_from, so a zone dated tomorrow cannot be in force at once, and "today" never goes backwards
across a zone change (known bug class, wave 1).
"""
from datetime import date
from zoneinfo import ZoneInfo
from erp_platform import work
KEY = "business_timezone"
EPOCH = date(1970, 1, 1)
def _chain(settings) -> tuple:
"""(the zone in force now, the floor): the zone is chosen by walking the dated rows, a row taking over once the business date in the zone BEFORE
it has reached its valid_from; the floor is the valid_from of the last row that took over, so a move to a zone BEHIND the old one holds today
at that date until the new zone catches up (today never goes backwards)."""
now = work.now()
name = settings.value_on(KEY, EPOCH) or "Europe/London"
floor = EPOCH
for row in settings.rows(KEY):
if row["valid_from"] <= EPOCH:
continue
if row["valid_from"] <= now.astimezone(ZoneInfo(name)).date():
name, floor = row["value"] or name, row["valid_from"]
else:
break
return name, floor
def timezone_name(settings) -> str:
return _chain(settings)[0]
def today(settings) -> date:
name, floor = _chain(settings)
return max(work.now().astimezone(ZoneInfo(name)).date(), floor)