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)