editorial-system / intake /time_fmt.py
ICSAC's picture
Public-flip preparation
470bcea
"""ET display formatting for ICSAC submission intake timestamps.
Storage policy: every timestamp on disk (submission.json, state.json,
audit-log.jsonl, review markdown frontmatter) stays as UTC ISO 8601
("YYYY-MM-DDTHH:MM:SSZ"). This is the machine-canonical form β€” sortable
lexicographically, parseable by every JSON tooling, immune to DST
ambiguity.
Display policy: when a timestamp surfaces to a human (state-page JSON
response, worker stdout, Telegram messages, intake-failure emails), it
is rendered as "MM/DD/YY HH:MM:SS EDT|EST" via to_et_display(). The
zone abbreviation tracks DST automatically β€” no assumptions about which
zone rule is active when the code runs.
Shared between intake_server.py and submission_worker.py so both sides
emit identical formats.
"""
from __future__ import annotations
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
ET = ZoneInfo("America/New_York")
DISPLAY_FMT = "%m/%d/%y %H:%M:%S %Z"
def to_et_display(iso_utc_str: str | None) -> str | None:
"""Convert a UTC ISO 8601 string to 'MM/DD/YY HH:MM:SS EDT|EST'.
Pass-through for None / empty input. Pass-through unchanged if the
string isn't parseable (defense against malformed legacy entries β€”
we'd rather show the raw value than crash a state response).
"""
if not iso_utc_str:
return iso_utc_str
s = iso_utc_str.replace("Z", "+00:00")
try:
dt_utc = datetime.fromisoformat(s)
except (ValueError, TypeError):
return iso_utc_str
if dt_utc.tzinfo is None:
dt_utc = dt_utc.replace(tzinfo=timezone.utc)
return dt_utc.astimezone(ET).strftime(DISPLAY_FMT)
def now_et_display() -> str:
"""Right-now in ET display format. Used for log-prefix in worker
stdout β€” no UTC-to-ET round trip needed."""
return datetime.now(ET).strftime(DISPLAY_FMT)
def now_utc_iso() -> str:
"""Canonical now-string for storage. Mirrors the inline helpers
that already exist in intake_server.py / submission_worker.py /
apply_decision.py β€” keeping a single shared definition here so
storage and display formats live in the same module."""
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")