Skip to content

Advanced date override handling and timezone compatibility improvements#5

Open
akshayutture-augment wants to merge 1 commit into
date-algorithm-basefrom
date-algorithm-enhanced
Open

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

Conversation

@akshayutture-augment

@akshayutture-augment akshayutture-augment commented Nov 14, 2025

Copy link
Copy Markdown

Test 7

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Enhanced timezone handling for schedule overrides, ensuring consistent UTC-based time slot calculations across different timezones and improved accuracy of availability checking in timezone-aware scenarios.
  • Tests

    • Added test coverage for schedule override behavior across multiple timezones.

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


Replicated from ai-code-review-evaluation/cal.com-coderabbit#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>
@akshayutture-augment

Copy link
Copy Markdown
Author

@coderabbitai review

@akshayutture-augment

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 18, 2025

Copy link
Copy Markdown
✅ 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 Nov 18, 2025

Copy link
Copy Markdown

Walkthrough

The changes introduce timezone-aware slot availability checking across the scheduling system. Timezone information is propagated through override calculations, availability checks, and slot generation logic. Type definitions are enhanced to support timezone metadata on time ranges, and test coverage validates timezone-consistent UTC slot emission.

Changes

Cohort / File(s) Summary
Test Coverage
apps/web/test/lib/getSchedule.test.ts
Added new test case under "Check for Date overrides" scenario to verify UTC time slot consistency across different timezones (+6:00 offset)
Override Calculation
packages/lib/slots.ts
Replaced simple UTC hour/minute calculation with timezone-aware adjustment; now computes organizer and invitee UTC offsets and applies offset differences to override start and end times
Type Definitions
packages/types/schedule.d.ts
Added optional timeZone?: string field to TimeRange interface to carry timezone metadata
Availability Checking
packages/trpc/server/routers/viewer/slots.ts
Major refactoring: enhanced checkIfIsAvailable function with dateOverrides, workingHours, and organizerTimeZone parameters; added date override alignment validation; propagated timezone information through slot generation, filtering, and availability checks for both fixed and non-fixed hosts; added WorkingHours type import from @calcom/types/schedule

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant getSchedule
    participant checkIfIsAvailable
    participant Availability Store

    Caller->>getSchedule: request with timezone info
    Note over getSchedule: Propagate organizerTimeZone
    getSchedule->>getSchedule: Augment dateOverrides with<br/>userId & timeZone

    loop For each time slot
        getSchedule->>checkIfIsAvailable: slot, organizerTimeZone,<br/>dateOverrides, workingHours
        
        alt Slot aligned with override
            checkIfIsAvailable->>checkIfIsAvailable: Apply timezone offset<br/>adjustment to override times
            checkIfIsAvailable-->>getSchedule: Valid (within override)
        else Slot outside working hours
            checkIfIsAvailable->>checkIfIsAvailable: Check workingHours<br/>constraints
            checkIfIsAvailable-->>getSchedule: Valid/Invalid
        else Slot not in override
            checkIfIsAvailable->>Availability Store: Check availability
            Availability Store-->>checkIfIsAvailable: availability data
            checkIfIsAvailable-->>getSchedule: Valid/Invalid
        end
    end

    getSchedule-->>Caller: Available time slots<br/>(UTC-normalized)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Key areas requiring attention:

  • Timezone offset calculation logic in packages/lib/slots.ts — verify UTC offset computation and application to override times is mathematically correct across DST boundaries
  • Propagation of organizerTimeZone throughout packages/trpc/server/routers/viewer/slots.ts — ensure timezone context flows consistently from eventType/schedule/user data through all slot filtering and availability checks
  • Date override alignment logic — confirm boundary conditions and edge cases for slot-to-override matching with timezone adjustments
  • Integration with workingHours checks — verify interaction between new workingHours parameter validation and existing availability logic

Poem

🐰 Across time zones our slots now roam,
With offsets calculated, no slot displaced from home,
UTC echoes the same through +6 or -5,
Schedules synchronized, calendars thrive! ✨

Pre-merge checks and finishing touches

✅ 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 summarizes the main changes: timezone-aware date override handling improvements across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch date-algorithm-enhanced

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

@akshayutture-augment

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 21, 2025

Copy link
Copy Markdown
✅ 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 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/trpc/server/routers/viewer/slots.ts (1)

75-152: Fix date override equality and working-hours end-time logic in checkIfIsAvailable

Two critical correctness issues confirmed:

  1. Dayjs equality using === (line 114)
    Dayjs === checks object identity, not date value — two distinct Dayjs instances with the same moment are never ===. The condition if (dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset, "minutes")) will never be true and must use .isSame() instead.

  2. Working-hours end-time ignores event length (lines 139–144)
    Both start and end are computed identically from slotStartTime, while slotEndTime (correctly defined at line 99) is never used for the end calculation. A slot extending beyond workingHour.endTime will not be caught.

Apply the provided diff to fix both issues, and consider refactoring timezone handling to consistently use .tz(organizerTimeZone) to avoid subtle off-by-one-day issues around DST.

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

210-246: Verify override offset math and handle missing override.timeZone defensively

The new override mapping:

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(),
  };
});

concerns:

  1. Fragility if override.timeZone is undefined
    TimeRange.timeZone is optional, and getSlots is exported; other callers might pass overrides without timeZone. In that case, dayjs(...).tz(override.timeZone) will fall back to a default zone, which is likely not the organizer’s actual tz and will skew offsets.

    A safer pattern is to fall back to organizerTimeZone (or even the invitee tz) when override.timeZone is missing:

  • const organizerUtcOffset = dayjs(override.start.toString()).tz(override.timeZone).utcOffset();
  • const organizerUtcOffset = dayjs(override.start.toString())
  • .tz(override.timeZone || organizerTimeZone)
  • .utcOffset();

2. **Offset arithmetic is hard to reason about**  
You first derive an offset difference (`inviteeUtcOffset - organizerUtcOffset`) and then apply it to `dayjs(override.start).utc()`. This is non-obvious and easy to get wrong, especially around DST transitions.

A more direct and self-explanatory approach is to compute the override start/end directly in the invitee’s timezone and then take hour/minute, e.g.:

```ts
const startInInviteeTz = dayjs(override.start)
  .tz(override.timeZone || organizerTimeZone)
  .tz(timeZone);
const endInInviteeTz = dayjs(override.end)
  .tz(override.timeZone || organizerTimeZone)
  .tz(timeZone);

const startTime = startInInviteeTz.hour() * 60 + startInInviteeTz.minute();
const endTime = endInInviteeTz.hour() * 60 + endInInviteeTz.minute();

This makes it explicit that we’re expressing the override window in the invitee’s local day, and avoids manual offset math.

Given how central this is to availability, I’d strongly recommend adding/expanding tests that cover:

  • Organizer and invitee in different non-DST timezones (already partially covered).
  • A DST transition day where organizer and invitee observe DST differently.

to validate that the minute-of-day windows line up as expected.

📜 Review details

Configuration used: CodeRabbit UI

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 (1 hunks)
  • packages/lib/slots.ts (1 hunks)
  • packages/trpc/server/routers/viewer/slots.ts (9 hunks)
  • packages/types/schedule.d.ts (1 hunks)
🔇 Additional comments (6)
packages/types/schedule.d.ts (1)

1-6: TimeRange.timeZone addition matches new override usage

Adding timeZone?: string on TimeRange aligns with how date overrides are now consumed (e.g., in packages/lib/slots.ts) and keeps it optional for backward compatibility. No change needed.

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

22-22: Importing WorkingHours type is consistent with new usage

The new WorkingHours import correctly types the workingHours parameter in checkIfIsAvailable and keeps this helper aligned with @calcom/types/schedule.


483-499: Per-user organizerTimeZone in availability checks

In both fixed-host and loose-host paths you now pass the user’s own timezone into checkIfIsAvailable:

const isAvailable = checkIfIsAvailable({
  time: slot.time,
  ...schedule,
  ...availabilityCheckProps,
  organizerTimeZone: schedule.timeZone,
});



return checkIfIsAvailable({
  time: slot.time,
  ...userSchedule,
  ...availabilityCheckProps,
  organizerTimeZone: userSchedule.timeZone,
});

This makes the date override logic in checkIfIsAvailable user-specific, which is what we want for heterogeneous teams. Once the bugs in that helper are fixed, this wiring looks correct.

Also applies to: 505-523


579-586: checkIfIsAvailable call for selected-slot busy times ignores overrides by design

Here you:

const userSchedule = userAvailability.find(({ user: { id: userId } }) => userId === slotUserId);

return checkIfIsAvailable({
  time: slot.time,
  busy,
  ...availabilityCheckProps,
  organizerTimeZone: userSchedule?.timeZone,
});

By omitting workingHours and dateOverrides, this path reuses existing availability-filtered slots and only layers on additional busy intervals from selectedSlots, which is a sensible separation of concerns.

Once checkIfIsAvailable’s override/working-hours logic is corrected, this call site should behave as intended.


410-417: Add runtime guard for missing organizerTimeZone

The organizerTimeZone derivation at lines 439–440 can result in undefined (all three fallback sources falsy), but the GetSlots type requires it as a non-optional string. Currently there is no guard.

Add the suggested check:

const organizerTimeZone =
  eventType.timeZone || eventType?.schedule?.timeZone || userAvailability?.[0]?.timeZone;
if (!organizerTimeZone) {
  throw new TRPCError({
    code: "INTERNAL_SERVER_ERROR",
    message: "Missing organizer timezone for getSchedule",
  });
}

Additionally, verify in packages/lib/slots.ts line 212 that the override adjustment (override.timeZone) handles legacy data safely. The new decorator (lines 410–414) now adds timeZone from availability, but edge cases with missing timeZone on overrides should be tested with mixed-host teams and older events.

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

742-805: New date-override test across timezones validates core behavior

The added block:

const scheduleForEventOnADayWithDateOverrideDifferentTimezone = await getSchedule({
  eventTypeId: 1,
  eventTypeSlug: "",
  startTime: `${plus1DateString}T18:30:00.000Z`,
  endTime: `${plus2DateString}T18:29:59.999Z`,
  timeZone: Timezones["+6:00"],
}, ctx);

// it should return the same as this is the utc time
expect(scheduleForEventOnADayWithDateOverrideDifferentTimezone).toHaveTimeSlots(
  ["08:30:00.000Z", "09:30:00.000Z", "10:30:00.000Z", "11:30:00.000Z"],
  { dateString: plus2DateString }
);

nicely asserts that, with the same underlying override, changing invitee timezone from +5:30 to +6:00 leaves the emitted UTC slots unchanged. This is exactly the invariant your new override logic is supposed to guarantee.

If you want to further tighten this, you could also assert equality of the two full slots structures, but the current expectation is already strong and focused.

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