Files
fete/.specify/memory/research/datetime-best-practices.md
nitrix 6aeb4b8bca Migrate project artifacts to spec-kit format
- Move cross-cutting docs (personas, design system, implementation phases,
  Ideen.md) to .specify/memory/
- Move cross-cutting research and plans to .specify/memory/research/ and
  .specify/memory/plans/
- Extract 5 setup tasks from spec/setup-tasks.md into individual
  specs/001-005/spec.md files with spec-kit template format
- Extract 20 user stories from spec/userstories.md into individual
  specs/006-026/spec.md files with spec-kit template format
- Relocate feature-specific research and plan docs into specs/[feature]/
- Add spec-kit constitution, templates, scripts, and slash commands
- Slim down CLAUDE.md to Claude-Code-specific config, delegate principles
  to .specify/memory/constitution.md
- Update ralph.sh with stream-json output and per-iteration logging
- Delete old spec/ and docs/agents/ directories
- Gitignore Ralph iteration JSONL logs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:19:41 +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