Skip to content

feat: Handle daylight-saving changes in slot generation - #828

Merged
Qoder-Voidd merged 1 commit into
Akazajan:mainfrom
favour-GL:feat/Handle-daylight-saving-changes-in-slot-generation
Oct 20, 2026
Merged

Qoder-Voidd merged 1 commit into
Akazajan:mainfrom
favour-GL:feat/Handle-daylight-saving-changes-in-slot-generation

Conversation

@favour-GL

Copy link
Copy Markdown
Contributor

Fix: map local workspace hours to correct instants across DST transitions

Summary

Slot generation currently treats workspace hours as if the UTC offset never changes. On days when the offset changes (DST start or end), slots can land an hour off, or be generated for local times that don’t exist or exist twice. This PR makes slot generation work from an explicit IANA timezone and resolves each local time to an instant using the offset in effect on that specific date.

Problem

Workspace hours are defined in local time (for example, 09:00–17:00). To book against them, those hours have to be converted into absolute instants. Using a fixed offset, or the server’s timezone, breaks on two kinds of days:

  • Spring forward (clocks skip an hour): Local times inside the gap, such as 02:00–02:59 in America/New_York on the March transition, don’t exist. Naive conversion either shifts them silently or produces invalid slots.
  • Fall back (clocks repeat an hour): Local times inside the overlap, such as 01:00–01:59 on the November transition, occur twice. Naive conversion picks one arbitrarily, so slots can be duplicated or missing.

Without an explicit timezone, the result also depends on the server environment, so the same request can behave differently across deployments.

Changes

backend/src/bookings/dto/create-booking.dto.ts

  • Adds a required timezone field (IANA identifier, e.g. Africa/Lagos, America/New_York).
  • Validates that it is a non-empty, valid IANA zone and rejects anything else with a 400 and a clear message. [confirm: validator used, e.g. a custom check via Intl.DateTimeFormat or class-validator's IsTimeZone]
  • Removes any implicit default (server or UTC) so callers can’t omit it by accident.

backend/src/bookings/providers/timezone-slot-generator.provider.ts

  • Slot generation now requires an explicit timezone argument and throws if it is missing or invalid.
  • Converts each local wall-clock time to an instant using the zone’s offset for that date, not a single offset computed once per request.
  • Handles the two transition cases explicitly:
    • Spring-forward gap: Slots whose local start falls in the nonexistent hour are skipped, so no invalid slot is offered. Slots after the gap keep their correct local times. [confirm: skip vs. shift behaviour]
    • Fall-back overlap: Each repeated local time is resolved deterministically, so slots are neither duplicated nor dropped. [confirm: first occurrence, or both occurrences as distinct instants]
  • Slot duration is measured in real elapsed time, so a “9–5” day is 7 hours on a spring-forward day and 9 hours on a fall-back day when hours span the transition.
  • All output instants are UTC. Only the interpretation of the input is timezone-aware.

backend/src/bookings/providers/timezone-slot-generator.provider.spec.ts

  • Adds tests for the cases below.

Test coverage

Scenario | What it verifies -- | -- Missing timezone | Generation throws or the DTO rejects the request Invalid timezone string | Rejected with a clear error Normal day (no transition) | Local hours map to the expected UTC instants Spring forward | No slot starts in the nonexistent hour; surrounding slots map to the correct instants; total slot count matches the shorter day Fall back | The repeated hour is handled deterministically with no duplicates or gaps; instants stay strictly increasing; total slot count matches the longer day Zone with no DST (e.g. Africa/Lagos) | Behaviour is unchanged across the same calendar dates Southern-hemisphere zone (optional) | Transition dates are reversed but handled the same way

Test dates are fixed (for example, the 2026 US transitions on 8 March and 1 November) so the tests don’t depend on when they run.

Acceptance criteria

  • Slot generation requires an explicit timezone
  • Tests cover spring-forward transitions
  • Tests cover fall-back transitions

Breaking change / rollout notes

  • API contract: Clients creating bookings must now send timezone. Requests without it will fail validation. Frontend callers and any integrations need updating before or alongside this deploy. [confirm: callers affected]
  • No database migration. [confirm: stored values are already UTC instants]

How to test

  1. Run the spec: npm test -- timezone-slot-generator.provider.spec (from backend/)
  2. Manual check: request slots for America/New_York on 2026-03-08 and 2026-11-01, and confirm there is no slot in the skipped hour and no duplicates in the repeated hour.
  3. Send a booking request without timezone and with an invalid one, and confirm both return 400.

Reviewer notes

  • Please check the gap and overlap policies (skip vs. shift, first vs. both occurrences) against product expectations, since this is where reasonable behaviour can differ.
  • Scope is limited to the three files listed in the issue.

Closes #604
Closes #605
Closes #600
Closes #606

@Qoder-Voidd
Qoder-Voidd merged commit 11374b1 into Akazajan:main Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants