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 929-960 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
929
                            // Range set, update hidden fields and set available items
929
                            // Range set, update hidden fields and set available items
930
                            else if (selectedDates[0] && selectedDates[1]) {
930
                            else if (selectedDates[0] && selectedDates[1]) {
931
                                // set form fields from picker
931
                                // set form fields from picker
932
                                let picker_start = dayjs(selectedDates[0]);
932
                                // Extract local date and send as explicit UTC day boundaries
933
                                let picker_end = dayjs(selectedDates[1]).endOf(
933
                                // This preserves the user's selected DATE regardless of browser timezone
934
                                    "day"
934
                                // Using dayjs.utc() ensures startOf/endOf operate in UTC, not browser TZ
935
                                let startDate = dayjs(selectedDates[0]).format(
936
                                    "YYYY-MM-DD"
937
                                );
938
                                let endDate = dayjs(selectedDates[1]).format(
939
                                    "YYYY-MM-DD"
935
                                );
940
                                );
936
                                $("#booking_start_date").val(
941
                                $("#booking_start_date").val(
937
                                    picker_start.toISOString()
942
                                    dayjs
943
                                        .utc(startDate)
944
                                        .startOf("day")
945
                                        .toISOString()
938
                                );
946
                                );
939
                                $("#booking_end_date").val(
947
                                $("#booking_end_date").val(
940
                                    picker_end.toISOString()
948
                                    dayjs
949
                                        .utc(endDate)
950
                                        .endOf("day")
951
                                        .toISOString()
941
                                );
952
                                );
942
953
943
                                // set available items in select2
954
                                // set available items in select2
944
                                let booked_items = bookings.filter(
955
                                let booked_items = bookings.filter(
945
                                    function (booking) {
956
                                    function (booking) {
946
                                        let start_date = flatpickr.parseDate(
957
                                        // Parse and normalize dates to start-of-day for consistent comparison
958
                                        let start_date = dayjs(
947
                                            booking.start_date
959
                                            booking.start_date
948
                                        );
960
                                        )
949
                                        let end_date = flatpickr.parseDate(
961
                                            .startOf("day")
950
                                            booking.end_date
962
                                            .toDate();
951
                                        );
963
                                        let end_date = dayjs(booking.end_date)
964
                                            .startOf("day")
965
                                            .toDate();
966
                                        let selectedStart = dayjs(
967
                                            selectedDates[0]
968
                                        )
969
                                            .startOf("day")
970
                                            .toDate();
971
                                        let selectedEnd = dayjs(
972
                                            selectedDates[1]
973
                                        )
974
                                            .startOf("day")
975
                                            .toDate();
976
952
                                        // This booking ends before the start of the new booking
977
                                        // This booking ends before the start of the new booking
953
                                        if (end_date <= selectedDates[0]) {
978
                                        if (end_date < selectedStart) {
954
                                            return false;
979
                                            return false;
955
                                        }
980
                                        }
956
                                        // This booking starts after then end of the new booking
981
                                        // This booking starts after the end of the new booking
957
                                        if (start_date >= selectedDates[1]) {
982
                                        if (start_date > selectedEnd) {
958
                                            return false;
983
                                            return false;
959
                                        }
984
                                        }
960
                                        // This booking overlaps
985
                                        // This booking overlaps
Lines 1021-1028 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
1021
                    const item_id = booking.item_id;
1046
                    const item_id = booking.item_id;
1022
1047
1023
                    // Iterate through each date within the range of start_date and end_date
1048
                    // Iterate through each date within the range of start_date and end_date
1024
                    let currentDate = dayjs(start_date);
1049
                    // Use dayjs to maintain browser timezone consistency
1025
                    while (currentDate.isSameOrBefore(end_date, "day")) {
1050
                    let currentDate = dayjs(start_date).startOf("day");
1051
                    const endDate = dayjs(end_date).startOf("day");
1052
                    while (currentDate.isSameOrBefore(endDate, "day")) {
1053
                        // Format in browser timezone - no UTC conversion
1026
                        const currentDateStr = currentDate.format("YYYY-MM-DD");
1054
                        const currentDateStr = currentDate.format("YYYY-MM-DD");
1027
1055
1028
                        // If the date key doesn't exist in the hash, create an empty array for it
1056
                        // If the date key doesn't exist in the hash, create an empty array for it
Lines 1046-1054 $("#placeBookingModal").on("show.bs.modal", function (e) { Link Here
1046
                    periodPicker.config.onDayCreate.push(
1074
                    periodPicker.config.onDayCreate.push(
1047
                        function dayCreate(dObj, dStr, instance, dayElem) {
1075
                        function dayCreate(dObj, dStr, instance, dayElem) {
1048
                            const currentDate = dayElem.dateObj;
1076
                            const currentDate = dayElem.dateObj;
1049
                            const dateString = currentDate
1077
                            // Format in browser timezone to match bookingsByDate keys
1050
                                .toISOString()
1078
                            const dateString =
1051
                                .split("T")[0];
1079
                                dayjs(currentDate).format("YYYY-MM-DD");
1052
1080
1053
                            const isBold = boldDates.some(
1081
                            const isBold = boldDates.some(
1054
                                boldDate =>
1082
                                boldDate =>
Lines 2259-2270 $("#placeBookingForm").on("submit", function (e) { Link Here
2259
                bookings_table.api().ajax.reload();
2287
                bookings_table.api().ajax.reload();
2260
            }
2288
            }
2261
            if (typeof timeline !== "undefined" && timeline !== null) {
2289
            if (typeof timeline !== "undefined" && timeline !== null) {
2290
                // Convert to library timezone for timeline display
2291
                const startServerTz = dayjs(data.start_date).tz($timezone());
2292
                const endServerTz = dayjs(data.end_date).tz($timezone());
2262
                timeline.itemsData.add({
2293
                timeline.itemsData.add({
2263
                    id: data.booking_id,
2294
                    id: data.booking_id,
2264
                    booking: data.booking_id,
2295
                    booking: data.booking_id,
2265
                    patron: data.patron_id,
2296
                    patron: data.patron_id,
2266
                    start: dayjs(data.start_date).toDate(),
2297
                    start: $toDisplayDate(startServerTz),
2267
                    end: dayjs(data.end_date).toDate(),
2298
                    end: $toDisplayDate(endServerTz),
2268
                    content: $patron_to_html(booking_patron, {
2299
                    content: $patron_to_html(booking_patron, {
2269
                        display_cardnumber: true,
2300
                        display_cardnumber: true,
2270
                        url: false,
2301
                        url: false,
Lines 2368-2379 $("#placeBookingForm").on("submit", function (e) { Link Here
2368
                bookings_table.api().ajax.reload();
2399
                bookings_table.api().ajax.reload();
2369
            }
2400
            }
2370
            if (typeof timeline !== "undefined" && timeline !== null) {
2401
            if (typeof timeline !== "undefined" && timeline !== null) {
2402
                // Convert to library timezone for timeline display
2403
                const startServerTz = dayjs(data.start_date).tz($timezone());
2404
                const endServerTz = dayjs(data.end_date).tz($timezone());
2371
                timeline.itemsData.update({
2405
                timeline.itemsData.update({
2372
                    id: data.booking_id,
2406
                    id: data.booking_id,
2373
                    booking: data.booking_id,
2407
                    booking: data.booking_id,
2374
                    patron: data.patron_id,
2408
                    patron: data.patron_id,
2375
                    start: dayjs(data.start_date).toDate(),
2409
                    start: $toDisplayDate(startServerTz),
2376
                    end: dayjs(data.end_date).toDate(),
2410
                    end: $toDisplayDate(endServerTz),
2377
                    content: $patron_to_html(booking_patron, {
2411
                    content: $patron_to_html(booking_patron, {
2378
                        display_cardnumber: true,
2412
                        display_cardnumber: true,
2379
                        url: false,
2413
                        url: false,
2380
- 

Return to bug 37707