Skip to content

Conversation

hemantmm
Copy link
Contributor

@hemantmm hemantmm commented Aug 9, 2025

closes #22808

What does this PR do?

Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).

Video Demo (if applicable):

Screen.Recording.2025-08-09.at.20.25.06.mov

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Are there environment variables that should be set?
  • What are the minimal test data to have?
  • What is expected (happy path) to have (input and output)?
  • Any other important info that could help to test that PR

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

@graphite-app graphite-app bot requested a review from a team August 9, 2025 14:48
@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Aug 9, 2025
Copy link

vercel bot commented Aug 9, 2025

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

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Aug 9, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Replaces the previous per-end-time same-end merge with a global adjacency-aware overlap/merge pass in packages/lib/date-ranges.ts. Each candidate now precomputes startValue/endValue and is checked against all existing ranges for overlap or adjacency using ADJACENCY_THRESHOLD_MS = 15 minutes. When overlaps/adjacencies are found, old ranges are deleted and a merged range spanning the earliest start and latest end is upserted; endTimeToKeyMap buckets are updated using deduplicated key sets. No exported/public API signatures or declarations were changed.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added bookings area: bookings, availability, timezones, double booking Medium priority Created by Linear-GitHub Sync 🐛 bug Something isn't working labels Aug 9, 2025
Copy link

graphite-app bot commented Aug 9, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (08/09/25)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add community label" took an action on this PR • (08/09/25)

1 label was added to this PR based on Keith Williams's automation.

@hemantmm
Copy link
Contributor Author

hemantmm commented Aug 9, 2025

Todo:

  • Video publishing
  • Unit test.

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: 2

🧹 Nitpick comments (1)
packages/lib/date-ranges.ts (1)

128-176: Heavy Day.js use inside nested loops

The newly-added merge loop performs multiple dayjs.min/max calls and valueOf() conversions per existing range. Per team guidelines, prefer native timestamps or Day.js .utc() only when needed in performance-critical loops. Caching numeric epoch values before the loop will cut object allocations significantly.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce6969d and f0c02a9.

📒 Files selected for processing (1)
  • packages/lib/date-ranges.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/date-ranges.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/date-ranges.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{ts,tsx} : Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js `.utc()` in hot paths like loops
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{ts,tsx} : Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js `.utc()` in hot paths like loops

Applied to files:

  • packages/lib/date-ranges.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Codacy Static Code Analysis

Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0c02a9 and d24fe48.

📒 Files selected for processing (1)
  • packages/lib/date-ranges.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/date-ranges.test.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/date-ranges.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Codacy Static Code Analysis
🔇 Additional comments (1)
packages/lib/date-ranges.test.ts (1)

153-153: Unexpected truncation of last-day availability to 18:29:59.999Z — please verify DST/month-end boundary handling

This deviates from the same test’s pattern (09:00–17:00 local => 14:00–22:00Z) and introduces a 999ms boundary cap. This looks like a boundary-cropping side effect rather than intended behavior.

  • If month-end clamping is desired, assert it in a principled way (derive from item endTime and dateTo) rather than hard-coding a magic 18:29:59.999Z.
  • Also consider creating dateFrom/dateTo with explicit tz to avoid implicit-local ambiguities.

Suggested correction:

-        end: dayjs("2023-11-30T18:29:59.999Z").tz(timeZone),
+        end: dayjs("2023-11-30T22:00:00.000Z").tz(timeZone),

Would you confirm with a minimal reproduction that processWorkingHours is expected to shorten the 2023-11-30 window, and if so, document why that single day differs from the rest?

Copy link
Contributor

@kart1ka kart1ka left a comment

Choose a reason for hiding this comment

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

Looks like there is a test failing. Could you please fix the failing test and also address the coderabbit suggestions?

@kart1ka kart1ka marked this pull request as draft August 11, 2025 02:57
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
@hemantmm hemantmm marked this pull request as ready for review August 11, 2025 17:11
@hemantmm
Copy link
Contributor Author

hemantmm commented Aug 11, 2025

@kart1ka's unit test has been failing in my branch, but upon cross-checking with the main branch, a failing unit test case was found.

Because when I tested in local development, it was working fine

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: 0

♻️ Duplicate comments (2)
packages/lib/date-ranges.ts (2)

128-169: O(N²) complexity will degrade performance with many intervals

This nested loop iterates through all existing results for each new range, resulting in O(N²) complexity. Combined with Day.js object creation on every iteration (lines 138-139), this becomes a performance bottleneck as the number of intervals grows.

Consider:

  1. Using a sorted data structure (e.g., interval tree or sorted array with binary search) to find overlaps in O(log N)
  2. Pre-computing numeric timestamps with .valueOf() before the loop to avoid repeated Day.js instantiation
  3. Using native Date comparisons in the critical path
-    let foundOverlapOrAdjacent = false;
-    for (const existingKey in results) {
-      const existingRange = results[existingKey];
-
-      const isOverlapping =
-        (startResult.valueOf() <= existingRange.end.valueOf() &&
-          endResult.valueOf() >= existingRange.start.valueOf()) ||
-        Math.abs(startResult.valueOf() - existingRange.end.valueOf()) <= 15 * 60 * 1000 ||
-        Math.abs(endResult.valueOf() - existingRange.start.valueOf()) <= 15 * 60 * 1000;
+    // Pre-compute timestamps outside the loop
+    const startValue = startResult.valueOf();
+    const endValue = endResult.valueOf();
+    
+    let foundOverlapOrAdjacent = false;
+    // TODO: Replace with sorted structure for O(log N) lookup
+    for (const existingKey in results) {
+      const existingRange = results[existingKey];
+      const existingStartValue = existingRange.start.valueOf();
+      const existingEndValue = existingRange.end.valueOf();
+
+      const isOverlapping =
+        (startValue <= existingEndValue && endValue >= existingStartValue) ||
+        Math.abs(startValue - existingEndValue) <= 15 * 60 * 1000 ||
+        Math.abs(endValue - existingStartValue) <= 15 * 60 * 1000;

157-161: Duplicate keys accumulating in endTimeToKeyMap

Line 160 pushes newKey to the array without checking for duplicates, causing the same key to be added multiple times during repeated merges.

Apply the same Set-based deduplication used elsewhere in the file:

-          if (!endTimeToKeyMap.has(newKey)) {
-            endTimeToKeyMap.set(newKey, []);
-          }
-          endTimeToKeyMap.get(newKey)!.push(newKey);
+          const keySet = new Set(endTimeToKeyMap.get(newKey) || []);
+          keySet.add(newKey);
+          endTimeToKeyMap.set(newKey, Array.from(keySet));
🧹 Nitpick comments (1)
packages/lib/date-ranges.ts (1)

135-136: Consider making the 15-minute adjacency threshold configurable

The hardcoded 15-minute threshold for considering ranges as adjacent might need to be adjustable based on business requirements.

Consider extracting this as a constant or configuration parameter:

+// Threshold in milliseconds for considering ranges as adjacent
+const ADJACENCY_THRESHOLD_MS = 15 * 60 * 1000;
+
 const isOverlapping =
   (startResult.valueOf() <= existingRange.end.valueOf() &&
     endResult.valueOf() >= existingRange.start.valueOf()) ||
-  Math.abs(startResult.valueOf() - existingRange.end.valueOf()) <= 15 * 60 * 1000 ||
-  Math.abs(endResult.valueOf() - existingRange.start.valueOf()) <= 15 * 60 * 1000;
+  Math.abs(startResult.valueOf() - existingRange.end.valueOf()) <= ADJACENCY_THRESHOLD_MS ||
+  Math.abs(endResult.valueOf() - existingRange.start.valueOf()) <= ADJACENCY_THRESHOLD_MS;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d24fe48 and 8ffa6cc.

📒 Files selected for processing (2)
  • packages/lib/date-ranges.test.ts (1 hunks)
  • packages/lib/date-ranges.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/lib/date-ranges.test.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/date-ranges.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/date-ranges.ts
🧠 Learnings (1)
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{ts,tsx} : Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js `.utc()` in hot paths like loops

Applied to files:

  • packages/lib/date-ranges.ts
🔇 Additional comments (1)
packages/lib/date-ranges.ts (1)

198-201: Good use of Set for deduplication

The Set-based approach correctly ensures uniqueness of keys in the endTimeToKeyMap, preventing duplicate accumulation.

Also applies to: 218-221

Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
@hemantmm hemantmm requested a review from kart1ka August 11, 2025 17:45
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: 0

♻️ Duplicate comments (2)
packages/lib/date-ranges.ts (2)

128-178: O(N²) complexity remains unaddressed despite past review feedback

The nested loop over results object (lines 134-173) creates O(N²) time complexity as flagged in previous reviews. With multiple Day.js operations inside the loop (dayjs.min, dayjs.max), this will degrade performance significantly as the number of working hour intervals grows.

While timestamps are now pre-computed (lines 129-130), the fundamental algorithmic issue persists.

Consider refactoring to use a sorted data structure:

-    // TODO: Replace with sorted structure for O(log N) lookup
-    for (const existingKey in results) {
+    // Use sorted array or Map for O(log N) overlap detection
+    const sortedRanges = Object.entries(results)
+      .map(([key, range]) => ({ key: Number(key), ...range, startValue: range.start.valueOf(), endValue: range.end.valueOf() }))
+      .sort((a, b) => a.startValue - b.startValue);
+    
+    // Binary search for overlaps instead of linear scan

144-145: Excessive Day.js usage in performance-critical loop

The loop repeatedly calls dayjs.min() and dayjs.max() for each overlap check, creating unnecessary Day.js object instantiation in a hot path. This violates the coding guideline about excessive Day.js use in performance-critical code.

Use pre-computed numeric timestamps for comparisons:

-        const mergedStart = dayjs.min(existingRange.start, startResult);
-        const mergedEnd = dayjs.max(existingRange.end, endResult);
+        const mergedStartValue = Math.min(existingStartValue, startValue);
+        const mergedEndValue = Math.max(existingEndValue, endValue);
+        
+        const mergedStart = mergedStartValue === existingStartValue ? existingRange.start : startResult;
+        const mergedEnd = mergedEndValue === existingEndValue ? existingRange.end : endResult;
🧹 Nitpick comments (1)
packages/lib/date-ranges.ts (1)

139-142: Adjacency threshold hardcoded without configuration

The 15-minute adjacency threshold (15 * 60 * 1000) is hardcoded, making it difficult to adjust for different scheduling requirements or business rules.

Extract the threshold to a configurable parameter:

+const ADJACENCY_THRESHOLD_MS = 15 * 60 * 1000; // 15 minutes

   const isOverlapping =
     (startValue <= existingEndValue && endValue >= existingStartValue) ||
-    Math.abs(startValue - existingEndValue) <= 15 * 60 * 1000 ||
-    Math.abs(endValue - existingStartValue) <= 15 * 60 * 1000;
+    Math.abs(startValue - existingEndValue) <= ADJACENCY_THRESHOLD_MS ||
+    Math.abs(endValue - existingStartValue) <= ADJACENCY_THRESHOLD_MS;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ffa6cc and b49ebbd.

📒 Files selected for processing (1)
  • packages/lib/date-ranges.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/date-ranges.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/date-ranges.ts
🧠 Learnings (1)
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{ts,tsx} : Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js `.utc()` in hot paths like loops

Applied to files:

  • packages/lib/date-ranges.ts
🔇 Additional comments (3)
packages/lib/date-ranges.ts (3)

163-166: Set-based deduplication addresses previous duplicate key issue

The implementation now uses Set to prevent duplicate keys in endTimeToKeyMap, which addresses the concern raised in past reviews about key accumulation.


203-207: Consistent Set-based deduplication applied

The same Set-based approach is correctly applied here to maintain uniqueness in the exact-start-match path.


223-227: Set-based deduplication consistently implemented

The pattern is consistently applied in the new entry creation path, ensuring no duplicate keys accumulate in endTimeToKeyMap.

Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
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: 0

🔭 Outside diff range comments (1)
packages/lib/date-ranges.ts (1)

175-180: Similar duplicate key concern for new interval insertion

The same duplicate key issue exists when adding completely new intervals.

Apply the same fix as suggested above:

  if (!endTimeToKeyMap.has(endTimeKey)) {
    endTimeToKeyMap.set(endTimeKey, []);
  }
- endTimeToKeyMap.get(endTimeKey)!.push(newKey);
+ const keys = endTimeToKeyMap.get(endTimeKey)!;
+ if (!keys.includes(newKey)) {
+   keys.push(newKey);
+ }
♻️ Duplicate comments (2)
packages/lib/date-ranges.ts (2)

32-183: Performance concern: O(N²) complexity in interval merging

The nested loop checking all existing results for each new interval creates O(N²) complexity. Combined with repeated Day.js object instantiation in the hot path, this will degrade performance as schedules grow.

Consider these optimizations:

  1. Use a sorted data structure (e.g., sorted array with binary search) for O(log N) overlap detection
  2. Cache .valueOf() results outside loops to avoid repeated Day.js instantiation
  3. Consider native Date comparisons in critical sections
// At the beginning of processWorkingHours, create sorted structure
+ const sortedIntervals = new Map(); // sorted by start time
+ 
  for (let date = dateFrom.startOf("day"); utcDateTo.isAfter(date); date = date.add(1, "day")) {
    // ... existing code ...
    
+   const startValue = startResult.valueOf();
+   const endValue = endResult.valueOf();
    
-   for (const key of keysWithSameEndTime) {
-     const existingRange = results[key];
-     if (
-       startResult.valueOf() <= existingRange.end.valueOf() &&
-       endResult.valueOf() >= existingRange.start.valueOf()
-     ) {
+   // Use binary search or interval tree for O(log N) overlap detection
+   const overlappingKey = findOverlappingInterval(sortedIntervals, startValue, endValue);
+   if (overlappingKey !== null) {
      // ... merge logic ...
    }

149-163: Potential for duplicate keys in endTimeToKeyMap

When newKey equals endTimeKey, the same key could be added multiple times to the array if this code path is hit repeatedly.

Use a Set to prevent duplicates:

- if (!endTimeToKeyMap.has(endTimeKey)) {
-   endTimeToKeyMap.set(endTimeKey, []);
- }
- endTimeToKeyMap.get(endTimeKey)!.push(newKey);
+ const keySet = endTimeToKeyMap.get(endTimeKey) ?? new Set<number>();
+ keySet.add(newKey);
+ endTimeToKeyMap.set(endTimeKey, Array.from(keySet));

Or check before adding:

  if (!endTimeToKeyMap.has(endTimeKey)) {
    endTimeToKeyMap.set(endTimeKey, []);
  }
- endTimeToKeyMap.get(endTimeKey)!.push(newKey);
+ const keys = endTimeToKeyMap.get(endTimeKey)!;
+ if (!keys.includes(newKey)) {
+   keys.push(newKey);
+ }
🧹 Nitpick comments (1)
packages/lib/date-ranges.ts (1)

86-95: Lazy initialization of endTimeToKeyMap could be optimized

The lazy initialization rebuilds the entire map on first overlap check. For better performance, consider maintaining this map throughout the function lifecycle.

Initialize the map once at the beginning:

  const utcDateTo = dateTo.utc();
- let endTimeToKeyMap: Map<number, number[]> | undefined;
+ const endTimeToKeyMap = new Map<number, number[]>();
+ 
+ // Build initial map from existing results
+ for (const [key, range] of Object.entries(results)) {
+   const endTime = range.end.valueOf();
+   if (!endTimeToKeyMap.has(endTime)) {
+     endTimeToKeyMap.set(endTime, []);
+   }
+   endTimeToKeyMap.get(endTime)!.push(Number(key));
+ }

  for (let date = dateFrom.startOf("day"); utcDateTo.isAfter(date); date = date.add(1, "day")) {
    // ... existing code ...
    
-   if (!endTimeToKeyMap) {
-     endTimeToKeyMap = new Map<number, number[]>();
-     for (const [key, range] of Object.entries(results)) {
-       const endTime = range.end.valueOf();
-       if (!endTimeToKeyMap.has(endTime)) {
-         endTimeToKeyMap.set(endTime, []);
-       }
-       endTimeToKeyMap.get(endTime)!.push(Number(key));
-     }
-   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8b4ccbb and 9301e3f.

📒 Files selected for processing (2)
  • packages/lib/date-ranges.test.ts (2 hunks)
  • packages/lib/date-ranges.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/lib/date-ranges.test.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/date-ranges.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/date-ranges.ts
🧠 Learnings (1)
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{ts,tsx} : Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js `.utc()` in hot paths like loops

Applied to files:

  • packages/lib/date-ranges.ts
⏰ Context from checks skipped due to timeout of 90000ms. 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 (3)
packages/lib/date-ranges.ts (3)

111-129: Good implementation of map maintenance for overlapping intervals

The logic correctly updates the endTimeToKeyMap when merging overlapping ranges, properly cleaning up empty buckets and maintaining consistency.


68-68: Critical fix: Removed DST-related comments confirms the overlapping intervals fix

The removal of DST/midnight handling comments and the new endTimeToKeyMap logic properly addresses the overlapping intervals issue mentioned in the PR objectives. This should resolve the booking errors when working hours overlap.


100-134: Verify overlapping-interval edge cases (same‑end tests present; additional coverage recommended)

Quick summary: I inspected packages/lib/date-ranges.ts and the test suite. There are explicit tests for same‑end‑time overlap cases (packages/lib/date-ranges.test.ts — "should handle overlapping working hours with the same end time" and "should not lose earlier time slots when overlapping ranges have the same end time"), but the merging logic relies on an endTimeToKeyMap (and mixed result key semantics), which can make other overlap patterns brittle.

Please review/cover the following locations and edge cases:

  • packages/lib/date-ranges.ts — overlap/merge loop that updates endTimeToKeyMap and result keys (around the snippet in the original comment, ~lines 86–134). Verify handling when:

    • a new range overlaps an existing range but has a different end time (no matching endTime map entry),
    • multiple existing ranges share overlapping spans but different end times,
    • results is passed as an array vs. an object keyed by timestamps (tests sometimes pass Object.values(...)).
  • packages/lib/date-ranges.test.ts — existing overlap tests (search for the two test names above). Add tests for:

    • a new range that is fully contained within an existing range when end times differ,
    • a new range that partially overlaps and extends beyond an existing range with a different end time,
    • behavior when results is supplied as an array (Object.values) vs. a record keyed by timestamps.

Tag:

Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
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

🔭 Outside diff range comments (1)
packages/lib/date-ranges.ts (1)

337-341: Missing key insertion after creating map entry

When endTimeToKeyMap exists and a new end time key is created, the code only creates an empty array but doesn't add the newKey to it. This breaks the invariant that the map should track all keys with that end time.

Apply this fix:

     if (endTimeToKeyMap) {
       if (!endTimeToKeyMap.has(endTimeKey)) {
         endTimeToKeyMap.set(endTimeKey, []);
       }
+      const keySet = new Set(endTimeToKeyMap.get(endTimeKey) || []);
+      keySet.add(newKey);
+      endTimeToKeyMap.set(endTimeKey, Array.from(keySet));
     }
♻️ Duplicate comments (1)
packages/lib/date-ranges.ts (1)

322-324: Good fix for duplicate key issue

Using a Set to deduplicate keys before converting back to an array properly addresses the duplicate accumulation issue.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 9301e3f and 62e4b60.

📒 Files selected for processing (2)
  • packages/lib/date-ranges.test.ts (1 hunks)
  • packages/lib/date-ranges.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/lib/date-ranges.test.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/date-ranges.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/date-ranges.ts
🧠 Learnings (1)
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{ts,tsx} : Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js `.utc()` in hot paths like loops

Applied to files:

  • packages/lib/date-ranges.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Codacy Static Code Analysis

Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
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

🔭 Outside diff range comments (1)
packages/lib/date-ranges.ts (1)

229-233: Missing: new result never added to endTimeToKeyMap in the fresh-insert path

You create the bucket but don’t add newKey, so subsequent same-end-time lookups won’t find it.

   if (endTimeToKeyMap) {
     if (!endTimeToKeyMap.has(endTimeKey)) {
       endTimeToKeyMap.set(endTimeKey, []);
     }
+    const keySet = new Set(endTimeToKeyMap.get(endTimeKey) || []);
+    keySet.add(newKey);
+    endTimeToKeyMap.set(endTimeKey, Array.from(keySet));
   }
♻️ Duplicate comments (1)
packages/lib/date-ranges.ts (1)

137-158: O(N²) scan across results on every insert; consider a sorted structure or bucketed search

This still linearly scans all results for each candidate. With large schedules it’ll degrade. Consider a sorted array + binary search or an interval tree to find only potentially overlapping/adjacent ranges.

If you want a low-effort step-up: maintain two indexes in parallel:

  • byEnd: Map<endValue, number[]> (you already have)
  • byStart: a sorted array of [startValue, key] to binary-search first candidate where start <= endValue + threshold, then scan forward until start > endValue + threshold.
🧹 Nitpick comments (3)
packages/lib/date-ranges.ts (2)

106-116: Simplify same-end-time merge to a single assignment

The if/else is redundant; min() already covers both branches and avoids duplicate object literals.

-        if (startResult.valueOf() < existingRange.start.valueOf()) {
-          results[key] = {
-            start: dayjs.min(existingRange.start, startResult),
-            end: endResult,
-          };
-        } else {
-          results[key] = {
-            start: existingRange.start,
-            end: endResult,
-          };
-        }
+        results[key] = {
+          start: dayjs.min(existingRange.start, startResult),
+          end: endResult,
+        };

130-130: Consider hoisting ADJACENCY_THRESHOLD_MS to module scope

Minor: defined inside the loop, it’s re-created per candidate. Hoisting reduces churn and clarifies intent.

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

153-153: Verify why last slot end shrank to 18:29:59.999Z on 2023-11-30 (America/New_York)

This is a notable change from 22:00:00.000Z to 18:29:59.999Z. If this comes from adjacency/DST gating, confirm it’s expected product behavior and not masking a regression in processWorkingHours’ offset math.

If intended, please add a brief inline comment in the test to document the rationale. I can also add a focused unit test for the overlapping-hours case from the PR description (e.g., 09:00–15:30 and 15:15–17:00 on the same day) to prevent regressions. Want me to draft it?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 62e4b60 and cd8434f.

📒 Files selected for processing (2)
  • packages/lib/date-ranges.test.ts (1 hunks)
  • packages/lib/date-ranges.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/date-ranges.test.ts
  • packages/lib/date-ranges.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/date-ranges.test.ts
  • packages/lib/date-ranges.ts
🧠 Learnings (1)
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{ts,tsx} : Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js `.utc()` in hot paths like loops

Applied to files:

  • packages/lib/date-ranges.ts
🔇 Additional comments (3)
packages/lib/date-ranges.ts (3)

126-135: Good precomputation of timestamps

Caching startValue/endValue outside the loop is a solid win and aligns with our guideline to avoid excessive Day.js usage in hot paths.


214-216: Nice dedupe on endTimeToKeyMap bucket insertion

Using Set here avoids duplicate keys in the bucket while keeping the Map’s value type as number[].


219-219: LGTM on cleanup of oldKey after re-bucketing

Deleting the old entry post endTimeToKeyMap update is correct and prevents stale entries.

Copy link
Contributor

github-actions bot commented Sep 1, 2025

This PR is being marked as stale due to inactivity.

@github-actions github-actions bot added the Stale label Sep 1, 2025
Copy link
Contributor

@Devanshusharma2005 Devanshusharma2005 left a comment

Choose a reason for hiding this comment

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

tests are failing and address the code rabbit suggestions too.

hemantmm and others added 2 commits September 2, 2025 14:37
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
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: 0

Caution

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

⚠️ Outside diff range comments (1)
packages/lib/date-ranges.ts (1)

97-125: Remove same-end-time fast path — it prevents transitive merges

Short-circuiting on the same end-time can leave overlapping chains unmerged (e.g., [09:00–12:00], [11:00–15:00], new [10:00–15:00] updates only the 15:00 bucket and skips merging with 09:00–12:00). This risks preserving overlapping ranges and reintroducing booking edge cases.

Apply:

-    const keysWithSameEndTime = endTimeToKeyMap.get(endTimeKey) || [];
-    let foundOverlapping = false;
-
-    for (const key of keysWithSameEndTime) {
-      const existingRange = results[key];
-      if (
-        startResult.valueOf() <= existingRange.end.valueOf() &&
-        endResult.valueOf() >= existingRange.start.valueOf()
-      ) {
-        if (startResult.valueOf() < existingRange.start.valueOf()) {
-          results[key] = {
-            start: dayjs.min(existingRange.start, startResult),
-            end: endResult,
-          };
-        } else {
-          results[key] = {
-            start: existingRange.start,
-            end: endResult,
-          };
-        }
-        foundOverlapping = true;
-        break;
-      }
-    }
-
-    if (foundOverlapping) {
-      continue;
-    }
+    // Removed per-end-time fast path; rely on the global overlap/adjacency merge below
🧹 Nitpick comments (4)
packages/lib/date-ranges.ts (4)

126-135: Adjacency policy: confirm 15 minutes is intended behavior

Hard-coding 15 minutes will merge gaps up to 15 min (not only overlaps). Verify with product/spec; consider tying this to slot length or making it configurable.


137-145: Avoid entries() allocation in hot loop

Minor perf: iterate keys directly to skip Object.entries() array creation.

-    for (const [key, range] of Object.entries(results)) {
-      const rangeKey = Number(key);
-      const rangeStartValue = range.start.valueOf();
-      const rangeEndValue = range.end.valueOf();
+    for (const k in results) {
+      const rangeKey = Number(k);
+      const range = results[rangeKey];
+      const rangeStartValue = range.start.valueOf();
+      const rangeEndValue = range.end.valueOf();

137-158: Potential O(N²): consider ordered structure for overlaps

Scanning all ranges per insertion is O(N²). If this runs on large schedules, consider a sorted array + binary search (or interval tree) to locate the first candidate with end >= startValue - threshold, then sweep forward only while start <= endValue + threshold.


196-225: Redundant same-start merge branch

The global merge above already handles overlaps/adjacency. This branch adds complexity and duplicate code paths.

-    if (results[startResult.valueOf()]) {
-      const oldKey = startResult.valueOf();
-      const newKey = endResult.valueOf();
-      results[newKey] = {
-        start: results[oldKey].start,
-        end: dayjs.max(results[oldKey].end, endResult),
-      };
-      if (endTimeToKeyMap) {
-        const oldEndTime = results[oldKey].end.valueOf();
-        const oldKeys = endTimeToKeyMap.get(oldEndTime) || [];
-        const filteredKeys = oldKeys.filter((k) => k !== oldKey);
-        if (filteredKeys.length === 0) {
-          endTimeToKeyMap.delete(oldEndTime);
-        } else {
-          endTimeToKeyMap.set(oldEndTime, filteredKeys);
-        }
-        if (!endTimeToKeyMap.has(endTimeKey)) {
-          endTimeToKeyMap.set(endTimeKey, []);
-        }
-        const keySet = new Set(endTimeToKeyMap.get(endTimeKey) || []);
-        keySet.add(newKey);
-        endTimeToKeyMap.set(endTimeKey, Array.from(keySet));
-      }
-      delete results[oldKey];
-      continue;
-    }
+    // Removed redundant same-start merge; covered by the global merge path
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 cd8434f and 6815b7e.

📒 Files selected for processing (1)
  • packages/lib/date-ranges.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/date-ranges.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/date-ranges.ts
🧠 Learnings (1)
📚 Learning: 2025-07-28T11:50:23.946Z
Learnt from: CR
PR: calcom/cal.com#0
File: .cursor/rules/review.mdc:0-0
Timestamp: 2025-07-28T11:50:23.946Z
Learning: Applies to **/*.{ts,tsx} : Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js `.utc()` in hot paths like loops

Applied to files:

  • packages/lib/date-ranges.ts
⏰ Context from checks skipped due to timeout of 90000ms. 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 (2)
packages/lib/date-ranges.ts (2)

160-194: Good fix: safe merge ordering and map updates

Snapshotting old end-times, excluding newKey from deletions, and deduping the bucket prevents accidental removal and duplicate keys.


218-221: Nice: bucket dedupe via Set

Converts possible duplicates into a unique list before persisting.

Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
@hemantmm hemantmm marked this pull request as draft September 2, 2025 09:44
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
@hemantmm hemantmm marked this pull request as ready for review September 2, 2025 10:33
@hemantmm hemantmm requested a review from a team as a code owner September 2, 2025 10:33
@dosubot dosubot bot added the app-store area: app store, apps, calendar integrations, google calendar, outlook, lark, apple calendar label Sep 2, 2025
@github-actions github-actions bot removed the Stale label Sep 3, 2025
@hemantmm
Copy link
Contributor Author

hemantmm commented Sep 3, 2025

@Devanshusharma2005, I have fixed the failing test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
app-store area: app store, apps, calendar integrations, google calendar, outlook, lark, apple calendar 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 size/L
Projects
None yet
Development

Successfully merging this pull request may close these issues.

An overlapping working hour results in "could not book the meeting"
3 participants