@@ -19,6 +19,7 @@ import type prisma from "@calcom/prisma";
1919import { availabilityUserSelect } from "@calcom/prisma" ;
2020import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils" ;
2121import type { EventBusyDate } from "@calcom/types/Calendar" ;
22+ import type { WorkingHours } from "@calcom/types/schedule" ;
2223
2324import { TRPCError } from "@trpc/server" ;
2425
@@ -75,12 +76,21 @@ const checkIfIsAvailable = ({
7576 time,
7677 busy,
7778 eventLength,
79+ dateOverrides = [ ] ,
80+ workingHours = [ ] ,
7881 currentSeats,
82+ organizerTimeZone,
7983} : {
8084 time : Dayjs ;
8185 busy : EventBusyDate [ ] ;
8286 eventLength : number ;
87+ dateOverrides ?: {
88+ start : Date ;
89+ end : Date ;
90+ } [ ] ;
91+ workingHours ?: WorkingHours [ ] ;
8392 currentSeats ?: CurrentSeats ;
93+ organizerTimeZone ?: string ;
8494} ) : boolean => {
8595 if ( currentSeats ?. some ( ( booking ) => booking . startTime . toISOString ( ) === time . toISOString ( ) ) ) {
8696 return true ;
@@ -89,6 +99,57 @@ const checkIfIsAvailable = ({
8999 const slotEndTime = time . add ( eventLength , "minutes" ) . utc ( ) ;
90100 const slotStartTime = time . utc ( ) ;
91101
102+ //check if date override for slot exists
103+ let dateOverrideExist = false ;
104+
105+ if (
106+ dateOverrides . find ( ( date ) => {
107+ const utcOffset = organizerTimeZone ? dayjs . tz ( date . start , organizerTimeZone ) . utcOffset ( ) * - 1 : 0 ;
108+
109+ if (
110+ dayjs ( date . start ) . add ( utcOffset , "minutes" ) . format ( "YYYY MM DD" ) ===
111+ slotStartTime . format ( "YYYY MM DD" )
112+ ) {
113+ dateOverrideExist = true ;
114+ if ( dayjs ( date . start ) . add ( utcOffset , "minutes" ) === dayjs ( date . end ) . add ( utcOffset , "minutes" ) ) {
115+ return true ;
116+ }
117+ if (
118+ slotEndTime . isBefore ( dayjs ( date . start ) . add ( utcOffset , "minutes" ) ) ||
119+ slotEndTime . isSame ( dayjs ( date . start ) . add ( utcOffset , "minutes" ) )
120+ ) {
121+ return true ;
122+ }
123+ if ( slotStartTime . isAfter ( dayjs ( date . end ) . add ( utcOffset , "minutes" ) ) ) {
124+ return true ;
125+ }
126+ }
127+ } )
128+ ) {
129+ // slot is not within the date override
130+ return false ;
131+ }
132+
133+ if ( dateOverrideExist ) {
134+ return true ;
135+ }
136+
137+ //if no date override for slot exists check if it is within normal work hours
138+ if (
139+ workingHours . find ( ( workingHour ) => {
140+ if ( workingHour . days . includes ( slotStartTime . day ( ) ) ) {
141+ const start = slotStartTime . hour ( ) * 60 + slotStartTime . minute ( ) ;
142+ const end = slotStartTime . hour ( ) * 60 + slotStartTime . minute ( ) ;
143+ if ( start < workingHour . startTime || end > workingHour . endTime ) {
144+ return true ;
145+ }
146+ }
147+ } )
148+ ) {
149+ // slot is outside of working hours
150+ return false ;
151+ }
152+
92153 return busy . every ( ( busyTime ) => {
93154 const startTime = dayjs . utc ( busyTime . start ) . utc ( ) ;
94155 const endTime = dayjs . utc ( busyTime . end ) ;
@@ -115,7 +176,6 @@ const checkIfIsAvailable = ({
115176 else if ( startTime . isBetween ( time , slotEndTime ) ) {
116177 return false ;
117178 }
118-
119179 return true ;
120180 } ) ;
121181} ;
@@ -348,7 +408,11 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx:
348408 ) ;
349409 // flattens availability of multiple users
350410 const dateOverrides = userAvailability . flatMap ( ( availability ) =>
351- availability . dateOverrides . map ( ( override ) => ( { userId : availability . user . id , ...override } ) )
411+ availability . dateOverrides . map ( ( override ) => ( {
412+ userId : availability . user . id ,
413+ timeZone : availability . timeZone ,
414+ ...override ,
415+ } ) )
352416 ) ;
353417 const workingHours = getAggregateWorkingHours ( userAvailability , eventType . schedulingType ) ;
354418 const availabilityCheckProps = {
@@ -372,6 +436,9 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx:
372436
373437 const timeSlots : ReturnType < typeof getTimeSlots > = [ ] ;
374438
439+ const organizerTimeZone =
440+ eventType . timeZone || eventType ?. schedule ?. timeZone || userAvailability ?. [ 0 ] ?. timeZone ;
441+
375442 for (
376443 let currentCheckedTime = startTime ;
377444 currentCheckedTime . isBefore ( endTime ) ;
@@ -386,8 +453,7 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx:
386453 dateOverrides,
387454 minimumBookingNotice : eventType . minimumBookingNotice ,
388455 frequency : eventType . slotInterval || input . duration || eventType . length ,
389- organizerTimeZone :
390- eventType . timeZone || eventType ?. schedule ?. timeZone || userAvailability ?. [ 0 ] ?. timeZone ,
456+ organizerTimeZone,
391457 } )
392458 ) ;
393459 }
@@ -423,13 +489,15 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx:
423489 time : slot . time ,
424490 ...schedule ,
425491 ...availabilityCheckProps ,
492+ organizerTimeZone : schedule . timeZone ,
426493 } ) ;
427494 const endCheckForAvailability = performance . now ( ) ;
428495 checkForAvailabilityCount ++ ;
429496 checkForAvailabilityTime += endCheckForAvailability - startCheckForAvailability ;
430497 return isAvailable ;
431498 } ) ;
432499 } ) ;
500+
433501 // what else are you going to call it?
434502 const looseHostAvailability = userAvailability . filter ( ( { user : { isFixed } } ) => ! isFixed ) ;
435503 if ( looseHostAvailability . length > 0 ) {
@@ -446,6 +514,7 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx:
446514 time : slot . time ,
447515 ...userSchedule ,
448516 ...availabilityCheckProps ,
517+ organizerTimeZone : userSchedule . timeZone ,
449518 } ) ;
450519 } ) ;
451520 return slot ;
@@ -507,17 +576,19 @@ export async function getSchedule(input: z.infer<typeof getScheduleSchema>, ctx:
507576 return false ;
508577 }
509578
579+ const userSchedule = userAvailability . find ( ( { user : { id : userId } } ) => userId === slotUserId ) ;
580+
510581 return checkIfIsAvailable ( {
511582 time : slot . time ,
512583 busy,
513584 ...availabilityCheckProps ,
585+ organizerTimeZone : userSchedule ?. timeZone ,
514586 } ) ;
515587 } ) ;
516588 return slot ;
517589 } )
518590 . filter ( ( slot ) => ! ! slot . userIds ?. length ) ;
519591 }
520-
521592 availableTimeSlots = availableTimeSlots . filter ( ( slot ) => isTimeWithinBounds ( slot . time ) ) ;
522593
523594 const computedAvailableSlots = availableTimeSlots . reduce (
0 commit comments