Files
fete/docs/agents/research/2026-03-04-datetime-best-practices.md
nitrix e3ca613210 Add agent research and implementation plan docs for US-1
Research reports on datetime handling, RFC 9457, font selection.
Implementation plans for US-1 create event and post-review fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 10:57:44 +01:00

5.5 KiB

date, git_commit, branch, topic, tags, status
date git_commit branch topic tags status
2026-03-04T21:15:50+00:00 b8421274b4 master Date/Time Handling Best Practices for the fete Stack
research
datetime
java
postgresql
openapi
typescript
complete

Research: Date/Time Handling Best Practices

Research Question

What are the best practices for handling dates and times across the full fete stack (Java 25 / Spring Boot 3.5.x / PostgreSQL / OpenAPI 3.1 / Vue 3 / TypeScript)?

Summary

The project has two distinct date/time concepts: event date/time (when something happens) and expiry date (after which data is deleted). These map to different types at every layer. The recommendations align Java types, PostgreSQL column types, OpenAPI formats, and TypeScript representations into a consistent stack-wide approach.

Detailed Findings

Type Mapping Across the Stack

Concept Java PostgreSQL OpenAPI TypeScript Example
Event date/time OffsetDateTime timestamptz string, format: date-time string 2026-03-15T20:00:00+01:00
Expiry date LocalDate date string, format: date string 2026-06-15
Audit timestamps (createdAt, etc.) OffsetDateTime timestamptz string, format: date-time string 2026-03-04T14:22:00Z

Event Date/Time: OffsetDateTime + timestamptz

Why OffsetDateTime, not LocalDateTime:

  • PostgreSQL best practice explicitly recommends timestamptz over timestamp — the PostgreSQL wiki says "don't use timestamp". timestamptz maps naturally to OffsetDateTime.
  • Hibernate 6 (Spring Boot 3.5.x) has native OffsetDateTimetimestamptz support. LocalDateTime requires extra care to avoid silent timezone bugs at the JDBC driver level.
  • An ISO 8601 string with offset (2026-03-15T20:00:00+01:00) is unambiguous in the API. A bare LocalDateTime string forces the client to guess the timezone.
  • The OpenAPI date-time format and openapi-generator default to OffsetDateTime in Java — no custom type mappings needed.

Why not ZonedDateTime: Carries IANA zone IDs (e.g. Europe/Berlin) which add complexity without value for this use case. Worse JDBC support than OffsetDateTime.

How PostgreSQL stores it: timestamptz does not store the timezone. It converts input to UTC and stores UTC. On retrieval, it converts to the session's timezone setting. The offset is preserved in the Java OffsetDateTime via the JDBC driver.

Practical flow: The frontend sends the offset based on the organizer's browser locale. The server stores UTC. Display-side conversion happens in the frontend.

Expiry Date: LocalDate + date

The expiry date is a calendar-date concept ("after which day should data be deleted"), not a point-in-time. A cleanup job runs periodically and deletes events where expiryDate < today. Sub-day precision adds no value and complicates the UX.

Jackson Serialization (Spring Boot 3.5.x)

Spring Boot 3.x auto-configures jackson-datatype-jsr310 (JavaTimeModule) and disables WRITE_DATES_AS_TIMESTAMPS by default:

  • OffsetDateTime serializes to "2026-03-15T20:00:00+01:00" (ISO 8601 string)
  • LocalDate serializes to "2026-06-15"

No additional configuration needed. For explicitness, can add to application.properties:

spring.jackson.serialization.write-dates-as-timestamps=false

Hibernate 6 Configuration

With Hibernate 6, OffsetDateTime maps to timestamptz using the NATIVE timezone storage strategy by default on PostgreSQL. Can be made explicit:

spring.jpa.properties.hibernate.timezone.default_storage=NATIVE

This tells Hibernate to use the database's native TIMESTAMP WITH TIME ZONE type directly.

OpenAPI Schema Definitions

# Event date/time
eventDateTime:
  type: string
  format: date-time
  example: "2026-03-15T20:00:00+01:00"

# Expiry date
expiryDate:
  type: string
  format: date
  example: "2026-06-15"

Code-generation mapping (defaults, no customization needed):

OpenAPI format Java type (openapi-generator) TypeScript type (openapi-typescript)
date-time java.time.OffsetDateTime string
date java.time.LocalDate string

Frontend (TypeScript)

openapi-typescript generates string for both format: date-time and format: date. This is correct — JSON has no native date type, so dates travel as strings. Parsing to Date objects happens explicitly at the application boundary when needed (e.g. for display formatting).

Sources