Skip to content

Render timestamps as local-with-offset for humans, UTC for JSON#351

Merged
daniel-thom merged 3 commits into
mainfrom
fix/consistent-timestamps
May 25, 2026
Merged

Render timestamps as local-with-offset for humans, UTC for JSON#351
daniel-thom merged 3 commits into
mainfrom
fix/consistent-timestamps

Conversation

@daniel-thom
Copy link
Copy Markdown
Collaborator

Summary

  • Apply a single timestamp display policy across CLI, TUI, dash, and Python client:
    • Humans (TUI, dash, CLI table mode, CLI detail printouts) see local time with an explicit ±HHMM offset suffix (e.g. 2026-05-25 14:30:00 -0700)
    • JSON mode keeps the server's raw UTC RFC3339 value
  • Server stores UTC already, so this is purely a client-side rendering change. File mtimes get the same treatment as RFC3339 timestamps.
  • Includes the new job.start_time field from PR Add start_time and compute_node_id to the job table #350 — it lands consistently from day one rather than inheriting the prior raw-passthrough pattern.

Why

Timestamp display had drifted into three different conventions: raw RFC3339 in some CLI sites, local-without-offset in others, and silent local in the dash. A user SSH'd into a remote host couldn't tell whether a dash time was local or server-side. Now every human surface shows a self-describing string.

What changed

  • src/client/utils.rs — added format_local_timestamp(&str) and format_local_timestamp_epoch(f64) helpers, plus a shared HUMAN_TIMESTAMP_FORMAT constant and three unit tests.
  • CLI (workflows.rs, results.rs, compute_nodes.rs, jobs.rs) — route detail-view printlns and tabled table rows through the helper. JSON paths untouched (already UTC RFC3339).
  • CLI events.rsformat_timestamp_ms (human) now includes the offset; new format_timestamp_ms_utc formats the JSON convenience timestamp_formatted field as UTC RFC3339 with millisecond precision.
  • TUI (ui.rs, components.rs) — format_timestamp (file mtimes, was UTC Z) and format_timestamp_ms (events, was local-no-offset) both now emit local-with-offset; job-detail popup formats start_time through the new helper.
  • Dash (app-utils.js, app-job-details.js) — formatDateLocal appends ±HHMM; app-job-details.js routes job.start_time through formatTimestamp instead of raw escapeHtml.
  • Python (common.py) — convert_timestamp now returns a tz-aware UTC datetime (datetime.fromtimestamp(ts/1000, tz=timezone.utc)) so callers can .astimezone() for local rather than silently inheriting the client zone. Docstring updated.

Notes

  • CSV output reuses the same tabled struct as table mode, so CSV is also local-with-offset. If a downstream tool wants machine-readable UTC, that would need a separate row struct — happy to follow up if needed.
  • The format_timestamp_ms in events keeps millisecond precision (events arrive frequently enough that subsecond ordering matters); the file-mtime / RFC3339 helpers use second precision.

Test plan

  • cargo build --workspace — clean
  • cargo fmt -- --check — clean
  • cargo clippy --all --all-targets --all-features -- -D warnings — clean
  • dprint check — clean
  • cargo nextest run --lib — 372/372 pass (3 new tests for the helpers)
  • Manual: launch TUI, eyeball file mtime and event timestamps for offset suffix
  • Manual: open dash, eyeball workflow/event/file timestamps for offset suffix; verify job detail "Start Time" renders as local-with-offset
  • Manual: torc results list <wf> and torc -f json results list <wf> — confirm table shows local, JSON shows UTC RFC3339
  • Manual: Python from torc.common import convert_timestamp; print(convert_timestamp(...).tzinfo) returns UTC

🤖 Generated with Claude Code

Timestamp display was inconsistent across the CLI, TUI, and dash. Some
fields rendered raw RFC3339, others stripped the offset to local time
without a suffix, and the dash showed local time with no way to tell it
apart from UTC. After PR #350 added job.start_time, the inconsistency
was about to spread to a new field.

Apply a single policy:

  * humans (TUI, dash, CLI table mode, CLI detail printouts) see local
    time with an explicit ±HHMM offset suffix
  * JSON mode keeps the server's raw UTC RFC3339 value

The server already stores UTC, so this is purely a client-side
rendering change. File mtimes get the same treatment as RFC3339
timestamps.

A new pair of helpers in `src/client/utils.rs` (format_local_timestamp
and format_local_timestamp_epoch) covers the CLI and TUI surfaces; the
dash routes everything through formatDateLocal in app-utils.js; and the
Python convert_timestamp helper now returns a tz-aware UTC datetime so
callers no longer silently inherit the client's local zone.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Unifies timestamp rendering across Torc’s human-facing clients (CLI/TUI/dash/Python) so humans consistently see local time with an explicit ±HHMM offset, while JSON output preserves UTC RFC3339 values.

Changes:

  • Added shared Rust timestamp helpers (format_local_timestamp*) and updated CLI/TUI call sites to use local-with-offset formatting for human output.
  • Updated dash timestamp formatting to append the local offset and updated job details to format job.start_time consistently.
  • Updated Python convert_timestamp to return a tz-aware UTC datetime to prevent naive/local-time ambiguity.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
torc-dash/static/js/app-utils.js Adds ±HHMM suffix for local timestamp rendering in the dash.
torc-dash/static/js/app-job-details.js Routes job.start_time through the dash timestamp formatter.
src/tui/ui.rs Updates TUI timestamp formatting (events + file mtimes) to include ±HHMM and adjusts column widths.
src/tui/components.rs Formats job.start_time in the job details popup via the shared helper.
src/client/utils.rs Introduces shared human timestamp format constant + RFC3339/epoch formatting helpers and tests.
src/client/commands/workflows.rs Formats workflow timestamps for human list/detail output via helper (JSON unchanged).
src/client/commands/results.rs Formats result completion time for human list/detail output via helper.
src/client/commands/jobs.rs Formats job start time for human detail output via helper.
src/client/commands/events.rs Human event timestamps now include offset; JSON “formatted” timestamp becomes UTC RFC3339 w/ millis.
src/client/commands/compute_nodes.rs Formats compute node start time for human list/detail output via helper.
python_client/src/torc/common.py Makes convert_timestamp return tz-aware UTC datetimes and updates docstring accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/client/utils.rs
Comment thread src/tui/ui.rs Outdated
Comment thread torc-dash/static/js/app-utils.js
daniel-thom and others added 2 commits May 25, 2026 14:05
Three robustness fixes flagged by Copilot review on PR #351:

* `format_local_timestamp_epoch` split epoch_secs into (i64 secs, u32
  nsecs), which could produce `nsecs == 1_000_000_000` from float
  rounding (chrono rejects this, returning None) or underflow the u32
  cast for pre-epoch fractional values. Both cases silently fell back to
  the raw float string. Route through total nanos via
  `from_timestamp_nanos` instead — that helper saturates cleanly on
  NaN/inf and handles negative epochs.

* TUI `format_timestamp` had the same bug, with worse fallout:
  `unwrap_or_default()` blanks the file's Modified cell when chrono
  returns None. Removed the duplicate helper and pointed the only caller
  at the shared one.

* Dash `formatTimestamp` / `formatUnixTimestamp` relied on a try/catch
  to handle bad input, but `new Date("garbage")` returns an Invalid
  Date object instead of throwing — `formatDateLocal` then rendered it
  as `NaN-NaN-NaN ... +NaNNaN`. Guard with `isNaN(date.getTime())` and
  fall back to the escaped original (formatTimestamp) or "-"
  (formatUnixTimestamp).

Added two unit tests covering the subsecond-rounding and pre-epoch
edge cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@daniel-thom daniel-thom merged commit 668c21c into main May 25, 2026
9 checks passed
@daniel-thom daniel-thom deleted the fix/consistent-timestamps branch May 25, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants