Skip to content

Comments

feat: implement DST-aware timezone conversion and handling#24436

Closed
tusharchawre wants to merge 3 commits intocalcom:mainfrom
tusharchawre:fix/dst-timezone-discrepancy-booking-times
Closed

feat: implement DST-aware timezone conversion and handling#24436
tusharchawre wants to merge 3 commits intocalcom:mainfrom
tusharchawre:fix/dst-timezone-discrepancy-booking-times

Conversation

@tusharchawre
Copy link

What does this PR do?

Fixes calcom#24350

This PR resolves a critical daylight saving time (DST) issue where booking times displayed incorrectly during DST transitions, causing ±1 hour discrepancies between the intended booking time and the displayed time.

Problem

When users book meetings around DST transition periods (e.g., Europe/Berlin end of March/October), the frontend displayed incorrect times while the backend correctly stored UTC timestamps. This led to:

  • Confusion for users booking near DST transitions
  • Potential missed meetings due to time discrepancies
  • Inconsistent time displays across the booking flow

Solution

Implemented comprehensive DST-aware timezone handling:

  1. Enhanced DST detection and handling in packages/lib/date-ranges.ts
  2. Improved timezone conversion utilities with new packages/lib/timezone-conversion.ts
  3. Fixed frontend time display in packages/features/bookings/components/AvailableTimes.tsx
  4. Updated core time formatting in packages/lib/dayjs/index.ts

@tusharchawre tusharchawre requested a review from a team as a code owner October 13, 2025 21:40
@vercel
Copy link

vercel bot commented Oct 13, 2025

@tusharchawre is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link

CLAassistant commented Oct 13, 2025

CLA assistant check
All committers have signed the CLA.

@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Oct 13, 2025
@graphite-app graphite-app bot requested a review from a team October 13, 2025 21:40
@github-actions github-actions bot added booking-page area: booking page, public booking page, booker Medium priority Created by Linear-GitHub Sync reactive⚡︎ 🐛 bug Something isn't working labels Oct 13, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 13, 2025

Walkthrough

This PR centralizes DST-aware timezone handling: it adds a new module packages/lib/timezone-conversion.ts with functions to convert UTC times to timezones, format times with DST awareness, detect DST transition days, and get timezone offsets. It updates packages/lib/dayjs/index.ts to use formatTimeWithDST, revises local-midnight alignment in packages/lib/date-ranges.ts to compute and apply offset differences robustly across DST, and adjusts slot time conversion and imports in packages/features/bookings/components/AvailableTimes.tsx. No existing exported signatures were removed; the new module exposes several utility functions.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the primary feature addition of DST-aware timezone conversion and handling and directly reflects the scope of changes introduced in the pull request.
Description Check ✅ Passed The pull request description clearly describes the DST issue being addressed, outlines the impact of incorrect time displays, and summarizes the implemented solution matching the code changes.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d969dc4 and e3710a9.

📒 Files selected for processing (1)
  • packages/lib/dayjs/index.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/lib/dayjs/index.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot dosubot bot added bookings area: bookings, availability, timezones, double booking platform Anything related to our platform plan labels Oct 13, 2025
@graphite-app graphite-app bot requested a review from a team October 13, 2025 21:41
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/lib/slots.ts (1)

134-139: Consider using the new DST-aware utility function.

The comment states "Use DST-aware conversion for accurate timezone handling," but the code continues to use the standard slotStartTime.tz(timeZone) approach. For consistency with the DST-aware improvements introduced in this PR, consider using convertUTCToTimezone from the new packages/lib/timezone-conversion.ts module.

Apply this diff to use the new DST-aware utility:

+import { convertUTCToTimezone } from "@calcom/lib/timezone-conversion";
+
 // Convert to target timezone BEFORE checking if rounding is needed
 // This ensures we check minute alignment in the local timezone, not UTC
 // This prevents issues with half-hour offset timezones like Asia/Kolkata (GMT+5:30)
 // Use DST-aware conversion for accurate timezone handling
-slotStartTime = slotStartTime.tz(timeZone);
+slotStartTime = convertUTCToTimezone(slotStartTime, timeZone);
packages/features/bookings/components/AvailableTimes.tsx (1)

118-120: Consider using the new DST-aware utility function.

The DST-aware conversion approach using dayjs.utc(slot.time).tz(timezone) is correct. However, for consistency with the new DST-aware utilities introduced in this PR, consider using convertUTCToTimezone from packages/lib/timezone-conversion.ts, which encapsulates this logic with additional validation and fallback handling.

Apply this diff:

+import { convertUTCToTimezone } from "@calcom/lib/timezone-conversion";
+
-  // Use DST-aware timezone conversion for accurate display during DST transitions
-  const slotTimeUTC = dayjs.utc(slot.time);
-  const computedDateWithUsersTimezone = slotTimeUTC.tz(timezone);
+  // Use DST-aware timezone conversion for accurate display during DST transitions
+  const computedDateWithUsersTimezone = convertUTCToTimezone(slot.time, timezone);
packages/lib/timezone-conversion.ts (2)

34-59: Reconsider the validation logic necessity.

The validation logic at lines 48-56 checks if the converted offset matches either standard or DST offset, but:

  1. Day.js's .tz() method already handles DST transitions correctly when provided with IANA timezone identifiers
  2. The validation only logs a warning without correcting the issue
  3. This may produce false positives in edge cases (e.g., during the actual transition hour)
  4. It adds computational overhead by creating additional dayjs instances for January and July

Consider simplifying to trust Day.js's DST handling:

-  // For DST-observing timezones, we need to be more careful
   const convertedTime = utcMoment.tz(targetTimezone);
-
-  // Check if this date falls on a DST transition day
-  const year = convertedTime.year();
-
-  // Get the DST difference for this timezone
-  const dstDifference = getDSTDifference(targetTimezone);
-
-  if (dstDifference !== 0) {
-    // This timezone observes DST, so we need to ensure proper handling
-    // The dayjs library should handle most cases correctly, but we add validation
-
-    // Verify that the conversion is reasonable
-    const expectedOffset = convertedTime.utcOffset();
-    const standardOffset = dayjs.tz(`${year}-01-01T00:00:00`, targetTimezone).utcOffset();
-    const dstOffset = dayjs.tz(`${year}-07-01T00:00:00`, targetTimezone).utcOffset();
-
-    // If the current offset doesn't match either standard or DST offset,
-    // there might be an issue with the conversion
-    if (expectedOffset !== standardOffset && expectedOffset !== dstOffset) {
-      console.warn(`Unexpected timezone offset for ${targetTimezone} on ${convertedTime.format()}`);
-    }
-  }
-
   return convertedTime;

If you want to keep validation for debugging purposes, consider moving it behind a feature flag or environment variable.


115-118: Consider if this utility function is necessary.

The getTimezoneOffset function is a thin wrapper around date.tz(timezone).utcOffset(). While centralizing this logic allows for future enhancements, consider if the added indirection is worth it for such a simple operation.

If keeping this function, consider adding error handling for invalid timezones:

 export function getTimezoneOffset(date: dayjs.Dayjs, timezone: string): number {
+  if (!timezone) {
+    return date.utcOffset();
+  }
+  try {
     const dateInTimezone = date.tz(timezone);
     return dateInTimezone.utcOffset();
+  } catch (error) {
+    console.warn(`Failed to get timezone offset for ${timezone}:`, error);
+    return date.utcOffset();
+  }
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 52ecea9 and d969dc4.

📒 Files selected for processing (5)
  • packages/features/bookings/components/AvailableTimes.tsx (2 hunks)
  • packages/lib/date-ranges.ts (1 hunks)
  • packages/lib/dayjs/index.ts (2 hunks)
  • packages/lib/slots.ts (1 hunks)
  • packages/lib/timezone-conversion.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/lib/dayjs/index.ts
  • packages/lib/date-ranges.ts
  • packages/lib/timezone-conversion.ts
  • packages/lib/slots.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/lib/dayjs/index.ts
  • packages/lib/date-ranges.ts
  • packages/lib/timezone-conversion.ts
  • packages/features/bookings/components/AvailableTimes.tsx
  • packages/lib/slots.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/lib/dayjs/index.ts
  • packages/lib/date-ranges.ts
  • packages/lib/timezone-conversion.ts
  • packages/features/bookings/components/AvailableTimes.tsx
  • packages/lib/slots.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/features/bookings/components/AvailableTimes.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: hariombalhara
PR: calcom/cal.com#23918
File: packages/features/schedules/lib/use-schedule/useTimesForSchedule.test.ts:16-23
Timestamp: 2025-09-23T08:00:07.619Z
Learning: In calcom/cal.com test files, particularly packages/features/schedules/lib/use-schedule/useTimesForSchedule.test.ts, the TIMEZONE_OFFSETS mapping is intentionally limited to only UTC variants and Asia/Kolkata. This is by design and should not be flagged as incomplete in future reviews (confirmed by maintainer hariombalhara).
📚 Learning: 2025-09-23T08:00:07.619Z
Learnt from: hariombalhara
PR: calcom/cal.com#23918
File: packages/features/schedules/lib/use-schedule/useTimesForSchedule.test.ts:16-23
Timestamp: 2025-09-23T08:00:07.619Z
Learning: In calcom/cal.com test files, particularly packages/features/schedules/lib/use-schedule/useTimesForSchedule.test.ts, the TIMEZONE_OFFSETS mapping is intentionally limited to only UTC variants and Asia/Kolkata. This is by design and should not be flagged as incomplete in future reviews (confirmed by maintainer hariombalhara).

Applied to files:

  • packages/features/bookings/components/AvailableTimes.tsx
🧬 Code graph analysis (1)
packages/lib/timezone-conversion.ts (1)
packages/lib/dayjs/index.ts (2)
  • timeZoneWithDST (207-211)
  • getDSTDifference (221-225)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (9)
packages/features/bookings/components/AvailableTimes.tsx (1)

9-9: LGTM: Import consolidation.

The type import consolidation improves code organization.

packages/lib/dayjs/index.ts (2)

21-23: LGTM: Documentation improvement.

The JSDoc comment clearly indicates DST-awareness, improving code documentation.


59-59: LGTM: Simplified error handling.

The bare catch clause is appropriate when the error value is not used.

packages/lib/timezone-conversion.ts (5)

1-2: LGTM: Import statements.

The imports are correctly structured and necessary for the DST-aware functionality.


12-27: Good error handling for invalid timezones.

The function correctly handles missing or invalid timezones by falling back to UTC with appropriate warnings. The use of timeZoneWithDST to validate the timezone before conversion is a good defensive programming practice.


29-32: LGTM: Optimization for non-DST timezones.

The early return for non-DST-observing timezones is a good performance optimization.


100-105: LGTM: Clean DST transition detection.

The function correctly identifies DST transition days by comparing UTC offsets at the start and end of the day. The early return for non-DST timezones is a good optimization.


71-91: No circular dependencies detected
Confirmed timezone-conversion.ts imports from dayjs modules and dayjs/index.ts does not import timezone-conversion.

packages/lib/date-ranges.ts (1)

70-76: Add unit tests for DST transition days: Cover spring-forward, fall-back, and non-standard DST offsets (30- and 45-minute shifts) to validate this alignment logic in packages/lib/date-ranges.ts (lines 70–76).

Copy link
Member

@dhairyashiil dhairyashiil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please attach before and after loom video, making it draft until then.

@dhairyashiil dhairyashiil marked this pull request as draft October 14, 2025 20:15
@github-actions
Copy link
Contributor

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

@github-actions github-actions bot added the Stale label Oct 27, 2025
@github-actions
Copy link
Contributor

This PR has been closed due to inactivity. Please feel free to reopen it if you'd like to continue the work.

@github-actions github-actions bot closed this Nov 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

booking-page area: booking page, public booking page, booker bookings area: bookings, availability, timezones, double booking 🐛 bug Something isn't working community Created by Linear-GitHub Sync Medium priority Created by Linear-GitHub Sync platform Anything related to our platform plan reactive⚡︎ size/L Stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] #17314 – Booking page displays incorrect time slot due to daylight savings offset

3 participants