fix(timezone): store event times as UTC Instant, display in Taipei#23
Merged
Conversation
Transaction history showed times 8h behind Taipei: event timestamps used LocalDateTime (a zone-less wall clock) written to TIMESTAMP WITHOUT TIME ZONE and stamped via LocalDateTime.now() in a UTC container. The same anti-pattern was latent in the audit path, where createdAt crosses RabbitMQ as JSON and silently loses its zone if services' zones differ. Adopt store-UTC-absolute-instant, convert-at-display: - Transaction/Member/AuditLog (both services) createdAt + lastLoginAt: LocalDateTime -> Instant; now() -> Instant.now(). - Flyway V5 retypes transactions/members timestamp columns to TIMESTAMPTZ (USING ... AT TIME ZONE UTC -- a retype, not a data shift). Replaces the earlier UTC-to-Taipei shift migration. - hibernate.jdbc.time_zone=UTC so storage is always the absolute instant. - TransactionSpec converts the user's Asia/Taipei date range to instants. - AppZone centralises the display zone; Transaction.getCreatedAtLocal() renders for the view; dashboard.html uses it. - Container TZ kept only for log readability (no longer load-bearing). Verified on a real Postgres (Testcontainers Flyway IT): migration applies, ddl-auto=validate accepts Instant<->timestamptz, date filter works. Full suites green (wallet_system 236, audit-service 2). MongoDB needs no migration: BSON Date already stores the absolute instant. Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem
Transaction history showed times 8 hours behind Taipei. Root cause: event timestamps used
LocalDateTime(a zone-less wall clock) written intoTIMESTAMP WITHOUT TIME ZONEcolumns and stamped viaLocalDateTime.now()in a UTC container — so the stored numbers were UTC wall clock and were rendered verbatim.The same anti-pattern was latent in the audit path:
AuditLog.createdAtis aLocalDateTimethat crosses RabbitMQ as JSON, which drops the zone — correctness only held because every service happened to share one zone.Fix — "store UTC absolute instant, convert at display"
Transaction/Member/AuditLog(wallet_system and audit-service)createdAt+lastLoginAt:LocalDateTime→Instant; all*.now()→Instant.now().V5retypestransactions.created_at,members.created_at,members.last_login_attoTIMESTAMPTZviaUSING ... AT TIME ZONE 'UTC'— a re-typing, not a data shift (the absolute moment is unchanged). This replaces an earlier UTC→Taipei row-shift migration that was never deployed.hibernate.jdbc.time_zone=UTCso storage is always the absolute instant.TransactionSpecconverts the user'sAsia/Taipeidate range to instants.AppZonecentralises the display zone;Transaction.getCreatedAtLocal()renders for the view;dashboard.htmluses it.TZ: kept only for log readability /LocalDate.now()— no longer load-bearing for correctness.Datealready stores the absolute UTC instant; the type change just reads it back asInstant.Verification
ddl-auto=validateacceptsInstant↔timestamptz, date-range filter works.Deploy note
Prod never ran the earlier shift migration, so its existing UTC-naive values are correctly reinterpreted by the
AT TIME ZONE 'UTC'retype. Flyway appliesV5automatically on the nextup -d --build.🤖 Generated with Claude Code