Skip to content

fix(timezone): display API datetimes in Europe/Paris (DST-safe)#141

Draft
dev-mansonthomas wants to merge 1 commit into
masterfrom
fix/timezone-utc-iso
Draft

fix(timezone): display API datetimes in Europe/Paris (DST-safe)#141
dev-mansonthomas wants to merge 1 commit into
masterfrom
fix/timezone-utc-iso

Conversation

@dev-mansonthomas
Copy link
Copy Markdown
Owner

Context

Backend datetime fields were historically serialised as naive strings (no TZ designator) and treated as local Paris time by the frontend, which relied on a hardcoded -2h offset in my-slots.component.ts and on Angular's default DatePipe (which uses the browser TZ).

This was wrong twice:

  • It broke during DST transitions (CET vs CEST).
  • It produced a 2h offset for users outside Paris TZ.

The backend (paired PR rcq-functions-v2 on the same branch name fix/timezone-utc-iso) now serialises every datetime field as strict UTC ISO 8601 with a Z suffix and seconds precision: YYYY-MM-DDTHH:MM:SSZ (no fraction, no offset).

Frontend changes

New ParisDatePipe (src/app/pipes/paris-date.pipe.ts)

Uses Intl.DateTimeFormat with timeZone='Europe/Paris'. Handles:

  • ISO strings with Z or explicit offset → parsed as instant.
  • Legacy naive strings (no Z, no offset) → re-interpreted as UTC, so the 5-minute Firestore cache window between backend deploy and frontend deploy stays correct.
  • Date objects and null / undefined → identity / empty string.
  • Format presets: short (dd/MM/yyyy HH:mm), medium, date, time. Falls back to Angular's DatePipe for unknown presets (behavioural compatibility).

Templates migrated to | parisDate

  • tronc-history.component.html (depart_theorique, depart, retour)
  • tronc-history-dialog.component.html (idem)
  • register-tronc-state.component.html (depart_theorique)

my-slots.component.ts

Removed the -2h hack on tronc.depart / tronc.retour. The component now sends new Date(...).toISOString() which produces strict "...Z" strings the backend accepts.

cloud-function.service.ts

parseTroncDates now treats naive legacy strings as UTC (parseAsUtcIfNaive helper) to keep parity with the pipe during the cache window.

shared.module.ts

Declare + export ParisDatePipe so every feature module that imports SharedModule can use | parisDate in templates.

Tests

src/app/pipes/paris-date.pipe.spec.ts covers:

  • Summer (UTC+2 / CEST)
  • Winter (UTC+1 / CET)
  • Legacy naive string
  • null / undefined
  • Date input
  • Unknown preset fallback

Coordination & deploy order

Paired with rcq-functions-v2 PR on the same branch name fix/timezone-utc-iso.

Front-first deploy is safe because the pipe accepts both legacy and ISO formats — once this PR is merged and deployed on the 3 envs, the backend PR can be merged and deployed without any visible regression.

Smoke-test once backend is deployed on dev:

curl -s "https://europe-west1-rq-fr-dev.cloudfunctions.net/historique-tronc-queteur?..." \
  -H "Authorization: Bearer $TOKEN" | jq '.[0].depart'
# -> must end with "Z"

Out of scope

The gcp-deploy.sh indexes subcommand (which unlocked the Mon historique page in the first place) is shipped separately in #140.


Pull Request opened by Augment Code with guidance from the PR author

Backend datetime fields were historically serialised as naive strings
(no TZ designator) and treated as local Paris time by the frontend,
which relied on a hardcoded -2h offset in my-slots.component.ts and on
Angular's default DatePipe (which uses the browser TZ). This was wrong
twice: it broke during DST transitions (CET vs CEST), and it produced a
2h offset for users outside Paris TZ.

The backend (rcq-functions-v2 #fix/timezone-utc-iso) now serialises
every datetime field as strict UTC ISO 8601 with a 'Z' suffix and
seconds precision: "YYYY-MM-DDTHH:MM:SSZ" (no fraction, no offset).

Frontend changes:

- New ParisDatePipe (src/app/pipes/paris-date.pipe.ts) using
  Intl.DateTimeFormat with timeZone='Europe/Paris'. Handles:
  * ISO strings with Z or explicit offset -> parsed as instant.
  * Legacy naive strings (no Z, no offset) -> re-interpreted as UTC,
    so the 5-minute Firestore cache window between backend deploy and
    frontend deploy stays correct.
  * Date objects and null/undefined -> identity / empty string.
  Format presets: 'short' (dd/MM/yyyy HH:mm), 'medium', 'date',
  'time'. Falls back to Angular DatePipe when an unknown preset is
  passed, keeping behavioural compatibility.

- Templates migrated to | parisDate:
  * tronc-history.component.html (depart_theorique, depart, retour)
  * tronc-history-dialog.component.html (idem)
  * register-tronc-state.component.html (depart_theorique)

- my-slots.component.ts: removed the -2h hack on tronc.depart /
  tronc.retour. The component now sends new Date(...).toISOString(),
  which produces strict "...Z" strings the backend accepts.

- cloud-function.service.ts: parseTroncDates now treats naive legacy
  strings as UTC (parseAsUtcIfNaive helper) to keep parity with the
  pipe during the cache window.

- shared.module.ts: declare + export ParisDatePipe so every feature
  module that imports SharedModule can use | parisDate in templates.

Tests:
- src/app/pipes/paris-date.pipe.spec.ts covers summer (UTC+2),
  winter (UTC+1), legacy naive, null/undefined, Date input, and
  unknown preset fallback.

Coordination: paired with rcq-functions-v2 PR on the same branch name
(fix/timezone-utc-iso). Front-first deploy is safe because the pipe
accepts both legacy and ISO formats.

Smoke-test once backend is deployed on dev:
  curl ... historique-tronc-queteur | jq '.[0].depart'
  -> must end with "Z".
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