Bugzilla – Attachment 192802 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: (follow-up) Fix timezone handling for bookings
Bug-37707-follow-up-Fix-timezone-handling-for-book.patch (text/plain), 12.69 KB, created by
Paul Derscheid
on 2026-02-09 14:50:31 UTC
(
hide
)
Description:
Bug 37707: (follow-up) Fix timezone handling for bookings
Filename:
MIME Type:
Creator:
Paul Derscheid
Created:
2026-02-09 14:50:31 UTC
Size:
12.69 KB
patch
obsolete
>From 602686691878f67f77389894c31a0881d1aaa72e 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: (follow-up) Fix timezone handling for bookings > >Fix date handling issues that caused bookings to shift by a day >when the browser timezone differs from the server timezone: > >- Normalize dates to browser timezone for calendar display >- Use dayjs without UTC conversion for bookingsByDate keys >- Extract local date and send as explicit UTC day boundaries >- Preserve user's selected DATE regardless of browser timezone > >Timeline display: >- Convert server dates to library timezone for display >- Add $timezone() and $toDisplayDate() helper functions > >This ensures that a booking for "June 15" displays and saves as >June 15 regardless of the user's timezone offset. > >Signed-off-by: Paul Derscheid <paul.derscheid@lmscloud.de> >--- > .../prog/en/includes/js-date-format.inc | 22 +++++ > .../prog/en/modules/bookings/list.tt | 7 +- > .../prog/js/modals/place_booking.js | 80 +++++++++++++------ > 3 files changed, 84 insertions(+), 25 deletions(-) > >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/js-date-format.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/js-date-format.inc >index 2a95e218c1d..029fa91862d 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/js-date-format.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/js-date-format.inc >@@ -118,6 +118,28 @@ > } > } > >+ /** >+ * Returns the configured Koha server timezone. >+ * Useful for components that need to display dates/times in library timezone. >+ */ >+ window.$timezone = function() { >+ return def_tz; >+ } >+ >+ /** >+ * Converts a dayjs object to a Date for timeline/calendar display. >+ * Creates a Date that displays these exact values regardless of browser timezone. >+ * (Strips timezone info so JS interprets the values as browser-local time) >+ * >+ * Use this when you need to display a specific date/time from a known timezone >+ * (e.g., library timezone) and want it to appear the same for all users. >+ * >+ * @param {dayjs.Dayjs} dayjsObj - A dayjs object (typically converted to a specific timezone) >+ * @returns {Date} A Date object that will display the dayjs values in the browser >+ */ >+ window.$toDisplayDate = function(dayjsObj) { >+ return new Date(dayjsObj.format('YYYY-MM-DDTHH:mm:ss')); >+ } > > })(); > </script> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt >index 0f0103f2ed3..a1f0bd96f82 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt >@@ -106,13 +106,16 @@ > display_cardnumber: true, > url: false > }); >+ // Convert to library timezone for timeline display >+ const startServerTz = dayjs(booking.start_date).tz($timezone()); >+ const endServerTz = dayjs(booking.end_date).tz($timezone()); > bookingsSet.add({ > id: booking.booking_id, > booking: booking.booking_id, > patron: booking.patron_id, > pickup_library: booking.pickup_library_id, >- start: dayjs(booking.start_date).toDate(), >- end: dayjs(booking.end_date).toDate(), >+ start: $toDisplayDate(startServerTz), >+ end: $toDisplayDate(endServerTz), > content: !isActive ? `<s>${patronContent}</s>` : patronContent, > className: booking.status === "cancelled" ? "cancelled" : "", > [% IF CAN_user_circulate_manage_bookings %] >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 e3c04692ff8..2096db3c222 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js >@@ -403,7 +403,7 @@ $("#placeBookingModal").on("show.bs.modal", function (e) { > end_date: checkout.due_date, > item_id: checkout.item_id, > patron_id: checkout.patron_id, >- start_date: new Date().toISOString(), >+ start_date: dayjs().format(), > }; > bookings.unshift(booking); > } >@@ -929,32 +929,57 @@ $("#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]); >- let picker_end = dayjs(selectedDates[1]).endOf( >- "day" >+ // Extract local date and send as explicit UTC day boundaries >+ // This preserves the user's selected DATE regardless of browser timezone >+ // Using dayjs.utc() ensures startOf/endOf operate in UTC, not browser TZ >+ let startDate = dayjs(selectedDates[0]).format( >+ "YYYY-MM-DD" >+ ); >+ let endDate = dayjs(selectedDates[1]).format( >+ "YYYY-MM-DD" > ); > $("#booking_start_date").val( >- picker_start.toISOString() >+ dayjs >+ .utc(startDate) >+ .startOf("day") >+ .toISOString() > ); > $("#booking_end_date").val( >- picker_end.toISOString() >+ dayjs >+ .utc(endDate) >+ .endOf("day") >+ .toISOString() > ); > > // 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 >@@ -1021,8 +1046,11 @@ $("#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 = dayjs(start_date); >- while (currentDate.isSameOrBefore(end_date, "day")) { >+ // Use dayjs to maintain browser timezone consistency >+ let currentDate = dayjs(start_date).startOf("day"); >+ const endDate = dayjs(end_date).startOf("day"); >+ while (currentDate.isSameOrBefore(endDate, "day")) { >+ // 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 >@@ -1046,9 +1074,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 => >@@ -2259,12 +2287,15 @@ $("#placeBookingForm").on("submit", function (e) { > bookings_table.api().ajax.reload(); > } > if (typeof timeline !== "undefined" && timeline !== null) { >+ // Convert to library timezone for timeline display >+ const startServerTz = dayjs(data.start_date).tz($timezone()); >+ const endServerTz = dayjs(data.end_date).tz($timezone()); > timeline.itemsData.add({ > id: data.booking_id, > booking: data.booking_id, > patron: data.patron_id, >- start: dayjs(data.start_date).toDate(), >- end: dayjs(data.end_date).toDate(), >+ start: $toDisplayDate(startServerTz), >+ end: $toDisplayDate(endServerTz), > content: $patron_to_html(booking_patron, { > display_cardnumber: true, > url: false, >@@ -2368,12 +2399,15 @@ $("#placeBookingForm").on("submit", function (e) { > bookings_table.api().ajax.reload(); > } > if (typeof timeline !== "undefined" && timeline !== null) { >+ // Convert to library timezone for timeline display >+ const startServerTz = dayjs(data.start_date).tz($timezone()); >+ const endServerTz = dayjs(data.end_date).tz($timezone()); > timeline.itemsData.update({ > id: data.booking_id, > booking: data.booking_id, > patron: data.patron_id, >- start: dayjs(data.start_date).toDate(), >- end: dayjs(data.end_date).toDate(), >+ start: $toDisplayDate(startServerTz), >+ end: $toDisplayDate(endServerTz), > content: $patron_to_html(booking_patron, { > display_cardnumber: true, > url: false, >-- >2.39.5
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