Skip to content

Advanced date override handling and timezone compatibility improvements#5

Draft
ShashankFC wants to merge 1 commit into
date-algorithm-basefrom
date-algorithm-enhanced
Draft

Advanced date override handling and timezone compatibility improvements#5
ShashankFC wants to merge 1 commit into
date-algorithm-basefrom
date-algorithm-enhanced

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Dec 31, 2025

Copy link
Copy Markdown
Collaborator

Test 7nn

Summary by CodeRabbit

  • New Features

    • Improved timezone awareness for date overrides and availability calculations
    • Added working hours validation to availability checks
    • Enhanced date override processing to properly handle timezone offsets between organizers and invitees
  • Tests

    • Added test verifying timezone consistency across schedule calculations

✏️ Tip: You can customize this high-level summary in your review settings.

nn---n*Replicated from [ai-code-review-evaluation/cal.com-coderabbit#7](https://github.com/ai-code-review-evaluation/cal.com-coderabbit/pull/7)*

* fix date override for fixed round robin + time zone in date override

* check if slot is within working hours of fixed hosts

* add test for date override in different time zone

* fix date overrides for not fixed hosts (round robin)

* code clean up

* fix added test

* use the correct timezone of user for date overrides

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jan 8, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Timezone-aware scheduling availability checking is implemented across multiple modules. Date override computation now adjusts for timezone differences between organizer and invitee. The availability check function is enhanced to accept and validate against date overrides and working hours parameters with timezone context.

Changes

Cohort / File(s) Summary
Timezone-aware override computation
packages/lib/slots.ts
Modified override calculation to compute organizer and invitee UTC offsets using dayjs-tz, then derive and apply a local offset (inviteeUtcOffset - organizerUtcOffset) to startTime and endTime values, replacing simple UTC hour/minute extraction.
Availability check enhancement
packages/trpc/server/routers/viewer/slots.ts
Updated checkIfIsAvailable signature to accept dateOverrides, workingHours, and organizerTimeZone parameters. Implemented logic to validate slots against date overrides with timezone adjustments and working hours constraints. Threaded organizerTimeZone through getTimeSlots and availability check calls. Augmented flattened dateOverrides with userId and timeZone fields.
Type definitions
packages/types/schedule.d.ts
Added optional timeZone?: string property to TimeRange type.
Test coverage
apps/web/test/lib/getSchedule.test.ts
Added test case verifying timezone handling consistency by evaluating schedule with different timezone (+6:00) and asserting UTC time slot consistency.

Sequence Diagram(s)

sequenceDiagram
    participant Router as Slots Router
    participant Slots as slots.ts
    participant Availability as Availability Check
    participant TZ as Timezone Calc

    Router->>Slots: getTimeSlots(organizerTimeZone)
    Slots->>TZ: Get organizer UTC offset
    Slots->>TZ: Get invitee UTC offset
    TZ->>Slots: Return offset delta
    Slots->>Slots: Apply offset to dateOverrides
    Slots->>Availability: checkIfIsAvailable(time, busy, dateOverrides, workingHours, organizerTimeZone)
    Availability->>Availability: Validate slot within dateOverride range
    Availability->>Availability: Check slot within workingHours
    Availability-->>Slots: Return slot validity
    Slots-->>Router: Return filtered time slots
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Timezones aligned, schedules now bright,
Organizers and invitees in sync, what a sight!
UTC offsets dance in the code,
Availability checks on a timezone road,
Schedules that work across the globe, day and night! 🌍✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: advanced date override handling and timezone compatibility improvements across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In @packages/lib/slots.ts:
- Around line 211-225: The code uses override.timeZone which is optional and may
be undefined when computing organizerUtcOffset in the activeOverrides flatMap;
update the computation to fall back to organizerTimeZone when override.timeZone
is missing (i.e., use override.timeZone || organizerTimeZone) and apply the same
fallback wherever override.timeZone is passed into dayjs(...).tz() for
calculating inviteeUtcOffset/offset so timezone conversions are safe; ensure you
reference the activeOverrides flatMap, organizerUtcOffset, inviteeUtcOffset, and
offset calculations when making the change.

In @packages/trpc/server/routers/viewer/slots.ts:
- Around line 102-135: The find() callback in the dateOverrides check
miscompares Dayjs objects with === and mixes returns with side effects via
dateOverrideExist; fix by replacing the === comparison with
dayjs(...).isSame(...) for value equality, compute adjustedStart and adjustedEnd
once inside the dateOverrides.find callback (using organizerTimeZone
adjustments), and ensure the callback always returns a boolean (return true when
the slot is outside the override: adjustedStart.isSame(adjustedEnd) OR
slotEndTime.isSameOrBefore(adjustedStart) OR slotStartTime.isAfter(adjustedEnd);
otherwise return false). Remove or stop relying on the side-effect variable
dateOverrideExist—use the boolean result of dateOverrides.find (or a dedicated
some()/every() call) to decide the subsequent returns instead of setting
dateOverrideExist inside the callback.
- Around line 137-151: The callback that checks workingHours incorrectly
computes end using slotStartTime, making start and end identical; update the
calculation of end to use slotEndTime (e.g., compute end = slotEndTime.hour() *
60 + slotEndTime.minute()) inside the workingHours.find callback and ensure the
predicate returns a boolean (true when start < workingHour.startTime || end >
workingHour.endTime) so slots that extend past workingHour.endTime are correctly
flagged.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4fe69d and 3ef3284.

📒 Files selected for processing (4)
  • apps/web/test/lib/getSchedule.test.ts
  • packages/lib/slots.ts
  • packages/trpc/server/routers/viewer/slots.ts
  • packages/types/schedule.d.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/lib/slots.ts (1)
apps/web/lib/clock.ts (1)
  • timeZone (54-54)
🔇 Additional comments (8)
packages/types/schedule.d.ts (1)

1-6: LGTM!

The addition of the optional timeZone property to TimeRange is a clean, backward-compatible change that enables timezone-aware scheduling across the codebase.

packages/trpc/server/routers/viewer/slots.ts (6)

75-94: LGTM on signature changes.

The extended function signature properly handles backward compatibility with default values for new parameters.


410-416: LGTM!

The flattening logic correctly propagates userId and timeZone from the user's availability context into each date override entry.


439-440: Verify fallback behavior when all timezone sources are undefined.

The fallback chain is reasonable, but if eventType.timeZone, eventType?.schedule?.timeZone, and userAvailability?.[0]?.timeZone are all undefined, organizerTimeZone will be undefined. Verify that downstream consumers (e.g., getTimeSlots, checkIfIsAvailable) handle this gracefully.


488-498: LGTM!

The organizerTimeZone is correctly derived from each user's schedule timezone when checking availability for fixed hosts.


513-518: LGTM!

Consistent propagation of organizerTimeZone for loose (round-robin) host availability checks.


579-586: LGTM!

The user schedule lookup and timezone propagation for selected slots availability check is implemented correctly.

apps/web/test/lib/getSchedule.test.ts (1)

787-804: LGTM!

Good test coverage for timezone consistency. This correctly verifies that UTC-based slot times remain stable when the invitee's timezone differs from the organizer's timezone, which is essential for the date override handling.

Comment thread packages/lib/slots.ts
Comment on lines +211 to +225
const overrides = activeOverrides.flatMap((override) => {
const organizerUtcOffset = dayjs(override.start.toString()).tz(override.timeZone).utcOffset();
const inviteeUtcOffset = dayjs(override.start.toString()).tz(timeZone).utcOffset();
const offset = inviteeUtcOffset - organizerUtcOffset;

return {
userIds: override.userId ? [override.userId] : [],
startTime:
dayjs(override.start).utc().add(offset, "minute").hour() * 60 +
dayjs(override.start).utc().add(offset, "minute").minute(),
endTime:
dayjs(override.end).utc().add(offset, "minute").hour() * 60 +
dayjs(override.end).utc().add(offset, "minute").minute(),
};
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for dateOverrides and where they are populated/created
rg -n "dateOverrides|activeOverrides" packages/lib/slots.ts -B 3 -A 3 | head -150

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 1123


🏁 Script executed:

#!/bin/bash
# Search for override type definition or interface
rg -n "override.*timeZone|interface.*override|type.*override" packages/lib/ -i | head -50

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 440


🏁 Script executed:

#!/bin/bash
# Find where activeOverrides is assigned in slots.ts
rg -n "activeOverrides\s*=" packages/lib/slots.ts -B 2 -A 2

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 365


🏁 Script executed:

#!/bin/bash
# Search for where dateOverrides or override objects are created
rg -n "dateOverrides\s*=|new.*Override|Override\s*\{" packages/lib/ -A 5 | head -100

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 450


🏁 Script executed:

#!/bin/bash
# Find and examine the TimeRange type definition
find . -path "*/types/schedule*" -type f | head -20

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 150


🏁 Script executed:

#!/bin/bash
# Search for TimeRange type definition
rg -n "type TimeRange|interface TimeRange" --type ts | head -20

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 202


🏁 Script executed:

#!/bin/bash
# Read the TimeRange type definition from both files
echo "=== packages/types/schedule.d.ts ===" && cat -n packages/types/schedule.d.ts

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 820


🏁 Script executed:

#!/bin/bash
# Also check the other schedule.ts file
echo "=== apps/web/lib/types/schedule.ts ===" && cat -n apps/web/lib/types/schedule.ts

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 728


🏁 Script executed:

#!/bin/bash
# Check which @calcom package/alias is being used in the project
find . -name "tsconfig.json" -o -name "tsconfig.*.json" | xargs grep -l "calcom" | head -5

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 232


🏁 Script executed:

#!/bin/bash
# Check the actual import source for @calcom/types
cat -n tsconfig.json 2>/dev/null || cat -n apps/web/tsconfig.json 2>/dev/null || cat -n packages/tsconfig.json 2>/dev/null | head -50

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 854


🏁 Script executed:

#!/bin/bash
# Look for path mappings or package.json setup
rg -n '"@calcom' tsconfig.json -A 1 | head -20

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 140


🏁 Script executed:

#!/bin/bash
# Let's verify what happens when dayjs().tz() is called with undefined
# Create a test to check dayjs behavior
node << 'EOF'
const dayjs = require('dayjs');
require('dayjs/plugin/utc');
require('dayjs/plugin/timezone');

dayjs.extend(require('dayjs/plugin/utc'));
dayjs.extend(require('dayjs/plugin/timezone'));

console.log("Testing dayjs().tz(undefined):");
const result = dayjs('2024-01-15').tz(undefined);
console.log("Result:", result.toString());
console.log("UTC Offset:", result.utcOffset());

console.log("\nTesting dayjs().tz() with no argument:");
const result2 = dayjs('2024-01-15').tz();
console.log("Result:", result2.toString());
console.log("UTC Offset:", result2.utcOffset());
EOF

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 922


🏁 Script executed:

#!/bin/bash
# Check the exact usage context around line 212 - see if there's any validation before the filter
sed -n '200,230p' packages/lib/slots.ts

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 1382


🏁 Script executed:

#!/bin/bash
# Search for how timeZone property is used elsewhere in the codebase
rg -n "\.timeZone" packages/lib/slots.ts -B 2 -A 2

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 453


🏁 Script executed:

#!/bin/bash
# Check if there's any validation or fallback for timeZone in the codebase
rg -n "override\.timeZone|dateOverride.*timeZone" packages/lib/ -B 3 -A 3

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 617


🏁 Script executed:

#!/bin/bash
# Check how dayjs().tz() is used elsewhere in the codebase to understand expected behavior
rg -n "\.tz\(" packages/lib/slots.ts -B 1 -A 1 | head -30

Repository: AI-Code-Review-Evals/coderabbit-cal_dot_com

Length of output: 1001


Add validation or fallback for override.timeZone which is optional.

The timeZone property in TimeRange (the type for date overrides) is optional, so override.timeZone can be undefined. Passing undefined to dayjs().tz() on line 212 will not correctly handle the timezone conversion. Add a fallback to organizerTimeZone when timeZone is missing:

const organizerUtcOffset = dayjs(override.start.toString()).tz(override.timeZone || organizerTimeZone).utcOffset();
🤖 Prompt for AI Agents
In @packages/lib/slots.ts around lines 211 - 225, The code uses
override.timeZone which is optional and may be undefined when computing
organizerUtcOffset in the activeOverrides flatMap; update the computation to
fall back to organizerTimeZone when override.timeZone is missing (i.e., use
override.timeZone || organizerTimeZone) and apply the same fallback wherever
override.timeZone is passed into dayjs(...).tz() for calculating
inviteeUtcOffset/offset so timezone conversions are safe; ensure you reference
the activeOverrides flatMap, organizerUtcOffset, inviteeUtcOffset, and offset
calculations when making the change.

Comment on lines +102 to +135
//check if date override for slot exists
let dateOverrideExist = false;

if (
dateOverrides.find((date) => {
const utcOffset = organizerTimeZone ? dayjs.tz(date.start, organizerTimeZone).utcOffset() * -1 : 0;

if (
dayjs(date.start).add(utcOffset, "minutes").format("YYYY MM DD") ===
slotStartTime.format("YYYY MM DD")
) {
dateOverrideExist = true;
if (dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset, "minutes")) {
return true;
}
if (
slotEndTime.isBefore(dayjs(date.start).add(utcOffset, "minutes")) ||
slotEndTime.isSame(dayjs(date.start).add(utcOffset, "minutes"))
) {
return true;
}
if (slotStartTime.isAfter(dayjs(date.end).add(utcOffset, "minutes"))) {
return true;
}
}
})
) {
// slot is not within the date override
return false;
}

if (dateOverrideExist) {
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical bug: Dayjs object comparison uses === instead of .isSame().

Line 114 compares two Dayjs objects using ===, which checks reference equality rather than value equality. This will always be false unless the objects are the same instance.

Additionally, the find() callback has inconsistent return behavior — it sometimes returns true and sometimes returns nothing (implicitly undefined), relying on side effects via dateOverrideExist. This makes the logic harder to follow and maintain.

🐛 Proposed fix for the Dayjs comparison
-        if (dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset, "minutes")) {
+        if (dayjs(date.start).add(utcOffset, "minutes").isSame(dayjs(date.end).add(utcOffset, "minutes"))) {
           return true;
         }
🤖 Prompt for AI Agents
In @packages/trpc/server/routers/viewer/slots.ts around lines 102 - 135, The
find() callback in the dateOverrides check miscompares Dayjs objects with ===
and mixes returns with side effects via dateOverrideExist; fix by replacing the
=== comparison with dayjs(...).isSame(...) for value equality, compute
adjustedStart and adjustedEnd once inside the dateOverrides.find callback (using
organizerTimeZone adjustments), and ensure the callback always returns a boolean
(return true when the slot is outside the override:
adjustedStart.isSame(adjustedEnd) OR slotEndTime.isSameOrBefore(adjustedStart)
OR slotStartTime.isAfter(adjustedEnd); otherwise return false). Remove or stop
relying on the side-effect variable dateOverrideExist—use the boolean result of
dateOverrides.find (or a dedicated some()/every() call) to decide the subsequent
returns instead of setting dateOverrideExist inside the callback.

Comment on lines +137 to +151
//if no date override for slot exists check if it is within normal work hours
if (
workingHours.find((workingHour) => {
if (workingHour.days.includes(slotStartTime.day())) {
const start = slotStartTime.hour() * 60 + slotStartTime.minute();
const end = slotStartTime.hour() * 60 + slotStartTime.minute();
if (start < workingHour.startTime || end > workingHour.endTime) {
return true;
}
}
})
) {
// slot is outside of working hours
return false;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical bug: end variable incorrectly uses slotStartTime instead of slotEndTime.

Line 142 computes end using slotStartTime, making it identical to start. This defeats the purpose of checking if the slot's end time exceeds working hours. Slots that extend past workingHour.endTime will incorrectly be considered valid.

🐛 Proposed fix
   if (
     workingHours.find((workingHour) => {
       if (workingHour.days.includes(slotStartTime.day())) {
         const start = slotStartTime.hour() * 60 + slotStartTime.minute();
-        const end = slotStartTime.hour() * 60 + slotStartTime.minute();
+        const end = slotEndTime.hour() * 60 + slotEndTime.minute();
         if (start < workingHour.startTime || end > workingHour.endTime) {
           return true;
         }
       }
     })
   ) {
🤖 Prompt for AI Agents
In @packages/trpc/server/routers/viewer/slots.ts around lines 137 - 151, The
callback that checks workingHours incorrectly computes end using slotStartTime,
making start and end identical; update the calculation of end to use slotEndTime
(e.g., compute end = slotEndTime.hour() * 60 + slotEndTime.minute()) inside the
workingHours.find callback and ensure the predicate returns a boolean (true when
start < workingHour.startTime || end > workingHour.endTime) so slots that extend
past workingHour.endTime are correctly flagged.

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.

2 participants