Skip to content

Commit d8ad431

Browse files
Interstellar-codeteknium1
authored andcommitted
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.
1 parent ca8126b commit d8ad431

1 file changed

Lines changed: 28 additions & 10 deletions

File tree

hermes_cli/kanban_db.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4756,26 +4756,44 @@ def board_stats(conn: sqlite3.Connection) -> dict:
47564756
}
47574757

47584758

4759-
def _safe_int(val: Optional[str]) -> Optional[int]:
4760-
"""Parse a timestamp field to int, returning None on garbage like '%s'."""
4759+
def _to_epoch(val) -> Optional[int]:
4760+
"""Normalise a timestamp to unix epoch seconds.
4761+
4762+
Accepts ints (pass-through), numeric strings, and ISO-8601 strings.
4763+
Returns ``None`` for ``None`` / empty values.
4764+
"""
47614765
if val is None:
47624766
return None
4763-
try:
4767+
if isinstance(val, int):
4768+
return val
4769+
if isinstance(val, float):
47644770
return int(val)
4765-
except (ValueError, TypeError):
4771+
s = str(val).strip()
4772+
if not s:
4773+
return None
4774+
try:
4775+
return int(s)
4776+
except ValueError:
4777+
pass
4778+
# ISO-8601 fallback (e.g. '2026-05-10T15:00:00Z')
4779+
try:
4780+
from datetime import datetime, timezone
4781+
dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
4782+
return int(dt.timestamp())
4783+
except (ValueError, OSError):
47664784
return None
47674785

47684786

47694787
def task_age(task: Task) -> dict:
47704788
"""Return age metrics for a single task. All values are seconds or None."""
47714789
now = int(time.time())
4772-
created = _safe_int(task.created_at)
4773-
started = _safe_int(task.started_at)
4774-
completed = _safe_int(task.completed_at)
4775-
age_since_created = now - created if created else None
4776-
age_since_started = now - started if started else None
4790+
_c = _to_epoch(task.created_at)
4791+
_s = _to_epoch(task.started_at)
4792+
_co = _to_epoch(task.completed_at)
4793+
age_since_created = now - _c if _c is not None else None
4794+
age_since_started = now - _s if _s is not None else None
47774795
time_to_complete = (
4778-
completed - (started or created) if completed else None
4796+
_co - (_s or _c) if _co is not None else None
47794797
)
47804798
return {
47814799
"created_age_seconds": age_since_created,

0 commit comments

Comments
 (0)