fix: Maintenance dates display one day off due to timezone conversion #237
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Maintenance record dates are displayed one day off when editing. A record listed as 3/13/2026 in the record list shows 03/14/2026 when the edit dialog opens. The bug was reproduced on staging.motovaultpro.com using the 2017 Corvette maintenance records.
Root Cause
The PostgreSQL
DATEtype (OID 1082) is returned by thepgdriver as a JavaScriptDateobject created at local midnight on the server. When Fastify serializes the API response viaJSON.stringify(), it callsDate.toJSON()which internally callstoISOString(), converting to UTC. The timezone offset between server local time and UTC shifts the date forward or backward by one day depending on the server's timezone.On the frontend,
dayjs()parses the UTC ISO string (e.g.,2026-03-13T05:00:00.000Z) and displays it in the user's local timezone, compounding the mismatch. Additionally,DatePickeronChange handlers usetoISOString().split('T')[0]to convert selected dates back to strings, which performs the same unsafe UTC conversion when saving.Data flow (before fix):
Solution (Full Stack, Option C from analysis)
Backend (1 file): Override pg type parser for DATE columns to return plain YYYY-MM-DD strings instead of Date objects. This is the primary architectural fix.
Frontend (6 files): Replace all
toISOString().split('T')[0]withdayjs.format('YYYY-MM-DD')in DatePicker onChange handlers. Parse API dates defensively withsubstring(0, 10)in edit dialogs.Affected Files
backend/src/core/config/database.tsfrontend/.../MaintenanceRecordEditDialog.tsxfrontend/.../MaintenanceScheduleEditDialog.tsxfrontend/.../MaintenanceRecordForm.tsxfrontend/.../MaintenanceScheduleForm.tsxfrontend/.../useMaintenanceReceiptOcr.tsAcceptance Criteria
Analysis
Codebase analysis and problem analysis skills were run. Four alternative solutions were evaluated (backend-only, frontend-only, full-stack, repository-layer). Full-stack was selected for defense-in-depth: the backend type parser prevents the bug at the source, and frontend changes guard against regressions and fix the independent toISOString() bug in onChange handlers.