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>
108 lines
5.5 KiB
Markdown
108 lines
5.5 KiB
Markdown
---
|
|
date: 2026-03-04T21:15:50+00:00
|
|
git_commit: b8421274b47c6d1778b83c6b0acb70fd82891e71
|
|
branch: master
|
|
topic: "Date/Time Handling Best Practices for the fete Stack"
|
|
tags: [research, datetime, java, postgresql, openapi, typescript]
|
|
status: 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`"](https://wiki.postgresql.org/wiki/Don't_Do_This). `timestamptz` maps naturally to `OffsetDateTime`.
|
|
- Hibernate 6 (Spring Boot 3.5.x) has native `OffsetDateTime` ↔ `timestamptz` 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`:
|
|
```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:
|
|
|
|
```properties
|
|
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
|
|
|
|
```yaml
|
|
# 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
|
|
|
|
- [PostgreSQL Wiki: Don't Do This](https://wiki.postgresql.org/wiki/Don't_Do_This) — recommends `timestamptz` over `timestamp`
|
|
- [PostgreSQL Docs: Date/Time Types](https://www.postgresql.org/docs/current/datatype-datetime.html)
|
|
- [Thorben Janssen: Hibernate 6 OffsetDateTime and ZonedDateTime](https://thorben-janssen.com/hibernate-6-offsetdatetime-and-zoneddatetime/)
|
|
- [Baeldung: OffsetDateTime Serialization With Jackson](https://www.baeldung.com/java-jackson-offsetdatetime)
|
|
- [Baeldung: Map Date Types With OpenAPI Generator](https://www.baeldung.com/openapi-map-date-types)
|
|
- [Baeldung: ZonedDateTime vs OffsetDateTime](https://www.baeldung.com/java-zoneddatetime-offsetdatetime)
|
|
- [Reflectoring: Handling Timezones in Spring Boot](https://reflectoring.io/spring-timezones/)
|
|
- [openapi-typescript documentation](https://openapi-ts.dev/)
|