@@ -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
47694787def 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