Skip to content

Type admin times on the event's clock, in 24-hour time - #316

Merged
leowilkin merged 6 commits into
mainfrom
leo/eloquent-hamilton-laj9io
Oct 31, 2026
Merged

leowilkin merged 6 commits into
mainfrom
leo/eloquent-hamilton-laj9io

Conversation

@leowilkin

Copy link
Copy Markdown
Member

Problem

Admin datetime-local fields submit a zone-less string like 2026-07-12T09:30, and Rails casts it in the app zone, UTC. Event schedule times and scheduled messages already re-read that string on the event's clock, each with its own copy of the code. Scan point windows, incident times and train/bus/car travel times did not. A 09:30 check-in window at a Boston event was stored as 09:30 UTC (05:30 local), and readers that treat the value as an instant (the scanner API, the incident API) were off by the event's offset.

Inputs also show 12- or 24-hour time depending on the browser.

Changes

  • EventLocalTimes concern: a model declares event_local_times :attr, .... A typed wall-clock string is re-parsed in the event's zone at validation, after an event or timezone submitted in the same form has been assigned. A value with its own offset is left alone. Views use local_time(:attr), local_time_field_value(:attr) and time_display_zone. Event and Message now use the concern instead of their own copies.
  • Legacy rows: existing rows on scan_contexts, incidents and travels hold the typed wall-clock as UTC. A new times_in_event_zone column marks them (false for existing rows; the model defaults new rows to true, so writes from an older deploy mid-rollout still read as legacy). They keep rendering on UTC exactly as before. The first time a legacy row's times are retyped through a form, all of its times are re-read as event-local and the flag flips, so a row never mixes the two readings. An unchanged instant echoed back by an API client does not flip it.
  • 24-hour time: every admin datetime-local input gets lang="en-GB". Admin travel fields, flight legs and the JS-built leg and export filter inputs are included. Chrome and Safari take the format from the OS locale and ignore lang, so admins on a 12-hour system still see 12-hour inputs there.
  • Zone notes: each admin time field says which zone it's in. Bans have no event, so ban expiry stays UTC and the form says so.
  • Scan points: typed, prefilled and listed on the event's clock.
  • Scan history and counts: the date filter and the "today" counts use the event's calendar day. Scan.today (UTC day) had no callers left and is removed.
  • Exports: filters read typed dates and times in the event's zone, for ad hoc exports and saved templates.
  • Incidents: the time is typed, prefilled and shown on the event's clock in admin, the Slack thread and the HQ escalation.
  • Travel (train/bus/car): times are read on the event's clock for every form that writes them, so the columns keep one meaning: the admin travel page, onboarding, the dashboard and the guardian portal. Only the admin forms get lang and the zone note. These times show on the clock they were typed on wherever they appear, including the ticket PDF and the CSV export. Flight legs keep their per-airport zones.
  • Incident Slack updates:
    • The diff is read from saved_changes after saving. Read before validation, an untouched time would count as an edit.
    • SendIncidentUpdateToSlackJob skips the post when no change is one it describes. Before, it sent Slack an empty message and raised. That already happened on a time-only edit; re-saving a legacy incident made it easier to hit.

Not changed

  • API, Airtable and MCP output: these still serialise the stored instants. New rows are now correct; legacy rows are still off by the event's offset, as before.
  • Travel calendar: legacy rows land where they always have.
  • Scan time displays: scanned-at times on the scans index, the scanner and the participant page show UTC with no label.
  • Audit log filters: the date filters use UTC days.
  • Other CSV columns: datetime columns other than typed travel times still export in UTC.
  • Inbound bus departure time on participant forms: it is now read in the event's zone, even if the participant leaves from another zone.

Testing

  • New specs:
    • the concern itself, covering new rows, legacy rows, the flag flip, an echoed instant and a timezone submitted with the times;
    • the incident, scan point and travel models;
    • admin request specs for both view variants, covering prefill, lang, the zone note, saving and legacy rows;
    • the participant travel paths;
    • the export filter and the CSV output;
    • both incident Slack jobs.
  • bundle exec rspec excluding spec/system: 3731 examples, 5 failures. All 5 are Passkit specs failing on a missing PASSKIT_APPLE_TEAM_IDENTIFIER in the test environment, and they fail the same way without these changes.
  • bin/rubocop on the changed Ruby files: no offenses.


Generated by Claude Code

leowilkin and others added 5 commits October 31, 2026 09:36
EventLocalTimes generalises what Event and Message each did by hand:
datetime-local strings are re-parsed in the event's zone at validation,
and forms/views get the time back on that clock. Scan contexts, incidents
and non-flight travel times now declare it too.

Rows on those three tables saved before this hold the typed wall-clock as
UTC. A times_in_event_zone flag (false for existing rows) keeps them
rendering on UTC as before; the first time a legacy row's times are
resubmitted, all of them move onto the event's clock and the flag flips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JmwPzDWunz2cS7cuhGMiW
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JmwPzDWunz2cS7cuhGMiW
…ght clock

Incident occurred_at is typed and prefilled on the event's clock and
shown that way in admin, Slack and the HQ escalation, with legacy rows
still on UTC. Every admin datetime-local field gets lang="en-GB" and a
note saying which zone it's in; bans have no event, so they stay UTC and
say so.

The incident Slack diff is now read after saving, since a typed time is
only moved onto the event's clock during validation. The update job
skips posting when no change is one it describes, instead of sending
Slack an empty message. A legacy row stays on UTC when an API client
echoes back its unchanged instant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JmwPzDWunz2cS7cuhGMiW
…ent's clock

Scan point start and end times are typed, prefilled and listed on the
event's clock in 24h, with legacy rows still on UTC. The scan history
date filter and every "today" count use the event's calendar day rather
than UTC's, so the unused Scan.today scope goes. Export filters read
typed dates and times in the event's zone (ad hoc and saved templates),
and their datetime inputs, server- or JS-built, get lang="en-GB".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JmwPzDWunz2cS7cuhGMiW
Travel's non-flight times are read on the event's clock for every writer
(admin travel page, onboarding, dashboard, guardian portal), so the
columns keep one meaning, and every place that shows them, including the
ticket PDF and CSV exports, shows them on the clock they were typed on.
Legacy rows stay on UTC. Admin travel inputs, flight legs included, get
lang="en-GB" and a zone note; participant-facing forms get neither.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012JmwPzDWunz2cS7cuhGMiW
@leowilkin
leowilkin force-pushed the leo/eloquent-hamilton-laj9io branch from 694dac7 to da340e0 Compare October 31, 2026 09:36
@leowilkin
leowilkin added this pull request to the merge queue Oct 31, 2026
@leowilkin
leowilkin removed this pull request from the merge queue due to a manual request Oct 31, 2026
@leowilkin
leowilkin added this pull request to the merge queue Oct 31, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Oct 31, 2026
@leowilkin
leowilkin added this pull request to the merge queue Oct 31, 2026
@leowilkin
leowilkin removed this pull request from the merge queue due to a manual request Oct 31, 2026
@leowilkin
leowilkin enabled auto-merge October 31, 2026 16:00
@leowilkin
leowilkin added this pull request to the merge queue Oct 31, 2026
Merged via the queue into main with commit 1798eee Oct 31, 2026
14 checks passed
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.

1 participant