Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(timezone): store event times as UTC Instant, display in Taipei#23

Merged
Clark1945 merged 1 commit into
masterfrom
fix/timezone-taipei
Jun 28, 2026
Merged

fix(timezone): store event times as UTC Instant, display in Taipei#23
Clark1945 merged 1 commit into
masterfrom
fix/timezone-taipei

Conversation

@Clark1945

Copy link
Copy Markdown
Owner

Problem

Transaction history showed times 8 hours behind Taipei. Root cause: event timestamps used LocalDateTime (a zone-less wall clock) written into TIMESTAMP WITHOUT TIME ZONE columns and stamped via LocalDateTime.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.createdAt is a LocalDateTime that 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"

  • Types: Transaction / Member / AuditLog (wallet_system and audit-service) createdAt + lastLoginAt: LocalDateTimeInstant; all *.now()Instant.now().
  • Schema: V5 retypes transactions.created_at, members.created_at, members.last_login_at to TIMESTAMPTZ via USING ... 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=UTC so storage is always the absolute instant.
  • Filtering: TransactionSpec converts the user's Asia/Taipei date range to instants.
  • Display: AppZone centralises the display zone; Transaction.getCreatedAtLocal() renders for the view; dashboard.html uses it.
  • Container TZ: kept only for log readability / LocalDate.now() — no longer load-bearing for correctness.
  • MongoDB: no migration — BSON Date already stores the absolute UTC instant; the type change just reads it back as Instant.

Verification

  • Testcontainers Flyway IT (real Postgres): V5 applies, ddl-auto=validate accepts Instanttimestamptz, date-range filter works.
  • Full suites green: wallet_system 236, audit-service 2.
  • New-code coverage 92.3% (gate ≥ 80%).

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 applies V5 automatically on the next up -d --build.

🤖 Generated with Claude Code

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]>
@sonarqubecloud

Copy link
Copy Markdown

@Clark1945 Clark1945 merged commit cc63d36 into master Jun 28, 2026
7 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