From ec59af8d5f794a14382f198adc2ce42e8078d097 Mon Sep 17 00:00:00 2001 From: Johann Hoffmann Date: Wed, 2 Sep 2026 13:52:47 +0200 Subject: [PATCH] fixed date format timzeone issue since native js date was still in use there; consolidated date helpers to use the luxon approach from abgabetool datehelpers and put them into the generic DateHelpers.js file; also replaced a few other sketchy date formats with the consolidated helpers function; --- public/js/apps/Cis/Cis.js | 8 +- .../Cis/Abgabetool/AbgabeMitarbeiterDetail.js | 14 +-- .../Cis/Abgabetool/AbgabeStudentDetail.js | 8 +- .../Cis/Abgabetool/AbgabeStudentTimeline.js | 2 +- .../Cis/Abgabetool/AbgabetoolAssistenz.js | 56 ++------- .../Cis/Abgabetool/AbgabetoolMitarbeiter.js | 34 ++---- .../Cis/Abgabetool/AbgabetoolStudent.js | 5 +- .../js/components/Cis/Abgabetool/dateUtils.js | 33 ------ .../Cis/Abgabetool/getDateStyleClass.js | 12 +- public/js/components/Cis/Mylv/LvInfo.js | 21 +--- .../js/components/Cis/Mylv/RoomInformation.js | 3 +- .../js/components/Cis/Raumsuche/Raumsuche.js | 33 ++---- .../js/components/DashboardWidget/Abstract.js | 13 +- public/js/components/DashboardWidget/News.js | 12 +- public/js/helpers/DateHelpers.js | 112 ++++++++++-------- 15 files changed, 128 insertions(+), 238 deletions(-) delete mode 100644 public/js/components/Cis/Abgabetool/dateUtils.js diff --git a/public/js/apps/Cis/Cis.js b/public/js/apps/Cis/Cis.js index d4f846f25..6e67ea607 100644 --- a/public/js/apps/Cis/Cis.js +++ b/public/js/apps/Cis/Cis.js @@ -22,6 +22,7 @@ import OtherLvPlan from "../../components/Cis/LvPlan/OtherLvPlan.js"; import ApiRouteInfo from '../../api/factory/routeinfo.js'; import {capitalize} from "../../helpers/StringHelpers.js"; +import { getTodayISO, toDateTime } from "../../helpers/DateHelpers.js"; const ciPath = FHC_JS_DATA_STORAGE_OBJECT.app_root.replace(/(https:|)(^|\/\/)(.*?\/)/g, '') + FHC_JS_DATA_STORAGE_OBJECT.ci_router; const isMobile = window.matchMedia("(max-width: 767px)").matches; @@ -88,7 +89,7 @@ const router = VueRouter.createRouter({ params: { // in this case always populate other params since they are not optional ort_kurzbz: to.params.ort_kurzbz, mode: isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP, - focus_date: new Date().toISOString().split("T")[0] + focus_date: getTodayISO() }, }; }, @@ -107,8 +108,7 @@ const router = VueRouter.createRouter({ : (isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP); // default to today date if not provided - const d = new Date(route.params.focus_date) - const focus_date = !isNaN(d) ? route.params.focus_date : new Date().toISOString().split("T")[0]; + const focus_date = toDateTime(route.params.focus_date) ? route.params.focus_date : getTodayISO(); // for consistency reasons format the props into one object but actually use a new name to we dont collide with // existing viewData declaration written from codeigniter 3 into routerview tag @@ -127,7 +127,7 @@ const router = VueRouter.createRouter({ name: "RoomInformation", params: { mode: to.params.mode || DEFAULT_MODE_RAUMINFO, - focus_date: to.params.focus_date || new Date().toISOString().split("T")[0], + focus_date: to.params.focus_date || getTodayISO(), ort_kurzbz: route.params.ort_kurzbz } }); diff --git a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js index b3bbeb1a2..23f4ab761 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeMitarbeiterDetail.js @@ -2,7 +2,7 @@ import BsModal from '../../Bootstrap/Modal.js'; import VueDatePicker from '../../vueDatepicker.js.php'; import ApiAbgabe from '../../../api/factory/abgabe.js' import { getDateStyleClass } from "./getDateStyleClass.js"; -import {compareISODateValues, formatDateTime, formatISODate, getViennaTodayISO} from "./dateUtils.js"; +import { compareISODateValues, formatDate, formatDateTime, getTodayISO } from "../../../helpers/DateHelpers.js"; export const AbgabeMitarbeiterDetail = { @@ -174,7 +174,7 @@ export const AbgabeMitarbeiterDetail = { 'fixtermin': false, 'invertedFixtermin': true, 'kurzbz': '', // todo kurzbz textfield value vorschlag für qualgates - 'datum': getViennaTodayISO(), + 'datum': getTodayISO(), 'note': this.allowedNotenOptions.find(opt => opt.note == 9), 'beurteilungsnotiz': '', 'upload_allowed': false, @@ -351,9 +351,7 @@ export const AbgabeMitarbeiterDetail = { } } }, - formatDate(dateParam) { - return formatISODate(dateParam) - }, + formatDate, openCreateNewAbgabeModal() { if(this.projektarbeit?.betreuerart_kurzbz == 'Zweitbegutachter') { // maybe alert that this is not allowed or smth @@ -370,7 +368,7 @@ export const AbgabeMitarbeiterDetail = { 'fixtermin': false, 'invertedFixtermin': true, 'kurzbz': '', - 'datum': getViennaTodayISO(), + 'datum': getTodayISO(), 'note': this.allowedNotenOptions.find(opt => opt.note == 9), 'beurteilungsnotiz': '', 'upload_allowed': typ.upload_allowed_default, @@ -404,7 +402,7 @@ export const AbgabeMitarbeiterDetail = { 'fixtermin': false, 'invertedFixtermin': true, 'kurzbz': '', - 'datum': getViennaTodayISO(), + 'datum': getTodayISO(), 'note': this.allowedNotenOptions.find(opt => opt.note == 9), 'beurteilungsnotiz': '', 'upload_allowed': false, @@ -603,7 +601,7 @@ export const AbgabeMitarbeiterDetail = { 'fixtermin': false, 'invertedFixtermin': true, 'kurzbz': '', - 'datum': getViennaTodayISO(), + 'datum': getTodayISO(), 'note': this.allowedNotenOptions.find(opt => opt.note == 9), 'beurteilungsnotiz': '', 'upload_allowed': typ.upload_allowed_default, diff --git a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js index 80750babc..6bc447b9f 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js +++ b/public/js/components/Cis/Abgabetool/AbgabeStudentDetail.js @@ -3,7 +3,7 @@ import BsModal from '../../Bootstrap/Modal.js'; import VueDatePicker from '../../vueDatepicker.js.php'; import ApiAbgabe from '../../../api/factory/abgabe.js' import FhcOverlay from "../../Overlay/FhcOverlay.js"; -import { formatISODate, getViennaTodayISO } from "./dateUtils.js"; +import { formatDate, getTodayISO } from "../../../helpers/DateHelpers.js"; import { validateThesisTitle } from './titleValidation.js' @@ -248,9 +248,7 @@ export const AbgabeStudentDetail = { window.open(FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + url) }, - formatDate(dateParam) { - return formatISODate(dateParam) - }, + formatDate, async upload(termin) { if (! await this.validate(termin)) @@ -286,7 +284,7 @@ export const AbgabeStudentDetail = { if(res.meta.status == "success") { this.$fhcAlert.alertSuccess(this.$capitalize(this.$p.t('abgabetool/c4fileUploadSuccessv3'))) - termin.abgabedatum = getViennaTodayISO(); + termin.abgabedatum = getTodayISO(); if(res?.data?.signatur !== undefined) { termin.signatur = res.data.signatur } diff --git a/public/js/components/Cis/Abgabetool/AbgabeStudentTimeline.js b/public/js/components/Cis/Abgabetool/AbgabeStudentTimeline.js index 7b2986637..ad0e9f824 100644 --- a/public/js/components/Cis/Abgabetool/AbgabeStudentTimeline.js +++ b/public/js/components/Cis/Abgabetool/AbgabeStudentTimeline.js @@ -1,5 +1,5 @@ import AbgabeterminStatusLegende from "./StatusLegende.js"; -import {formatDateTime} from "./dateUtils.js"; +import { formatDateTime } from "../../../helpers/DateHelpers.js"; export const AbgabeStudentTimeline = { name: "AbgabeStudentTimeline", diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js index 3d8fc1d79..ab9c78432 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolAssistenz.js @@ -11,7 +11,7 @@ import AbgabeStudentTimeline from "./AbgabeStudentTimeline.js"; import { splitMailsHelper } from "../../../helpers/EmailHelpers.js" import { getDateStyleClass} from "./getDateStyleClass.js"; import { dateFilter } from '../../../tabulator/filters/DatesManual.js'; -import { compareISODateValues, formatISODate, getViennaTodayISO, toViennaDate } from "./dateUtils.js"; +import { compareISODateValues, formatDate, getNow, getTodayISO, toDateTime } from "../../../helpers/DateHelpers.js"; export const AbgabetoolAssistenz = { name: "AbgabetoolAssistenz", @@ -114,7 +114,7 @@ export const AbgabetoolAssistenz = { fixtermin: false, }, serienTermin: Vue.reactive({ - datum: getViennaTodayISO(), + datum: getTodayISO(), bezeichnung: { paabgabetyp_kurzbz: 'zwischen', bezeichnung: 'Zwischenabgabe' @@ -643,7 +643,7 @@ export const AbgabetoolAssistenz = { upload_allowed: false, fixtermin: false, } - this.serienEdit.datum = getViennaTodayISO() + this.serienEdit.datum = getTodayISO() this.serienEdit.bezeichnung = this.abgabeTypeOptions.find(opt => opt.paabgabetyp_kurzbz === 'zwischen') this.serienEdit.kurzbz = '' this.serienEdit.upload_allowed = false @@ -1091,23 +1091,9 @@ export const AbgabetoolAssistenz = { return false; } - const toLuxon = (val) => { - if (!val) return null; - let dt; - if (val instanceof Date) { - dt = luxon.DateTime.fromJSDate(val); - } else if (typeof val === "string") { - dt = toViennaDate(val); - } else { // fallback - dt = luxon.DateTime.fromMillis(Number(val)); - } - - return dt.isValid ? dt : null; - }; - - const rowDate = toLuxon(rowVal); - const von = toLuxon(filterVal[0]); - const bis = toLuxon(filterVal[1]); + const rowDate = toDateTime(rowVal); + const von = toDateTime(filterVal[0]); + const bis = toDateTime(filterVal[1]); // specific day if (von && !bis) { @@ -1128,22 +1114,8 @@ export const AbgabetoolAssistenz = { const rowDate = rowVal.luxonDate; - const toLuxon = (val) => { - if (!val) return null; - let dt; - if (val instanceof Date) { - dt = luxon.DateTime.fromJSDate(val); - } else if (typeof val === "string") { - dt = toViennaDate(val); - } else { // fallback - dt = luxon.DateTime.fromMillis(Number(val)); - } - - return dt.isValid ? dt : null; - }; - - const von = toLuxon(filterVal[0]); - const bis = toLuxon(filterVal[1]); + const von = toDateTime(filterVal[0]); + const bis = toDateTime(filterVal[1]); // specific day if (von && !bis) { @@ -1372,7 +1344,7 @@ export const AbgabetoolAssistenz = { }, checkAbgabetermineProjektarbeit(projekt) { - const now = luxon.DateTime.now() + const now = getNow() // calculate Abgabetermin time diff to now and assign last and next to projekt projekt.abgabetermine.forEach(termin => { @@ -1387,7 +1359,7 @@ export const AbgabetoolAssistenz = { // while already looping through each termin, calculate datestyle beforehand termin.dateStyle = getDateStyleClass(termin, this.notenOptions) - const date = toViennaDate(termin.datum).endOf('day') + const date = toDateTime(termin.datum).endOf('day') termin.luxonDate = date termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past @@ -1654,9 +1626,7 @@ export const AbgabetoolAssistenz = { getNotenFilterOptionLabel(option) { return option.bezeichnung }, - formatDate(dateParam) { - return formatISODate(dateParam); - }, + formatDate, formAction(cell) { const actionButtons = document.createElement('div'); actionButtons.className = "d-flex gap-3"; @@ -1851,9 +1821,7 @@ export const AbgabetoolAssistenz = { return str }, isPastDate(date) { - const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day'); - const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna'); - return nowInVienna > deadline; + return getNow() > toDateTime(date).endOf("day"); }, setDetailComponent(details){ const pa = this.projektarbeiten.find(projektarbeit => projektarbeit.projektarbeit_id == details.projektarbeit_id) diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js index b13010cad..86c5daed8 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolMitarbeiter.js @@ -7,7 +7,7 @@ import FhcOverlay from "../../Overlay/FhcOverlay.js"; import { getDateStyleClass } from "./getDateStyleClass.js"; import { dateFilter } from '../../../tabulator/filters/DatesManual.js'; import {splitMailsHelper} from "../../../helpers/EmailHelpers.js"; -import { formatISODate, getViennaTodayISO, toViennaDate } from "./dateUtils.js"; +import { formatDate, getNow, getTodayISO, toDateTime } from "../../../helpers/DateHelpers.js"; export const AbgabetoolMitarbeiter = { name: "AbgabetoolMitarbeiter", @@ -54,7 +54,7 @@ export const AbgabetoolMitarbeiter = { allowedNotenOptions: null, notenOptionsNonFinal: null, serienTermin: Vue.reactive({ - datum: getViennaTodayISO(), + datum: getTodayISO(), bezeichnung: { paabgabetyp_kurzbz: 'zwischen', bezeichnung: 'Zwischenabgabe' @@ -668,22 +668,8 @@ export const AbgabetoolMitarbeiter = { const rowDate = rowVal.luxonDate; - const toLuxon = (val) => { - if (!val) return null; - let dt; - if (val instanceof Date) { - dt = luxon.DateTime.fromJSDate(val); - } else if (typeof val === "string") { - dt = toViennaDate(val); - } else { // fallback - dt = luxon.DateTime.fromMillis(Number(val)); - } - - return dt.isValid ? dt : null; - }; - - const von = toLuxon(filterVal[0]); - const bis = toLuxon(filterVal[1]); + const von = toDateTime(filterVal[0]); + const bis = toDateTime(filterVal[1]); // specific day if (von && !bis) { @@ -899,14 +885,14 @@ export const AbgabetoolMitarbeiter = { } }, checkAbgabetermineProjektarbeit(projekt) { - const now = luxon.DateTime.now() + const now = getNow() // calculate Abgabetermin time diff to now and assign last and next to projekt projekt.abgabetermine.forEach(termin => { // while already looping through each termin, calculate datestyle beforehand termin.dateStyle = getDateStyleClass(termin, this.notenOptions) - const date = toViennaDate(termin.datum).endOf('day') + const date = toDateTime(termin.datum).endOf('day') termin.luxonDate = date termin.diffMs = date.toMillis() - now.toMillis(); // positive = future, negative = past @@ -1017,9 +1003,7 @@ export const AbgabetoolMitarbeiter = { getOptionLabelAbgabetyp(option){ return this.$p.t('abgabetool/c4paatyp' + option.paabgabetyp_kurzbz) }, - formatDate(dateParam) { - return formatISODate(dateParam); - }, + formatDate, undoSelection(cell) { // checks if cells row is selected and unselects -> imitates columns which dont trigger row selection // but actually just revert it after the fact @@ -1104,9 +1088,7 @@ export const AbgabetoolMitarbeiter = { return str }, isPastDate(date) { - const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day'); - const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna'); - return nowInVienna > deadline; + return getNow() > toDateTime(date).endOf("day"); }, setDetailComponent(details){ this.loading=true diff --git a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js index 1c00c21e9..d203c8877 100644 --- a/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js +++ b/public/js/components/Cis/Abgabetool/AbgabetoolStudent.js @@ -4,6 +4,7 @@ import ApiAuthinfo from '../../../api/factory/authinfo.js'; import BsModal from "../../Bootstrap/Modal.js"; import FhcOverlay from "../../Overlay/FhcOverlay.js"; import { getDateStyleClass} from "./getDateStyleClass.js"; +import { getNow, toDateTime } from "../../../helpers/DateHelpers.js"; import { validateThesisTitle } from './titleValidation.js' export const AbgabetoolStudent = { @@ -171,9 +172,7 @@ export const AbgabetoolStudent = { return qgate1positiv && qgate2positiv }, isPastDate(date) { - const deadline = luxon.DateTime.fromISO(date, { zone: 'Europe/Vienna' }).endOf('day'); - const nowInVienna = luxon.DateTime.now().setZone('Europe/Vienna'); - return nowInVienna > deadline; + return getNow() > toDateTime(date).endOf("day"); }, setDetailComponent(details){ this.loading = true diff --git a/public/js/components/Cis/Abgabetool/dateUtils.js b/public/js/components/Cis/Abgabetool/dateUtils.js deleted file mode 100644 index 65971cf0a..000000000 --- a/public/js/components/Cis/Abgabetool/dateUtils.js +++ /dev/null @@ -1,33 +0,0 @@ -const zone = 'Europe/Vienna'; - -export function getViennaTodayISO() { - return luxon.DateTime.now().setZone(zone).toISODate(); -} - -export function formatISODate(dateParam) { - if (!dateParam) return ''; - - const date = luxon.DateTime.fromISO(String(dateParam), { zone }); - return date.isValid ? date.toFormat('dd.MM.yyyy') : ''; -} - -export function formatDateTime(dateParam) { - if (!dateParam) return ''; - - const date = luxon.DateTime.fromSQL(dateParam); - return date.isValid ? date.toFormat("dd.MM.yyyy HH:mm") : ''; -} - -export function toViennaDate(dateParam) { - if (!dateParam) return null; - - return luxon.DateTime.fromISO(String(dateParam), { zone }); -} - -export function compareISODateValues(a, b) { - if (!a && !b) return 0; - if (!a) return 1; - if (!b) return -1; - - return String(a).localeCompare(String(b)); -} diff --git a/public/js/components/Cis/Abgabetool/getDateStyleClass.js b/public/js/components/Cis/Abgabetool/getDateStyleClass.js index b6d8e3b78..823019920 100644 --- a/public/js/components/Cis/Abgabetool/getDateStyleClass.js +++ b/public/js/components/Cis/Abgabetool/getDateStyleClass.js @@ -1,12 +1,12 @@ -const zone = 'Europe/Vienna'; +import { getNow, toDateTime } from "../../../helpers/DateHelpers.js"; export function getDateStyleClass(termin, notenOptions) { - const today = luxon.DateTime.now().setZone(zone); - const datum = luxon.DateTime.fromISO(termin.datum, { zone }).endOf('day'); - const abgabedatum = termin.abgabedatum ? luxon.DateTime.fromISO(termin.abgabedatum, { zone }) : null; - termin.diffindays = datum.diff(today, 'days').days; - const isLate = abgabedatum && abgabedatum > datum; + const today = getNow(); + const datum = toDateTime(termin.datum)?.endOf('day'); + const abgabedatum = toDateTime(termin.abgabedatum); + termin.diffindays = datum ? datum.diff(today, 'days').days : NaN; + const isLate = abgabedatum && datum && abgabedatum > datum; // GRADE STATUS if (termin.note) { diff --git a/public/js/components/Cis/Mylv/LvInfo.js b/public/js/components/Cis/Mylv/LvInfo.js index 4fdbb695d..6cd557ba5 100644 --- a/public/js/components/Cis/Mylv/LvInfo.js +++ b/public/js/components/Cis/Mylv/LvInfo.js @@ -1,4 +1,4 @@ -import { numberPadding, formatDate } from "../../../helpers/DateHelpers.js" +import { formatDate, formatTime } from "../../../helpers/DateHelpers.js" export default { props: { @@ -25,27 +25,14 @@ export default { return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + `/CisVue/Cms/content/${this.event.ort_content_id}` }, start_time: function () { - if (!this.event.start) return 'N/A'; - if (!this.event.start instanceof Date) { - return this.event.start; - } - return numberPadding(this.event.start.getHours()) + ":" + numberPadding(this.event.start.getMinutes()); + return this.event.start ? formatTime(this.event.start) : 'N/A'; }, end_time: function () { - if (!this.event.end) return 'N/A'; - if (!this.event.end instanceof Date) { - return this.event.end; - } - return numberPadding(this.event.end.getHours()) + ":" + numberPadding(this.event.end.getMinutes()); + return this.event.end ? formatTime(this.event.end) : 'N/A'; } }, methods: { - mehtodNumberPadding: function (number) { - return numberPadding(number); - }, - methodFormatDate: function (d) { - return formatDate(d); - }, + methodFormatDate: formatDate, }, template:/*html*/` diff --git a/public/js/components/Cis/Mylv/RoomInformation.js b/public/js/components/Cis/Mylv/RoomInformation.js index 3a07e0a76..e5833da3f 100644 --- a/public/js/components/Cis/Mylv/RoomInformation.js +++ b/public/js/components/Cis/Mylv/RoomInformation.js @@ -1,6 +1,7 @@ import FhcCalendar from "../../Calendar/LvPlan.js"; import ApiLvPlan from '../../../api/factory/lvPlan.js'; +import { getTodayISO } from "../../../helpers/DateHelpers.js"; export const DEFAULT_MODE_RAUMINFO_MOBILE = 'List'; export const DEFAULT_MODE_RAUMINFO_DESKTOP = 'Week'; @@ -17,7 +18,7 @@ export default { inject: ["isMobile"], computed: { currentDay() { - return this.propsViewData?.focus_date || luxon.DateTime.now().setZone(FHC_JS_DATA_STORAGE_OBJECT.timezone).toISODate(); + return this.propsViewData?.focus_date || getTodayISO(); }, currentMode() { const defaultMode = this.isMobile ? DEFAULT_MODE_RAUMINFO_MOBILE : DEFAULT_MODE_RAUMINFO_DESKTOP; diff --git a/public/js/components/Cis/Raumsuche/Raumsuche.js b/public/js/components/Cis/Raumsuche/Raumsuche.js index 4790272a6..938d7afa9 100644 --- a/public/js/components/Cis/Raumsuche/Raumsuche.js +++ b/public/js/components/Cis/Raumsuche/Raumsuche.js @@ -1,6 +1,7 @@ import {CoreFilterCmpt} from "../../../components/filter/Filter.js"; import VueDatePicker from '../../vueDatepicker.js.php'; import ApiOrt from '../../../api/factory/ort.js' +import { getNow, getTodayISO } from "../../../helpers/DateHelpers.js"; export const Raumsuche = { name: "Raumsuche", components: { @@ -10,6 +11,8 @@ export const Raumsuche = { }, inject: ["isMobile"], data() { + const now = getNow(); + return { phrasenPromise: null, phrasenResolved: false, @@ -23,14 +26,14 @@ export const Raumsuche = { }, anzahl: 1, selectedType: null, - datum: new Date(), + datum: getTodayISO(), von: Vue.ref({ - hours: new Date().getHours(), - minutes: new Date().getMinutes() + hours: now.hour, + minutes: now.minute }), bis: Vue.ref({ - hours: new Date().getHours() + 1, - minutes: new Date().getMinutes() + hours: now.plus({ hours: 1 }).hour, + minutes: now.minute }), datepickerTextInputOptions: { enterSubmit: true, @@ -136,7 +139,7 @@ export const Raumsuche = { }) }, loadRooms() { - this.$api.call(ApiOrt.getRooms(this.datum.toISOString(), this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl)) + this.$api.call(ApiOrt.getRooms(this.datum, this.getTimeString(this.von), this.getTimeString(this.bis), this.selectedType?.raumtyp_kurzbz ?? '', this.anzahl)) .then(res => { if(res?.data?.retval) this.setupData(res.data.retval) }) @@ -150,17 +153,6 @@ export const Raumsuche = { setRoute(val) { // TODO: router push }, - dateFormat(date) { - const day = String(date.getDate()).padStart(2, '0'); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const year = date.getFullYear(); - return `${day}.${month}.${year}` - }, - timeFormat(date) { - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${hours}:${minutes}`; - }, async setupMounted() { this.tableBuiltPromise = new Promise(this.tableResolve) @@ -199,7 +191,8 @@ export const Raumsuche = { :dark="isDarkMode" :clearable="false" :enable-time-picker="false" - :format="dateFormat" + format="dd.MM.yyyy" + model-type="yyyy-MM-dd" :text-input="datepickerTextInputOptions" :min-date="new Date()" date-picker @@ -213,7 +206,7 @@ export const Raumsuche = { v-model="von" :dark="isDarkMode" :clearable="false" - :format="timeFormat" + format="HH:mm" :text-input="timepickerTextInputOptions" :is-24="true" time-picker @@ -227,7 +220,7 @@ export const Raumsuche = { v-model="bis" :dark="isDarkMode" :clearable="false" - :format="timeFormat" + format="HH:mm" :text-input="timepickerTextInputOptions" :is-24="true" time-picker diff --git a/public/js/components/DashboardWidget/Abstract.js b/public/js/components/DashboardWidget/Abstract.js index 0b6a1151f..25652e1e7 100644 --- a/public/js/components/DashboardWidget/Abstract.js +++ b/public/js/components/DashboardWidget/Abstract.js @@ -1,3 +1,5 @@ +import { formatDate, formatDateTime } from "../../helpers/DateHelpers.js"; + export default { props: [ "config", @@ -30,14 +32,7 @@ export default { }, }, methods: { - formatDateTime: function(dateTime) { - const dt = new Date(dateTime); - return dt.getDate() + '.' + (dt.getMonth()+1) + '.' + dt.getFullYear() + ' | ' - + dt.getHours() + ':' + dt.getMinutes(); - }, - getDate: function(dateTime){ - const dt = new Date(dateTime); - return dt.getDate() + '.' + (dt.getMonth()+1) + '.' + dt.getFullYear(); - } + formatDateTime, + getDate: formatDate } } diff --git a/public/js/components/DashboardWidget/News.js b/public/js/components/DashboardWidget/News.js index 103bc4fa1..9fe1c016b 100644 --- a/public/js/components/DashboardWidget/News.js +++ b/public/js/components/DashboardWidget/News.js @@ -1,6 +1,6 @@ import AbstractWidget from './Abstract.js'; import BsModal from '../Bootstrap/Modal.js'; -import { numberPadding } from '../../helpers/DateHelpers.js'; +import { formatDate, formatTime } from '../../helpers/DateHelpers.js'; import ApiCms from '../../api/factory/cms.js'; const MAX_LOADED_NEWS = 30; @@ -84,14 +84,8 @@ export default { }); }) }, - formatDate: function (dateTime) { - const dt = new Date(dateTime); - return numberPadding(dt.getDate()) + '.' + numberPadding((dt.getMonth() + 1)) + '.' + dt.getFullYear(); - }, - formatTime: function (dateTime) { - const dt = new Date(dateTime); - return numberPadding(dt.getHours()) + ':' + numberPadding(dt.getMinutes()); - }, + formatDate, + formatTime, isString(value){ return Object.prototype.toString.call(value) === '[object String]'; }, diff --git a/public/js/helpers/DateHelpers.js b/public/js/helpers/DateHelpers.js index 9f0dc7dec..5750025be 100644 --- a/public/js/helpers/DateHelpers.js +++ b/public/js/helpers/DateHelpers.js @@ -1,61 +1,69 @@ -// HELPER FILE -- that contains multiple functions which create, handle and manipulate js dates +// Everything parses and formats with luxon in the server timezone. new Date("2026-09-02") +// reads UTC midnight and shows the day before west of Greenwich. -// custom Error class for DateHelpers.js misuse -class DateHelperError extends Error { - constructor(message) { - super(message); - this.name = "DateHelperError"; - } + +function getZone() { + return globalThis.FHC_JS_DATA_STORAGE_OBJECT?.timezone || 'Europe/Vienna'; } /** - * adds padding to a number + * converts a date value into a luxon DateTime in the server timezone * - * @param {number|string} number - the number on which padding should be added. - * @returns {string} number with padding. + * @param {string|number|Date|Object} value - ISO string, SQL string, milliseconds, Date or luxon DateTime. + * @returns {Object|null} luxon DateTime, or null if the value is empty or unparsable. */ +export function toDateTime(value) { + if (!value) return null; + + const zone = getZone(); + let date; + + if (value instanceof luxon.DateTime) date = value.setZone(zone); + else if (value instanceof Date) date = luxon.DateTime.fromJSDate(value, { zone }); + else if (typeof value === 'number') date = luxon.DateTime.fromMillis(value, { zone }); + // fromISO rejects the space that SQL puts between date and time + else if (typeof value === 'string') date = value.includes(' ') + ? luxon.DateTime.fromSQL(value, { zone }) + : luxon.DateTime.fromISO(value, { zone }); + else return null; + + return date.isValid ? date : null; +} + +// current time in the server timezone +export function getNow() { + return luxon.DateTime.now().setZone(getZone()); +} + +// today in the server timezone, as yyyy-MM-dd +export function getTodayISO() { + return getNow().toISODate(); +} + +// the three formatters return '' for an empty or unparsable value +export function formatDate(value) { + return toDateTime(value)?.toFormat('dd.MM.yyyy') ?? ''; +} + +export function formatTime(value) { + return toDateTime(value)?.toFormat('HH:mm') ?? ''; +} + +export function formatDateTime(value) { + return toDateTime(value)?.toFormat('dd.MM.yyyy HH:mm') ?? ''; +} + +// tabulator sorter for ISO dates, sorts empty values last +export function compareISODateValues(a, b) { + if (!a && !b) return 0; + if (!a) return 1; + if (!b) return -1; + + return String(a).localeCompare(String(b)); +} + +// pads hours and minutes to two digits export function numberPadding(number) { - if(typeof number !== "string" && typeof number !== "number") - { - throw new TypeError("function numberPadding in file DateHelpers.js is only usable with strings or numbers"); - } - if(number.toString().length > 2) - { - throw new DateHelperError("The number on which the padding should be added should not be longer than to 2 characters, please refere to the function numberPadding in the helper file DateHelpers.js"); - } - return number.toString().length == 1 ? '0' + number.toString() : number.toString(); -} - -/** - * formats date to dd.mm.yyyy - * - * @param {string|Date} d - the date that should be formatted. - * @returns {string} formatted date string. - */ -export function formatDate(d) { - // parameter is of type Date - if(d instanceof Date) - { - if (isNaN(date.valueOf())) { - return 'N/A'; - } - // if the date is an invalid string then creating a date from the string will fail and N/A is returned - return `${numberPadding(d.getDate())}.${numberPadding(d.getMonth() + 1)}.${d.getFullYear()}`; - } - // parameter is of type string - else if (typeof d === "string") - { - let date = new Date(d); - // if the date is an invalid string then creating a date from the string will fail and N/A is returned - if (isNaN(date.valueOf())) { - return 'N/A'; - } - return `${numberPadding(date.getDate())}.${numberPadding(date.getMonth() + 1)}.${date.getFullYear()}`; - } - // parameter is not of type string or Date and an exception is thrown - else - { - throw new TypeError("The parameter provided for this function is not a string or a Date object, please refere to the function formatDate in the DateHelpers.js file"); - } + return String(number).padStart(2, '0'); }