View | Details | Raw Unified | Return to bug 37707
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/js-date-format.inc (+22 lines)
Lines 118-123 Link Here
118
            }
118
            }
119
        }
119
        }
120
120
121
        /**
122
         * Returns the configured Koha server timezone.
123
         * Useful for components that need to display dates/times in library timezone.
124
         */
125
        window.$timezone = function() {
126
            return def_tz;
127
        }
128
129
        /**
130
         * Converts a dayjs object to a Date for timeline/calendar display.
131
         * Creates a Date that displays these exact values regardless of browser timezone.
132
         * (Strips timezone info so JS interprets the values as browser-local time)
133
         *
134
         * Use this when you need to display a specific date/time from a known timezone
135
         * (e.g., library timezone) and want it to appear the same for all users.
136
         *
137
         * @param {dayjs.Dayjs} dayjsObj - A dayjs object (typically converted to a specific timezone)
138
         * @returns {Date} A Date object that will display the dayjs values in the browser
139
         */
140
        window.$toDisplayDate = function(dayjsObj) {
141
            return new Date(dayjsObj.format('YYYY-MM-DDTHH:mm:ss'));
142
        }
121
143
122
    })();
144
    })();
123
</script>
145
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/bookings/list.tt (-2 / +5 lines)
Lines 106-118 Link Here
106
                            display_cardnumber: true,
106
                            display_cardnumber: true,
107
                            url: false
107
                            url: false
108
                        });
108
                        });
109
                        // Convert to library timezone for timeline display
110
                        const startServerTz = dayjs(booking.start_date).tz($timezone());
111
                        const endServerTz = dayjs(booking.end_date).tz($timezone());
109
                        bookingsSet.add({
112
                        bookingsSet.add({
110
                            id: booking.booking_id,
113
                            id: booking.booking_id,
111
                            booking: booking.booking_id,
114
                            booking: booking.booking_id,
112
                            patron: booking.patron_id,
115
                            patron: booking.patron_id,
113
                            pickup_library: booking.pickup_library_id,
116
                            pickup_library: booking.pickup_library_id,
114
                            start: dayjs(booking.start_date).toDate(),
117
                            start: $toDisplayDate(startServerTz),
115
                            end: dayjs(booking.end_date).toDate(),
118
                            end: $toDisplayDate(endServerTz),
116
                            content: !isActive ? `<s>${patronContent}</s>` : patronContent,
119
                            content: !isActive ? `<s>${patronContent}</s>` : patronContent,
117
                            className: booking.status === "cancelled" ? "cancelled" : "",
120
                            className: booking.status === "cancelled" ? "cancelled" : "",
118
                            [% IF CAN_user_circulate_manage_bookings %]
121
                            [% IF CAN_user_circulate_manage_bookings %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/modals/place_booking.js (-24 / +57 lines)
Lines 403-409 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
403
                            end_date: checkout.due_date,
403
                            end_date: checkout.due_date,
404
                            item_id: checkout.item_id,
404
                            item_id: checkout.item_id,
405
                            patron_id: checkout.patron_id,
405
                            patron_id: checkout.patron_id,
406
                            start_date: new Date().toISOString(),
406
                            start_date: dayjs().format(),
407
                        };
407
                        };
408
                        bookings.unshift(booking);
408
                        bookings.unshift(booking);
409
                    }
409
                    }
Lines 907-938 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
907
                            // Range set, update hidden fields and set available items
907
                            // Range set, update hidden fields and set available items
908
                            else if (selectedDates[0] && selectedDates[1]) {
908
                            else if (selectedDates[0] && selectedDates[1]) {
909
                                // set form fields from picker
909
                                // set form fields from picker
910
                                let picker_start = dayjs(selectedDates[0]);
910
                                // Extract local date and send as explicit UTC day boundaries
911
                                let picker_end = dayjs(selectedDates[1]).endOf(
911
                                // This preserves the user's selected DATE regardless of browser timezone
912
                                    "day"
912
                                // Using dayjs.utc() ensures startOf/endOf operate in UTC, not browser TZ
913
                                let startDate = dayjs(selectedDates[0]).format(
914
                                    "YYYY-MM-DD"
915
                                );
916
                                let endDate = dayjs(selectedDates[1]).format(
917
                                    "YYYY-MM-DD"
913
                                );
918
                                );
914
                                $("#booking_start_date").val(
919
                                $("#booking_start_date").val(
915
                                    picker_start.toISOString()
920
                                    dayjs
921
                                        .utc(startDate)
922
                                        .startOf("day")
923
                                        .toISOString()
916
                                );
924
                                );
917
                                $("#booking_end_date").val(
925
                                $("#booking_end_date").val(
918
                                    picker_end.toISOString()
926
                                    dayjs
927
                                        .utc(endDate)
928
                                        .endOf("day")
929
                                        .toISOString()
919
                                );
930
                                );
920
931
921
                                // set available items in select2
932
                                // set available items in select2
922
                                let booked_items = bookings.filter(
933
                                let booked_items = bookings.filter(
923
                                    function (booking) {
934
                                    function (booking) {
924
                                        let start_date = flatpickr.parseDate(
935
                                        // Parse and normalize dates to start-of-day for consistent comparison
936
                                        let start_date = dayjs(
925
                                            booking.start_date
937
                                            booking.start_date
926
                                        );
938
                                        )
927
                                        let end_date = flatpickr.parseDate(
939
                                            .startOf("day")
928
                                            booking.end_date
940
                                            .toDate();
929
                                        );
941
                                        let end_date = dayjs(booking.end_date)
942
                                            .startOf("day")
943
                                            .toDate();
944
                                        let selectedStart = dayjs(
945
                                            selectedDates[0]
946
                                        )
947
                                            .startOf("day")
948
                                            .toDate();
949
                                        let selectedEnd = dayjs(
950
                                            selectedDates[1]
951
                                        )
952
                                            .startOf("day")
953
                                            .toDate();
954
930
                                        // This booking ends before the start of the new booking
955
                                        // This booking ends before the start of the new booking
931
                                        if (end_date <= selectedDates[0]) {
956
                                        if (end_date < selectedStart) {
932
                                            return false;
957
                                            return false;
933
                                        }
958
                                        }
934
                                        // This booking starts after then end of the new booking
959
                                        // This booking starts after the end of the new booking
935
                                        if (start_date >= selectedDates[1]) {
960
                                        if (start_date > selectedEnd) {
936
                                            return false;
961
                                            return false;
937
                                        }
962
                                        }
938
                                        // This booking overlaps
963
                                        // This booking overlaps
Lines 999-1006 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
999
                    const item_id = booking.item_id;
1024
                    const item_id = booking.item_id;
1000
1025
1001
                    // Iterate through each date within the range of start_date and end_date
1026
                    // Iterate through each date within the range of start_date and end_date
1002
                    let currentDate = dayjs(start_date);
1027
                    // Use dayjs to maintain browser timezone consistency
1003
                    while (currentDate.isSameOrBefore(end_date, "day")) {
1028
                    let currentDate = dayjs(start_date).startOf("day");
1029
                    const endDate = dayjs(end_date).startOf("day");
1030
                    while (currentDate.isSameOrBefore(endDate, "day")) {
1031
                        // Format in browser timezone - no UTC conversion
1004
                        const currentDateStr = currentDate.format("YYYY-MM-DD");
1032
                        const currentDateStr = currentDate.format("YYYY-MM-DD");
1005
1033
1006
                        // If the date key doesn't exist in the hash, create an empty array for it
1034
                        // If the date key doesn't exist in the hash, create an empty array for it
Lines 1024-1032 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
1024
                    periodPicker.config.onDayCreate.push(
1052
                    periodPicker.config.onDayCreate.push(
1025
                        function dayCreate(dObj, dStr, instance, dayElem) {
1053
                        function dayCreate(dObj, dStr, instance, dayElem) {
1026
                            const currentDate = dayElem.dateObj;
1054
                            const currentDate = dayElem.dateObj;
1027
                            const dateString = currentDate
1055
                            // Format in browser timezone to match bookingsByDate keys
1028
                                .toISOString()
1056
                            const dateString =
1029
                                .split("T")[0];
1057
                                dayjs(currentDate).format("YYYY-MM-DD");
1030
1058
1031
                            const isBold = boldDates.some(
1059
                            const isBold = boldDates.some(
1032
                                boldDate =>
1060
                                boldDate =>
Lines 2237-2248 $("#placeBookingForm").on("submit", function (e) { Link Here
2237
                bookings_table.api().ajax.reload();
2265
                bookings_table.api().ajax.reload();
2238
            }
2266
            }
2239
            if (typeof timeline !== "undefined" && timeline !== null) {
2267
            if (typeof timeline !== "undefined" && timeline !== null) {
2268
                // Convert to library timezone for timeline display
2269
                const startServerTz = dayjs(data.start_date).tz($timezone());
2270
                const endServerTz = dayjs(data.end_date).tz($timezone());
2240
                timeline.itemsData.add({
2271
                timeline.itemsData.add({
2241
                    id: data.booking_id,
2272
                    id: data.booking_id,
2242
                    booking: data.booking_id,
2273
                    booking: data.booking_id,
2243
                    patron: data.patron_id,
2274
                    patron: data.patron_id,
2244
                    start: dayjs(data.start_date).toDate(),
2275
                    start: $toDisplayDate(startServerTz),
2245
                    end: dayjs(data.end_date).toDate(),
2276
                    end: $toDisplayDate(endServerTz),
2246
                    content: $patron_to_html(booking_patron, {
2277
                    content: $patron_to_html(booking_patron, {
2247
                        display_cardnumber: true,
2278
                        display_cardnumber: true,
2248
                        url: false,
2279
                        url: false,
Lines 2346-2357 $("#placeBookingForm").on("submit", function (e) { Link Here
2346
                bookings_table.api().ajax.reload();
2377
                bookings_table.api().ajax.reload();
2347
            }
2378
            }
2348
            if (typeof timeline !== "undefined" && timeline !== null) {
2379
            if (typeof timeline !== "undefined" && timeline !== null) {
2380
                // Convert to library timezone for timeline display
2381
                const startServerTz = dayjs(data.start_date).tz($timezone());
2382
                const endServerTz = dayjs(data.end_date).tz($timezone());
2349
                timeline.itemsData.update({
2383
                timeline.itemsData.update({
2350
                    id: data.booking_id,
2384
                    id: data.booking_id,
2351
                    booking: data.booking_id,
2385
                    booking: data.booking_id,
2352
                    patron: data.patron_id,
2386
                    patron: data.patron_id,
2353
                    start: dayjs(data.start_date).toDate(),
2387
                    start: $toDisplayDate(startServerTz),
2354
                    end: dayjs(data.end_date).toDate(),
2388
                    end: $toDisplayDate(endServerTz),
2355
                    content: $patron_to_html(booking_patron, {
2389
                    content: $patron_to_html(booking_patron, {
2356
                        display_cardnumber: true,
2390
                        display_cardnumber: true,
2357
                        url: false,
2391
                        url: false,
2358
- 

Return to bug 37707