Skip to content

Run the backend in UTC and take the hotel's dates and times from Madrid - #31

Merged
bpstack merged 10 commits into
mainfrom
claude/compassionate-planck-gh6aof
Oct 14, 2026
Merged

bpstack merged 10 commits into
mainfrom
claude/compassionate-planck-gh6aof

Conversation

@bpstack

@bpstack bpstack commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Changes

  • One clock for the backend (ADR-047): the process and every MySQL session run in UTC on any machine, as Render and Aiven already did. A machine in Madrid read every DATE one day early and each edit moved it.
  • The hotel's "today" and "now": CURDATE(), NOW(), DATE(timestamp) and toISOString() gave yesterday between 00:00 and 02:00 in Madrid (parking filters and stats, group arrivals and payments, overdue invoices, cashier history and vouchers, activity, logbook, maintenance). They now use Madrid's date, built in Node or with CONVERT_TZ.
  • Parking times: stored as typed but read back as UTC, so every booking showed two hours late and each edit moved it two more. They are now the hotel's wall clock, answered without Z.
  • Booking code and report prefix: dated in Madrid (20261014_booking_code_hotel_date.sql, applied on local and Aiven).
  • Frontend "today": getMadridDate() instead of the UTC date (new invoice, cashier log day, parking edit, blacklist, export names).
  • Histories: a changed group date no longer splits into one change per character; maintenance statuses, priorities and assignees and cashier statuses read in words; times in Madrid.

Checked

  • pnpm verify in backend (1532 tests) and frontend; backend tests without a database, as CI runs them. Helpers tested at 00:30 in Madrid, summer and winter.
  • Local: every changed endpoint, maintenance and cashier histories in the browser.
  • Preview (Aiven): curl over every module; parking shows 12:00 and saving keeps it; group, maintenance and cashier histories in the browser.

- Set TZ=UTC first thing in index.ts: Render already ran in UTC, but a
  machine in Madrid read every DATE one day early and each edit moved it.
- Read and write dates in UTC in mysql2 and set time_zone to UTC on every
  MySQL connection, as Aiven already is, so NOW() and CURRENT_TIMESTAMP
  mean the same everywhere.
- Run the tests in UTC on any machine.
- Record the decision as ADR-047.
- Replace CURDATE() and NOW() with Madrid's date and time, built in Node
  and inlined as literals (repositories/shared/hotel-clock.ts): between
  00:00 and 02:00 in Madrid the UTC date was still yesterday in parking
  filters, group arrivals, group payments and overdue invoices.
- Treat parking check-in/out times as the hotel's wall clock: they were
  stored as typed but read back as UTC, so the screens showed every
  booking two hours late and each edit moved it two more. The API now
  answers them without Z, and the default times are Madrid's.
- Take the maintenance report prefix, the activity range, the backoffice
  ZIP name and the batch payment's default month from Madrid's date.
- Date the parking booking code in Madrid (20261014_booking_code_hotel_date.sql,
  applied on local and Aiven).
- Test the helpers at 00:30 in Madrid, summer and winter.
- Replace DATE(column) on UTC timestamps with the column's Madrid date
  (sqlMadridDay, CONVERT_TZ to Europe/Madrid): an action at 00:30 in Madrid
  was listed under the day before in the cashier history and vouchers,
  the activity feed, logbooks without a date, maintenance reports, parking
  stats and the demo activity log.
- Fall back to the UTC date where MySQL has no named time zones (a local
  install), as before; Aiven has them.
- Replace new Date().toISOString() as "today" with getMadridDate(): between
  00:00 and 02:00 in Madrid it gave yesterday as the default date of a new
  invoice, the cashier log day, the parking edit form and the blacklist
  form, and in the names of the CSV and ZIP exports.
- Store a changed date as plain ISO text: JSON.stringify quoted it, and the
  history tab parsed the quoted string as an object and listed one change
  per character.
- Compare field by field only when both values are records, and strip the
  quotes of the rows already stored, so old entries read as dates too.
- Show history times and dates in Madrid time.
- Maintenance history: statuses and priorities through their translations,
  and the assignee by name (the API now joins the user of an assignment)
  instead of in_progress or a user id.
- Cashier history: shift and voucher statuses translated, and dates and
  times fixed to Madrid.
- Blacklist audit trail: dates fixed to Madrid.
- The regex lost its backslash and never matched, so in_progress stayed untranslated.
@vercel

vercel Bot commented Oct 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
four-points Ready Ready Preview Oct 14, 2026 8:02pm UTC

@bpstack
bpstack merged commit 4c4567a into main Oct 14, 2026
6 checks passed

This branch was successfully deployed

1 active deployment
Preview — a0de96fb Deployed Oct 14, 2026 by vercel[bot]
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