fix(tz): timezone-aware datetimes — unblock TZ=Asia/Jerusalem (#210) #368
@@ -7,7 +7,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime, timezone
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
from legal_mcp.services import db
|
from legal_mcp.services import db
|
||||||
@@ -38,7 +38,7 @@ async def log_action(
|
|||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)""",
|
VALUES ($1, $2, $3, $4, $5, $6, $7)""",
|
||||||
uuid4(), action, case_id, document_id,
|
uuid4(), action, case_id, document_id,
|
||||||
json.dumps(details or {}, ensure_ascii=False, default=str),
|
json.dumps(details or {}, ensure_ascii=False, default=str),
|
||||||
user, datetime.utcnow(),
|
user, datetime.now(timezone.utc),
|
||||||
)
|
)
|
||||||
logger.info("AUDIT: %s | case=%s | user=%s | %s", action, case_id, user,
|
logger.info("AUDIT: %s | case=%s | user=%s | %s", action, case_id, user,
|
||||||
json.dumps(details or {}, ensure_ascii=False)[:200])
|
json.dumps(details or {}, ensure_ascii=False)[:200])
|
||||||
|
|||||||
@@ -15,8 +15,14 @@ import os
|
|||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
# Display timezone (INV-UI9): timestamps surfaced to humans are rendered in
|
||||||
|
# Israel time deterministically, regardless of the process clock (the container
|
||||||
|
# may run UTC or Asia/Jerusalem). Storage stays UTC; this is display-only.
|
||||||
|
IL_TZ = ZoneInfo("Asia/Jerusalem")
|
||||||
|
|
||||||
# Placeholder strings — any of these means "not yet filled"
|
# Placeholder strings — any of these means "not yet filled"
|
||||||
CHAIR_POSITION_PLACEHOLDERS = (
|
CHAIR_POSITION_PLACEHOLDERS = (
|
||||||
"[ימולא ע\"י יו\"ר הוועדה]",
|
"[ימולא ע\"י יו\"ר הוועדה]",
|
||||||
@@ -191,7 +197,7 @@ def parse(file_path: Path) -> dict[str, Any]:
|
|||||||
date_str = date_match.group(1) if date_match else ""
|
date_str = date_match.group(1) if date_match else ""
|
||||||
|
|
||||||
stat = file_path.stat()
|
stat = file_path.stat()
|
||||||
mtime_iso = datetime.fromtimestamp(stat.st_mtime).isoformat()
|
mtime_iso = datetime.fromtimestamp(stat.st_mtime, tz=IL_TZ).isoformat()
|
||||||
|
|
||||||
result: dict[str, Any] = {
|
result: dict[str, Any] = {
|
||||||
"header": {
|
"header": {
|
||||||
@@ -354,7 +360,7 @@ def update_chair_position(
|
|||||||
"saved": True,
|
"saved": True,
|
||||||
"section_id": section_id,
|
"section_id": section_id,
|
||||||
"preview": preview,
|
"preview": preview,
|
||||||
"timestamp": datetime.now().isoformat(),
|
"timestamp": datetime.now(IL_TZ).isoformat(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,9 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
from statistics import mean
|
from statistics import mean
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -344,8 +345,17 @@ async def _run(args, ts: str) -> dict:
|
|||||||
return {"dry_run": False, "grid": grid_summary, "by_block": by_block}
|
return {"dry_run": False, "grid": grid_summary, "by_block": by_block}
|
||||||
|
|
||||||
|
|
||||||
|
IL_TZ = ZoneInfo("Asia/Jerusalem")
|
||||||
|
|
||||||
|
|
||||||
def _ts() -> str:
|
def _ts() -> str:
|
||||||
return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
"""Filename-safe report stamp in Israel time (INV-UI9: human-facing display).
|
||||||
|
|
||||||
|
This script runs on the host, which is UTC, so a process-clock stamp would
|
||||||
|
mislead the chair. We pin Asia/Jerusalem and suffix ``IL`` (instead of a bare
|
||||||
|
``Z``) so the displayed/filename time is unambiguously Israel-local.
|
||||||
|
"""
|
||||||
|
return datetime.now(IL_TZ).strftime("%Y%m%dT%H%M%S-IL")
|
||||||
|
|
||||||
|
|
||||||
def _write_report(result: dict, ts: str) -> tuple[Path, Path]:
|
def _write_report(result: dict, ts: str) -> tuple[Path, Path]:
|
||||||
@@ -356,8 +366,10 @@ def _write_report(result: dict, ts: str) -> tuple[Path, Path]:
|
|||||||
|
|
||||||
g = result["grid"]
|
g = result["grid"]
|
||||||
n = g["n_finals"]
|
n = g["n_finals"]
|
||||||
|
il_now = datetime.now(IL_TZ).strftime("%Y-%m-%d %H:%M")
|
||||||
lines = [
|
lines = [
|
||||||
f"# #208 — כיול model×effort מול הסופיים — {ts}\n",
|
f"# #208 — כיול model×effort מול הסופיים — {ts}\n",
|
||||||
|
f"> נוצר: {il_now} (שעון ישראל · Asia/Jerusalem)\n",
|
||||||
f"> ⚠️ **גודל-מדגם: {n} סופיים** ({', '.join(g['finals']) or '—'}). "
|
f"> ⚠️ **גודל-מדגם: {n} סופיים** ({', '.join(g['finals']) or '—'}). "
|
||||||
"זוהי **עדות-כיוון, לא רגרסיה** — מעט תיקים בעלי סופי-עלוי. "
|
"זוהי **עדות-כיוון, לא רגרסיה** — מעט תיקים בעלי סופי-עלוי. "
|
||||||
"ההמלצה אדוויזורית; ההכרעה בידי היו\"ר/המפעיל.\n",
|
"ההמלצה אדוויזורית; ההכרעה בידי היו\"ר/המפעיל.\n",
|
||||||
|
|||||||
Reference in New Issue
Block a user