Bugzilla – Attachment 191360 Details for
Bug 37707
Lead/Trail times should work in combination
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 37707: Fix timezone handling in place_booking modal
27aeacb.patch (text/plain), 16.36 KB, created by
Martin Renvoize (ashimema)
on 2026-01-13 13:40:28 UTC
(
hide
)
Description:
Bug 37707: Fix timezone handling in place_booking modal
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2026-01-13 13:40:28 UTC
Size:
16.36 KB
patch
obsolete
>From 27aeacb93e77d0c24676c2f6834ad8fa8420c41a Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Tue, 13 Jan 2026 13:08:56 +0000 >Subject: [PATCH] Bug 37707: Fix timezone handling in place_booking modal > >This patch fixes timezone inconsistencies in the place_booking modal >that caused dates to shift when the server and browser are in different >timezones. > >Issues Fixed: >1. Date index creation used .toISOString().split("T")[0] which converted > to UTC, causing western timezone dates to appear on wrong calendar days >2. Date comparisons mixed flatpickr.parseDate() and dayjs() without > normalizing to start-of-day >3. Booking overlap detection had inconsistent timezone handling > >Changes: >- Replace .toISOString().split("T")[0] with dayjs().format('YYYY-MM-DD') > to maintain browser timezone consistency >- Normalize all date comparisons to start-of-day using dayjs().startOf('day') >- Use dayjs() consistently for parsing RFC3339 datetime strings from API >- Add explicit .startOf('day') and .endOf('day') for API submission clarity > >These changes ensure: >- Bookings display on correct calendar days regardless of browser timezone >- Multi-day bookings span the correct number of days >- Conflict detection works consistently across all timezones >- Item availability calculations are accurate >- Future hourly booking support is preserved (time components maintained) > >Test plan: >1. Set browser to PST (UTC-8), server to UTC >2. Create a booking for January 15 >3. Verify booking displays on Jan 15 only (not Jan 15-16) >4. Create a 3-day booking (Jan 15-17) >5. Verify displays on exactly 3 days >6. Test conflict detection with overlapping bookings >7. Repeat tests with browser in HST (UTC-10) and NZDT (UTC+13) >--- > .../prog/js/modals/place_booking.js | 179 ++++++++++-------- > 1 file changed, 105 insertions(+), 74 deletions(-) > >diff --git a/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js b/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js >index c1dcf5ec3f8..a351090b717 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js >@@ -468,19 +468,25 @@ $("#placeBookingModal").on("show.bs.modal", function (e) { > continue; > } > >- let start_date = flatpickr.parseDate( >- booking.start_date >- ); >- let end_date = flatpickr.parseDate( >- booking.end_date >- ); >+ // Parse and normalize dates to start-of-day for consistent comparison >+ let start_date = dayjs(booking.start_date) >+ .startOf("day") >+ .toDate(); >+ let end_date = dayjs(booking.end_date) >+ .startOf("day") >+ .toDate(); > > // patron has selected a start date (end date checks) > if (selectedDates[0]) { >+ // Normalize selected date to start-of-day for comparison >+ let selectedStart = dayjs(selectedDates[0]) >+ .startOf("day") >+ .toDate(); >+ > // new booking start date is between existing booking start and end dates > if ( >- selectedDates[0] >= start_date && >- selectedDates[0] <= end_date >+ selectedStart >= start_date && >+ selectedStart <= end_date > ) { > if (booking.item_id) { > if ( >@@ -506,57 +512,62 @@ $("#placeBookingModal").on("show.bs.modal", function (e) { > } > > // new booking end date would be between existing booking start and end dates >- else if ( >- date >= start_date && >- date <= end_date >- ) { >- if (booking.item_id) { >- if ( >- unavailable_items.indexOf( >- booking.item_id >- ) === -1 >- ) { >- unavailable_items.push( >- booking.item_id >- ); >- } >- } else { >- if ( >- biblio_bookings.indexOf( >- booking.booking_id >- ) === -1 >- ) { >- biblio_bookings.push( >- booking.booking_id >- ); >+ else if (date) { >+ let selectedEnd = dayjs(date) >+ .startOf("day") >+ .toDate(); >+ if ( >+ selectedEnd >= start_date && >+ selectedEnd <= end_date >+ ) { >+ if (booking.item_id) { >+ if ( >+ unavailable_items.indexOf( >+ booking.item_id >+ ) === -1 >+ ) { >+ unavailable_items.push( >+ booking.item_id >+ ); >+ } >+ } else { >+ if ( >+ biblio_bookings.indexOf( >+ booking.booking_id >+ ) === -1 >+ ) { >+ biblio_bookings.push( >+ booking.booking_id >+ ); >+ } > } > } >- } > >- // new booking would span existing booking >- else if ( >- selectedDates[0] <= start_date && >- date >= end_date >- ) { >- if (booking.item_id) { >- if ( >- unavailable_items.indexOf( >- booking.item_id >- ) === -1 >- ) { >- unavailable_items.push( >- booking.item_id >- ); >- } >- } else { >- if ( >- biblio_bookings.indexOf( >- booking.booking_id >- ) === -1 >- ) { >- biblio_bookings.push( >- booking.booking_id >- ); >+ // new booking would span existing booking >+ else if ( >+ selectedStart <= start_date && >+ selectedEnd >= end_date >+ ) { >+ if (booking.item_id) { >+ if ( >+ unavailable_items.indexOf( >+ booking.item_id >+ ) === -1 >+ ) { >+ unavailable_items.push( >+ booking.item_id >+ ); >+ } >+ } else { >+ if ( >+ biblio_bookings.indexOf( >+ booking.booking_id >+ ) === -1 >+ ) { >+ biblio_bookings.push( >+ booking.booking_id >+ ); >+ } > } > } > } >@@ -777,10 +788,14 @@ $("#placeBookingModal").on("show.bs.modal", function (e) { > // Range set, update hidden fields and set available items > else if (selectedDates[0] && selectedDates[1]) { > // set form fields from picker >- let picker_start = dayjs(selectedDates[0]); >+ // Ensure dates are normalized to start/end of day in browser timezone >+ let picker_start = dayjs( >+ selectedDates[0] >+ ).startOf("day"); > let picker_end = dayjs(selectedDates[1]).endOf( > "day" > ); >+ // toISOString() converts to RFC3339 format with timezone offset > $("#booking_start_date").val( > picker_start.toISOString() > ); >@@ -791,18 +806,32 @@ $("#placeBookingModal").on("show.bs.modal", function (e) { > // set available items in select2 > let booked_items = bookings.filter( > function (booking) { >- let start_date = flatpickr.parseDate( >+ // Parse and normalize dates to start-of-day for consistent comparison >+ let start_date = dayjs( > booking.start_date >- ); >- let end_date = flatpickr.parseDate( >- booking.end_date >- ); >+ ) >+ .startOf("day") >+ .toDate(); >+ let end_date = dayjs(booking.end_date) >+ .startOf("day") >+ .toDate(); >+ let selectedStart = dayjs( >+ selectedDates[0] >+ ) >+ .startOf("day") >+ .toDate(); >+ let selectedEnd = dayjs( >+ selectedDates[1] >+ ) >+ .startOf("day") >+ .toDate(); >+ > // This booking ends before the start of the new booking >- if (end_date <= selectedDates[0]) { >+ if (end_date < selectedStart) { > return false; > } >- // This booking starts after then end of the new booking >- if (start_date >= selectedDates[1]) { >+ // This booking starts after the end of the new booking >+ if (start_date > selectedEnd) { > return false; > } > // This booking overlaps >@@ -869,11 +898,13 @@ $("#placeBookingModal").on("show.bs.modal", function (e) { > const item_id = booking.item_id; > > // Iterate through each date within the range of start_date and end_date >- let currentDate = new Date(start_date); >- while (currentDate <= end_date) { >- const currentDateStr = currentDate >- .toISOString() >- .split("T")[0]; >+ // Use dayjs to maintain browser timezone consistency >+ let currentDate = dayjs(start_date).startOf("day"); >+ const endDate = dayjs(end_date).endOf("day"); >+ >+ while (currentDate <= endDate) { >+ // Format in browser timezone - no UTC conversion >+ const currentDateStr = currentDate.format("YYYY-MM-DD"); > > // If the date key doesn't exist in the hash, create an empty array for it > if (!bookingsByDate[currentDateStr]) { >@@ -884,7 +915,7 @@ $("#placeBookingModal").on("show.bs.modal", function (e) { > bookingsByDate[currentDateStr].push(item_id); > > // Move to the next day >- currentDate.setDate(currentDate.getDate() + 1); >+ currentDate = currentDate.add(1, "day"); > } > }); > >@@ -896,9 +927,9 @@ $("#placeBookingModal").on("show.bs.modal", function (e) { > periodPicker.config.onDayCreate.push( > function dayCreate(dObj, dStr, instance, dayElem) { > const currentDate = dayElem.dateObj; >- const dateString = currentDate >- .toISOString() >- .split("T")[0]; >+ // Format in browser timezone to match bookingsByDate keys >+ const dateString = >+ dayjs(currentDate).format("YYYY-MM-DD"); > > const isBold = boldDates.some( > boldDate => >-- >2.52.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 37707
:
182718
|
190859
|
190860
|
190861
|
190862
|
191000
|
191001
|
191002
|
191003
|
191232
|
191233
|
191350
|
191351
|
191352
|
191353
|
191354
|
191355
|
191356
|
191357
|
191358
|
191359
|
191360
|
191361
|
191394
|
191395
|
191746
|
192006
|
192007
|
192008
|
192009
|
192010
|
192800
|
192801
|
192802
|
192803
|
192804
|
192805
|
192806
|
192807
|
192863
|
192864
|
192865
|
192866
|
192867
|
192868
|
192869
|
192870