From ed67982ec0b01d5360ec964c9a558a176862fec0 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sun, 25 May 2025 23:32:50 +0200 Subject: [PATCH] Explicitly set locale to avoid hydration mismatch Some functions in luxon default to the system's locale while other functions default to "en-US". Explicitly set the locale everywhere the luxon objects are created to avoid possible mismatches and unexpected behaviour should the system's locale be different. --- components/EventCard.vue | 2 +- components/ScheduleTable.vue | 22 +++++++++++----------- components/ShiftScheduleTable.vue | 22 +++++++++++----------- components/Timetable.vue | 14 +++++++------- server/api/account.patch.ts | 2 +- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/components/EventCard.vue b/components/EventCard.vue index 6f0b33f..b2308fa 100644 --- a/components/EventCard.vue +++ b/components/EventCard.vue @@ -53,7 +53,7 @@ const { data: accounts } = await useAccounts(); const idToAccount = computed(() => new Map(accounts.value?.map(a => [a.id, a]))); function formatTime(time: string) { - return DateTime.fromISO(time, { zone: accountStore.activeTimezone }).toFormat("yyyy-LL-dd HH:mm"); + return DateTime.fromISO(time, { zone: accountStore.activeTimezone, locale: "en-US" }).toFormat("yyyy-LL-dd HH:mm"); } async function toggle(id: string, slotIds?: string[]) { diff --git a/components/ScheduleTable.vue b/components/ScheduleTable.vue index a0f6600..1440256 100644 --- a/components/ScheduleTable.vue +++ b/components/ScheduleTable.vue @@ -462,12 +462,12 @@ const newEventStart = ref(""); const newEventDuration = ref("01:00"); const newEventEnd = computed({ get: () => ( - DateTime.fromISO(newEventStart.value, { zone: accountStore.activeTimezone }) - .plus(Duration.fromISOTime(newEventDuration.value)) + DateTime.fromISO(newEventStart.value, { zone: accountStore.activeTimezone, locale: "en-US" }) + .plus(Duration.fromISOTime(newEventDuration.value, { locale: "en-US" })) .toFormat("HH:mm") ), set: (value: string) => { - const start = DateTime.fromISO(newEventStart.value, { zone: accountStore.activeTimezone }); + const start = DateTime.fromISO(newEventStart.value, { zone: accountStore.activeTimezone, locale: "en-US" }); const end = endFromTime(start, value); newEventDuration.value = dropDay(end.diff(start)).toFormat("hh:mm"); }, @@ -478,16 +478,16 @@ watch(() => props.location, () => { }); function endFromTime(start: DateTime, time: string) { - let end = start.startOf("day").plus(Duration.fromISOTime(time)); + let end = start.startOf("day").plus(Duration.fromISOTime(time, { locale: "en-US" })); if (end.toMillis() <= start.toMillis()) { end = end.plus({ days: 1 }); } return end; } function durationFromTime(time: string) { - let duration = Duration.fromISOTime(time); + let duration = Duration.fromISOTime(time, { locale: "en-US" }); if (duration.toMillis() === 0) { - duration = Duration.fromMillis(oneDayMs); + duration = Duration.fromMillis(oneDayMs, { locale: "en-US" }); } return duration; } @@ -504,7 +504,7 @@ function editEventSlot( } ) { if (edits.start) { - const start = DateTime.fromISO(edits.start, { zone: accountStore.activeTimezone }); + const start = DateTime.fromISO(edits.start, { zone: accountStore.activeTimezone, locale: "en-US" }); eventSlot = { ...eventSlot, start, @@ -573,7 +573,7 @@ function newEventSlot(options: { start?: DateTime, end?: DateTime } = {}) { end = options.end; start = options.end.minus(duration); } else { - start = DateTime.fromISO(newEventStart.value, { zone: accountStore.activeTimezone }); + start = DateTime.fromISO(newEventStart.value, { zone: accountStore.activeTimezone, locale: "en-US" }); end = endFromTime(start, newEventEnd.value); } if (!start.isValid || !end.isValid) { @@ -637,8 +637,8 @@ const eventSlots = computed(() => { location, assigned: slot.assigned ?? [], origLocation: location, - start: DateTime.fromISO(slot.start, { zone: accountStore.activeTimezone }), - end: DateTime.fromISO(slot.end, { zone: accountStore.activeTimezone }), + start: DateTime.fromISO(slot.start, { zone: accountStore.activeTimezone, locale: "en-US" }), + end: DateTime.fromISO(slot.end, { zone: accountStore.activeTimezone, locale: "en-US" }), }); } } @@ -655,7 +655,7 @@ const eventSlots = computed(() => { gaps.push([index, { type: "gap", location: props.location, - start: DateTime.fromMillis(maxEnd), + start: DateTime.fromMillis(maxEnd, { locale: "en-US" }), end: second.start, }]); } diff --git a/components/ShiftScheduleTable.vue b/components/ShiftScheduleTable.vue index 085efc9..1c4b8a0 100644 --- a/components/ShiftScheduleTable.vue +++ b/components/ShiftScheduleTable.vue @@ -432,12 +432,12 @@ const newShiftStart = ref(""); const newShiftDuration = ref("01:00"); const newShiftEnd = computed({ get: () => ( - DateTime.fromISO(newShiftStart.value, { zone: accountStore.activeTimezone }) - .plus(Duration.fromISOTime(newShiftDuration.value)) + DateTime.fromISO(newShiftStart.value, { zone: accountStore.activeTimezone, locale: "en-US" }) + .plus(Duration.fromISOTime(newShiftDuration.value, { locale: "en-US" })) .toFormat("HH:mm") ), set: (value: string) => { - const start = DateTime.fromISO(newShiftStart.value, { zone: accountStore.activeTimezone }); + const start = DateTime.fromISO(newShiftStart.value, { zone: accountStore.activeTimezone, locale: "en-US" }); const end = endFromTime(start, value); newShiftDuration.value = dropDay(end.diff(start)).toFormat("hh:mm"); }, @@ -448,16 +448,16 @@ watch(() => props.role, () => { }); function endFromTime(start: DateTime, time: string) { - let end = start.startOf("day").plus(Duration.fromISOTime(time)); + let end = start.startOf("day").plus(Duration.fromISOTime(time, { locale: "en-US" })); if (end.toMillis() <= start.toMillis()) { end = end.plus({ days: 1 }); } return end; } function durationFromTime(time: string) { - let duration = Duration.fromISOTime(time); + let duration = Duration.fromISOTime(time, { locale: "en-US" }); if (duration.toMillis() === 0) { - duration = Duration.fromMillis(oneDayMs); + duration = Duration.fromMillis(oneDayMs, { locale: "en-US" }); } return duration; } @@ -474,7 +474,7 @@ function editShiftSlot( } ) { if (edits.start) { - const start = DateTime.fromISO(edits.start, { zone: accountStore.activeTimezone }); + const start = DateTime.fromISO(edits.start, { zone: accountStore.activeTimezone, locale: "en-US" }); shiftSlot = { ...shiftSlot, start, @@ -554,7 +554,7 @@ function newShiftSlot(options: { start?: DateTime, end?: DateTime } = {}) { end = options.end; start = options.end.minus(duration); } else { - start = DateTime.fromISO(newShiftStart.value, { zone: accountStore.activeTimezone }); + start = DateTime.fromISO(newShiftStart.value, { zone: accountStore.activeTimezone, locale: "en-US" }); end = endFromTime(start, newShiftEnd.value); } if (!start.isValid || !end.isValid) { @@ -617,8 +617,8 @@ const shiftSlots = computed(() => { role: shift.role, assigned: slot.assigned ?? [], origRole: shift.role, - start: DateTime.fromISO(slot.start, { zone: accountStore.activeTimezone }), - end: DateTime.fromISO(slot.end, { zone: accountStore.activeTimezone }), + start: DateTime.fromISO(slot.start, { zone: accountStore.activeTimezone, locale: "en-US" }), + end: DateTime.fromISO(slot.end, { zone: accountStore.activeTimezone, locale: "en-US" }), }); } } @@ -634,7 +634,7 @@ const shiftSlots = computed(() => { gaps.push([index, { type: "gap", role: props.role, - start: DateTime.fromMillis(maxEnd), + start: DateTime.fromMillis(maxEnd, { locale: "en-US" }), end: second.start, }]); } diff --git a/components/Timetable.vue b/components/Timetable.vue index 193b509..2b62efb 100644 --- a/components/Timetable.vue +++ b/components/Timetable.vue @@ -251,7 +251,7 @@ function* stretchesFromSpans(spans: Iterable, minSeparation: number): Gene /** Cuts up a span by whole hours that crosses it */ function* cutSpansByHours(span: Span, timezone: string): Generator { - const startHour = DateTime.fromMillis(span.start.ts, { zone: timezone }) + const startHour = DateTime.fromMillis(span.start.ts, { zone: timezone, locale: "en-US" }) .startOf("hour") ; const end = span.end.ts; @@ -294,11 +294,11 @@ function* cutSpansByHours(span: Span, timezone: string): Generator { function padStretch(stretch: Stretch, timezone: string): Stretch { // Pad by one hour and extend it to the nearest whole hour. - let start = DateTime.fromMillis(stretch.start, { zone: timezone }) + let start = DateTime.fromMillis(stretch.start, { zone: timezone, locale: "en-US" }) .minus(oneHourMs) .startOf("hour") ; - let end = DateTime.fromMillis(stretch.end, { zone: timezone }) + let end = DateTime.fromMillis(stretch.end, { zone: timezone, locale: "en-US" }) .plus(2 * oneHourMs - 1) .startOf("hour") ; @@ -389,7 +389,7 @@ function tableElementsFromStretches( let first = true; for (let stretch of stretches) { stretch = padStretch(stretch, timezone); - const startDate = DateTime.fromMillis(stretch.start, { zone: timezone }); + const startDate = DateTime.fromMillis(stretch.start, { zone: timezone, locale: "en-US" }); if (first) { first = false; startColumnGroup(); @@ -452,16 +452,16 @@ function tableElementsFromStretches( } pushColumn(durationMs / oneMinMs); - const endDate = DateTime.fromMillis(end, { zone: timezone }); + const endDate = DateTime.fromMillis(end, { zone: timezone, locale: "en-US" }); if (end === endDate.startOf("day").toMillis()) { startDay( - DateTime.fromMillis(cutSpan.end.ts, { zone: timezone }) + DateTime.fromMillis(cutSpan.end.ts, { zone: timezone, locale: "en-US" }) .toFormat("yyyy-LL-dd") ); } if (end === endDate.startOf("hour").toMillis()) { startHour( - DateTime.fromMillis(cutSpan.end.ts, { zone: timezone }) + DateTime.fromMillis(cutSpan.end.ts, { zone: timezone, locale: "en-US" }) .toFormat("HH:mm") ); } diff --git a/server/api/account.patch.ts b/server/api/account.patch.ts index afccc5d..d3149ca 100644 --- a/server/api/account.patch.ts +++ b/server/api/account.patch.ts @@ -26,7 +26,7 @@ export default defineEventHandler(async (event) => { }); } if (body.timezone.length) { - const zonedTime = DateTime.local().setZone(body.timezone); + const zonedTime = DateTime.local({ locale: "en-US" }).setZone(body.timezone); if (!zonedTime.isValid) { throw createError({ status: 400,