File size: 2,214 Bytes
470bcea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
"""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")