Skip to content

feat(viz): format timestamp axes in the dashboard timezone — and reconcile the two timezone models #70

Description

@xe-nvdk

units.ts from #30 formats quantities. It does not format instants, and a time series axis is nothing but instants. That is missing on purpose — it needs a timezone, and Launchpad currently has two incompatible ideas of what a timezone is.

The conflict

  • src/lib/dashboard/model.ts — timezone: 'utc' | 'browser' | <IANA name>, matching Grafana, which is what an imported dashboard carries.
  • src/lib/stores/timezone.ts — 'utc' | 'local', a two-state toggle in the header, persisted as arc-timezone, and already wired through the logs and instance pages.

These are not the same type and the overlap is partial: 'browser' and 'local' mean the same thing under different names, and the store has no way to express America/Montevideo. A dashboard saved with an explicit IANA zone and then viewed through the existing toggle silently renders in the wrong one — off by hours, with no error.

Decide this before writing the formatter, not after. The recommendation is that the dashboard model wins, the store widens to 'utc' | 'browser' | string, 'local' migrates to 'browser' on read, and the header toggle becomes a picker on dashboard routes while staying a toggle elsewhere. But that is a call to make deliberately — it touches every existing consumer of the store.

The formatter

timeAxisFormatter(range: { from: number; to: number }, timezone: string): (ms: number) => string
  • Tick granularity follows the span, not the tick count. A 5-minute window wants 15:04:05; a 3-day window wants Mar 12 15:00; a year wants Mar 2026. Rendering a full ISO string at every tick is how axes become unreadable.
  • One format for the whole axis. Mixed granularity across ticks stops reading as a scale — the same rule formatAxisTicks already follows for quantities.
  • Context goes in the axis label, not in every tick. The year appears once.
  • Tooltips get full precision including the zone, since that is where someone copies a timestamp out of.

Traps

  • Intl.DateTimeFormat with a timeZone option is the only correct way to do this. Adding an offset to a Date is wrong across a DST boundary, and a dashboard spanning one is exactly when someone is looking.
  • Construct the Intl.DateTimeFormat once per axis, not per tick. It is one of the more expensive objects in the platform; building it inside a 60-tick loop on every redraw is measurable.
  • An invalid IANA name from an imported dashboard makes Intl.DateTimeFormat throw, which takes the panel down. Validate at parse or catch at construction — do not let it reach a draw call.
  • uPlot's default x unit is seconds unless opts.ms is set; frame.ts produces milliseconds. Whatever wires this needs to agree on one.

Scope

The reconciliation, src/lib/dashboard/time.ts + tests, and migrating the existing store consumers. Blocks the time series panel (#36) and the shared panel chrome (#34).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dashboardsDashboarding and visualizationenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions