-
Notifications
You must be signed in to change notification settings - Fork 10.4k
fix: overlapping intervals #22996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: overlapping intervals #22996
Conversation
@hemantmm is attempting to deploy a commit to the cal Team on Vercel. A member of the Team first needs to authorize it. |
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughReplaces 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
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
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. |
Todo:
|
There was a problem hiding this 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 loopsThe newly-added merge loop performs multiple
dayjs.min/max
calls andvalueOf()
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
📒 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 useinclude
, always useselect
Ensure thecredential.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>
There was a problem hiding this 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
📒 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 useinclude
, always useselect
Ensure thecredential.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 handlingThis 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?
There was a problem hiding this 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?
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
@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 |
There was a problem hiding this 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 intervalsThis 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:
- Using a sorted data structure (e.g., interval tree or sorted array with binary search) to find overlaps in O(log N)
- Pre-computing numeric timestamps with
.valueOf()
before the loop to avoid repeated Day.js instantiation- 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 endTimeToKeyMapLine 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 configurableThe 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
📒 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 useinclude
, always useselect
Ensure thecredential.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 deduplicationThe 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>
There was a problem hiding this 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 feedbackThe 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 loopThe loop repeatedly calls
dayjs.min()
anddayjs.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 configurationThe 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
📒 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 useinclude
, always useselect
Ensure thecredential.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 issueThe implementation now uses
Set
to prevent duplicate keys inendTimeToKeyMap
, which addresses the concern raised in past reviews about key accumulation.
203-207
: Consistent Set-based deduplication appliedThe same Set-based approach is correctly applied here to maintain uniqueness in the exact-start-match path.
223-227
: Set-based deduplication consistently implementedThe 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>
There was a problem hiding this 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 insertionThe 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 mergingThe 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:
- Use a sorted data structure (e.g., sorted array with binary search) for O(log N) overlap detection
- Cache
.valueOf()
results outside loops to avoid repeated Day.js instantiation- 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 inendTimeToKeyMap
When
newKey
equalsendTimeKey
, 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 ofendTimeToKeyMap
could be optimizedThe 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.
📒 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 useinclude
, always useselect
Ensure thecredential.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 intervalsThe 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 fixThe 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>
There was a problem hiding this 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 entryWhen
endTimeToKeyMap
exists and a new end time key is created, the code only creates an empty array but doesn't add thenewKey
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 issueUsing 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.
📒 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 useinclude
, always useselect
Ensure thecredential.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>
There was a problem hiding this 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 pathYou 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 searchThis 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 assignmentThe 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 scopeMinor: 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.
📒 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 useinclude
, always useselect
Ensure thecredential.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 timestampsCaching 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 insertionUsing 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-bucketingDeleting the old entry post endTimeToKeyMap update is correct and prevents stale entries.
This PR is being marked as stale due to inactivity. |
There was a problem hiding this 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.
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
There was a problem hiding this 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 mergesShort-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 behaviorHard-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 loopMinor 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 overlapsScanning 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 branchThe 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.
📒 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 useinclude
, always useselect
Ensure thecredential.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 updatesSnapshotting old end-times, excluding newKey from deletions, and deduping the bucket prevents accidental removal and duplicate keys.
218-221
: Nice: bucket dedupe via SetConverts possible duplicates into a unique list before persisting.
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
…l.com into overlapping-interval
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
Signed-off-by: Hemant M Mehta <hemant29mehta@gmail.com>
@Devanshusharma2005, I have fixed the failing test |
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)
How should this be tested?
Checklist