fix(kanban): task_age() tolerates ISO-8601 timestamps

Prevents ValueError crash in dashboard get_board() when a task has
an ISO timestamp (e.g. "2026-05-10T15:00:00Z") instead of a unix epoch
int. Adds _to_epoch() helper that normalises both formats.
This commit is contained in:
Interstellar-code
2026-05-18 20:17:59 -07:00
committed by Teknium
parent ca8126bd53
commit d8ad431de8
+28 -10
View File
@@ -4756,26 +4756,44 @@ def board_stats(conn: sqlite3.Connection) -> dict:
}
def _safe_int(val: Optional[str]) -> Optional[int]:
"""Parse a timestamp field to int, returning None on garbage like '%s'."""
def _to_epoch(val) -> Optional[int]:
"""Normalise a timestamp to unix epoch seconds.
Accepts ints (pass-through), numeric strings, and ISO-8601 strings.
Returns ``None`` for ``None`` / empty values.
"""
if val is None:
return None
try:
if isinstance(val, int):
return val
if isinstance(val, float):
return int(val)
except (ValueError, TypeError):
s = str(val).strip()
if not s:
return None
try:
return int(s)
except ValueError:
pass
# ISO-8601 fallback (e.g. '2026-05-10T15:00:00Z')
try:
from datetime import datetime, timezone
dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
return int(dt.timestamp())
except (ValueError, OSError):
return None
def task_age(task: Task) -> dict:
"""Return age metrics for a single task. All values are seconds or None."""
now = int(time.time())
created = _safe_int(task.created_at)
started = _safe_int(task.started_at)
completed = _safe_int(task.completed_at)
age_since_created = now - created if created else None
age_since_started = now - started if started else None
_c = _to_epoch(task.created_at)
_s = _to_epoch(task.started_at)
_co = _to_epoch(task.completed_at)
age_since_created = now - _c if _c is not None else None
age_since_started = now - _s if _s is not None else None
time_to_complete = (
completed - (started or created) if completed else None
_co - (_s or _c) if _co is not None else None
)
return {
"created_age_seconds": age_since_created,