@@ -124,10 +124,18 @@ protected function consider_multiple_attendants(
124124 return [];
125125 }
126126
127+ $ period_start = new DateTime ($ date . ' ' . $ date_working_plan ['start ' ]);
128+ $ period_end = new DateTime ($ date . ' ' . $ date_working_plan ['end ' ]);
129+
130+ // If end is before or equal to start, it means the period crosses midnight (e.g., 18:00-02:00)
131+ if ($ period_end <= $ period_start ) {
132+ $ period_end ->modify ('+1 day ' );
133+ }
134+
127135 $ periods = [
128136 [
129- 'start ' => new DateTime ( $ date . ' ' . $ date_working_plan [ ' start ' ]) ,
130- 'end ' => new DateTime ( $ date . ' ' . $ date_working_plan [ ' end ' ]) ,
137+ 'start ' => $ period_start ,
138+ 'end ' => $ period_end ,
131139 ],
132140 ];
133141
@@ -202,11 +210,33 @@ public function remove_breaks(string $date, array $periods, array $breaks): arra
202210 return $ periods ;
203211 }
204212
213+ // Determine the working day start time for cross-midnight handling
214+ // by looking at the first period's start time
215+ $ working_day_start_time = null ;
216+ if (!empty ($ periods ) && isset ($ periods [0 ]['start ' ])) {
217+ $ first_period_start = $ periods [0 ]['start ' ];
218+ if ($ first_period_start instanceof DateTime) {
219+ $ working_day_start_time = $ first_period_start ->format ('H:i ' );
220+ } else {
221+ $ working_day_start_time = $ first_period_start ;
222+ }
223+ }
224+
205225 foreach ($ breaks as $ break ) {
206226 $ break_start = new DateTime ($ date . ' ' . $ break ['start ' ]);
207-
208227 $ break_end = new DateTime ($ date . ' ' . $ break ['end ' ]);
209228
229+ // For cross-midnight periods: if break time is before the working day start,
230+ // the break is on the next day (e.g., break at 01:00 when work starts at 18:00)
231+ if ($ working_day_start_time !== null ) {
232+ if ($ break ['start ' ] < $ working_day_start_time ) {
233+ $ break_start ->modify ('+1 day ' );
234+ }
235+ if ($ break ['end ' ] <= $ working_day_start_time ) {
236+ $ break_end ->modify ('+1 day ' );
237+ }
238+ }
239+
210240 foreach ($ periods as &$ period ) {
211241 $ period_start = $ period ['start ' ];
212242
@@ -391,13 +421,27 @@ public function get_available_periods(string $date, array $provider, ?int $exclu
391421 'end ' => $ date_working_plan ['end ' ],
392422 ];
393423
394- $ day_start = new DateTime ($ date_working_plan ['start ' ]);
395- $ day_end = new DateTime ($ date_working_plan ['end ' ]);
424+ $ day_start = new DateTime ($ date . ' ' . $ date_working_plan ['start ' ]);
425+ $ day_end = new DateTime ($ date . ' ' . $ date_working_plan ['end ' ]);
426+
427+ // If end is before or equal to start, it means the period crosses midnight (e.g., 18:00-02:00)
428+ if ($ day_end <= $ day_start ) {
429+ $ day_end ->modify ('+1 day ' );
430+ }
396431
397432 // Split the working plan to available time periods that do not contain the breaks in them.
398433 foreach ($ date_working_plan ['breaks ' ] as $ break ) {
399- $ break_start = new DateTime ($ break ['start ' ]);
400- $ break_end = new DateTime ($ break ['end ' ]);
434+ $ break_start = new DateTime ($ date . ' ' . $ break ['start ' ]);
435+ $ break_end = new DateTime ($ date . ' ' . $ break ['end ' ]);
436+
437+ // For cross-midnight periods: if break time is before the working day start,
438+ // the break is on the next day (e.g., break at 01:00 when work starts at 18:00)
439+ if ($ break ['start ' ] < $ date_working_plan ['start ' ]) {
440+ $ break_start ->modify ('+1 day ' );
441+ }
442+ if ($ break ['end ' ] <= $ date_working_plan ['start ' ]) {
443+ $ break_end ->modify ('+1 day ' );
444+ }
401445
402446 if ($ break_start < $ day_start ) {
403447 $ break_start = $ day_start ;
@@ -412,8 +456,16 @@ public function get_available_periods(string $date, array $provider, ?int $exclu
412456 }
413457
414458 foreach ($ periods as $ key => $ period ) {
415- $ period_start = new DateTime ($ period ['start ' ]);
416- $ period_end = new DateTime ($ period ['end ' ]);
459+ $ period_start = new DateTime ($ date . ' ' . $ period ['start ' ]);
460+ $ period_end = new DateTime ($ date . ' ' . $ period ['end ' ]);
461+
462+ // Adjust for cross-midnight: if period time is before working day start, it's on the next day
463+ if ($ period ['start ' ] < $ date_working_plan ['start ' ]) {
464+ $ period_start ->modify ('+1 day ' );
465+ }
466+ if ($ period ['end ' ] <= $ date_working_plan ['start ' ]) {
467+ $ period_end ->modify ('+1 day ' );
468+ }
417469
418470 $ remove_current_period = false ;
419471
@@ -459,6 +511,14 @@ public function get_available_periods(string $date, array $provider, ?int $exclu
459511 $ period_start = new DateTime ($ date . ' ' . $ period ['start ' ]);
460512 $ period_end = new DateTime ($ date . ' ' . $ period ['end ' ]);
461513
514+ // Adjust for cross-midnight: if period time is before working day start, it's on the next day
515+ if ($ period ['start ' ] < $ date_working_plan ['start ' ]) {
516+ $ period_start ->modify ('+1 day ' );
517+ }
518+ if ($ period ['end ' ] <= $ date_working_plan ['start ' ]) {
519+ $ period_end ->modify ('+1 day ' );
520+ }
521+
462522 if (
463523 $ appointment_start <= $ period_start &&
464524 $ appointment_end <= $ period_end &&
@@ -552,6 +612,11 @@ protected function generate_available_hours(string $date, array $service, array
552612
553613 $ end_hour = new DateTime ($ date . ' ' . $ period ['end ' ]);
554614
615+ // If end is before or equal to start, it means the period crosses midnight (e.g., 18:00-02:00)
616+ if ($ end_hour <= $ start_hour ) {
617+ $ end_hour ->modify ('+1 day ' );
618+ }
619+
555620 $ interval = !empty ($ service ['slot_interval ' ]) ? (int ) $ service ['slot_interval ' ] : 15 ;
556621
557622 $ current_hour = $ start_hour ;
0 commit comments