Refactor: centralize timezone utilities in src/utils/timezone.ts (#491)

Deduplicate resolveTimezone, resolveTimezoneId, convertEventDateTimeToISO,
and getTimezoneOffset into a single canonical module, removing copies from
EventApi, eventUtils, TimezoneSelector, EventUpdateModal, and calendarUtils.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Yadd
2026-02-02 11:20:12 +01:00
committed by GitHub
parent 5ae838c425
commit 6014681f29
10 changed files with 87 additions and 188 deletions
+1 -39
View File
@@ -1,9 +1,8 @@
import { detectDateTimeFormat } from "@/components/Event/utils/dateTimeHelpers";
import { api } from "@/utils/apiUtils";
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
import { convertEventDateTimeToISO, resolveTimezoneId } from "@/utils/timezone";
import { TIMEZONES } from "@/utils/timezone-data";
import ICAL from "ical.js";
import moment from "moment-timezone";
import { CalDavItem } from "../Calendars/api/types";
import { CalendarEvent } from "./EventsTypes";
import {
@@ -13,17 +12,6 @@ import {
parseCalendarEvent,
} from "./eventUtils";
function resolveTimezoneId(tzid?: string): string | undefined {
if (!tzid) return undefined;
if (TIMEZONES.zones[tzid]) {
return tzid;
}
if (TIMEZONES.aliases[tzid]) {
return TIMEZONES.aliases[tzid].aliasTo;
}
return tzid;
}
export async function reportEvent(
event: CalendarEvent,
match: { start: string; end: string }
@@ -148,32 +136,6 @@ export async function getEvent(event: CalendarEvent, isMaster?: boolean) {
return merged;
}
function convertEventDateTimeToISO(
datetime: string,
eventTimezone: string
): string | undefined {
if (!datetime || !eventTimezone) return undefined;
if (datetime.includes("Z") || datetime.match(/[+-]\d{2}:\d{2}$/)) {
return datetime;
}
const dateOnlyRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
if (dateOnlyRegex.test(datetime)) {
return undefined;
}
const format = detectDateTimeFormat(datetime);
const momentDate = moment.tz(datetime, format, eventTimezone);
if (!momentDate.isValid()) {
console.warn(
`[convertEventDateTimeToISO] Invalid datetime: "${datetime}" with format "${format}" in timezone "${eventTimezone}"`
);
return undefined;
}
return momentDate.toISOString();
}
export async function dlEvent(event: CalendarEvent) {
const response = await api.get(`dav${event.URL}?export=`);
const eventData = await response.text();
+1 -1
View File
@@ -1,6 +1,6 @@
import { useAppDispatch, useAppSelector } from "@/app/hooks";
import { CalendarName } from "@/components/Calendar/CalendarName";
import { getTimezoneOffset } from "@/components/Calendar/TimezoneSelector";
import { getTimezoneOffset } from "@/utils/timezone";
import { formatEventChipTitle } from "@/components/Calendar/utils/calendarUtils";
import ResponsiveDialog from "@/components/Dialog/ResponsiveDialog";
import { EditModeDialog } from "@/components/Event/EditModeDialog";
+1 -4
View File
@@ -1,8 +1,5 @@
import { useAppDispatch, useAppSelector } from "@/app/hooks";
import {
getTimezoneOffset,
resolveTimezone,
} from "@/components/Calendar/TimezoneSelector";
import { getTimezoneOffset, resolveTimezone } from "@/utils/timezone";
import { updateTempCalendar } from "@/components/Calendar/utils/calendarUtils";
import { ResponsiveDialog } from "@/components/Dialog";
import EventFormFields from "@/components/Event/EventFormFields";
+5 -30
View File
@@ -25,7 +25,11 @@ import {
showErrorNotification,
} from "@/utils/eventFormTempStorage";
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
import { browserDefaultTimeZone } from "@/utils/timezone";
import {
browserDefaultTimeZone,
resolveTimezone,
getTimezoneOffset,
} from "@/utils/timezone";
import { TIMEZONES } from "@/utils/timezone-data";
import { addVideoConferenceToDescription } from "@/utils/videoConferenceUtils";
import { Box, Button } from "@linagora/twake-mui";
@@ -108,39 +112,10 @@ function EventUpdateModal({
);
}, [calendarsList, user.userData?.openpaasId]);
// Helper function to resolve timezone aliases
const resolveTimezone = (tzName: string): string => {
if (TIMEZONES.zones[tzName]) {
return tzName;
}
if (TIMEZONES.aliases[tzName]) {
return TIMEZONES.aliases[tzName].aliasTo;
}
return tzName;
};
const timezoneList = useMemo(() => {
const zones = Object.keys(TIMEZONES.zones).sort();
const browserTz = resolveTimezone(browserDefaultTimeZone);
const getTimezoneOffset = (tzName: string): string => {
const resolvedTz = resolveTimezone(tzName);
const tzData = TIMEZONES.zones[resolvedTz];
if (!tzData) return "";
const icsMatch = tzData.ics.match(/TZOFFSETTO:([+-]\d{4})/);
if (!icsMatch) return "";
const offset = icsMatch[1];
const hours = parseInt(offset.slice(0, 3));
const minutes = parseInt(offset.slice(3));
if (minutes === 0) {
return `UTC${hours >= 0 ? "+" : ""}${hours}`;
}
return `UTC${hours >= 0 ? "+" : ""}${hours}:${Math.abs(minutes).toString().padStart(2, "0")}`;
};
return { zones, browserTz, getTimezoneOffset };
}, []);
+4 -44
View File
@@ -1,8 +1,6 @@
import {
convertFormDateTimeToISO,
detectDateTimeFormat,
} from "@/components/Event/utils/dateTimeHelpers";
import { convertFormDateTimeToISO } from "@/components/Event/utils/dateTimeHelpers";
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
import { resolveTimezoneId, convertEventDateTimeToISO } from "@/utils/timezone";
import { TIMEZONES } from "@/utils/timezone-data";
import ICAL from "ical.js";
import moment from "moment-timezone";
@@ -11,18 +9,6 @@ import { createAttendee } from "../User/models/attendee.mapper";
import { AlarmObject, CalendarEvent, RepetitionObject } from "./EventsTypes";
type RawEntry = [string, Record<string, string>, string, any];
function resolveTimezoneId(tzid?: string): string | undefined {
if (!tzid) return undefined;
if (TIMEZONES.zones[tzid]) {
return tzid;
}
const alias = TIMEZONES.aliases[tzid];
if (alias) {
return alias.aliasTo;
}
return tzid;
}
function inferTimezoneFromValue(
params: Record<string, string> | undefined,
value: string
@@ -213,14 +199,14 @@ export function parseCalendarEvent(
}
if (!event.allday && event.start && eventTimezone) {
const startISO = convertDateTimeStringToISO(event.start, eventTimezone);
const startISO = convertEventDateTimeToISO(event.start, eventTimezone);
if (startISO) {
event.start = startISO;
}
}
if (!event.allday && event.end && eventTimezone) {
const endISO = convertDateTimeStringToISO(event.end, eventTimezone);
const endISO = convertEventDateTimeToISO(event.end, eventTimezone);
if (endISO) {
event.end = endISO;
}
@@ -229,32 +215,6 @@ export function parseCalendarEvent(
return event as CalendarEvent;
}
function convertDateTimeStringToISO(
datetime: string,
timezone: string
): string | undefined {
if (!datetime || !timezone) return undefined;
if (datetime.includes("Z") || datetime.match(/[+-]\d{2}:\d{2}$/)) {
return undefined;
}
const dateOnlyRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
if (dateOnlyRegex.test(datetime)) {
return undefined;
}
const format = detectDateTimeFormat(datetime);
const momentDate = moment.tz(datetime, format, timezone);
if (!momentDate.isValid()) {
console.warn(
`[convertDateTimeStringToISO] Invalid datetime: "${datetime}" with format "${format}" in timezone "${timezone}"`
);
return undefined;
}
return momentDate.toISOString();
}
export function calendarEventToJCal(
event: CalendarEvent,
calOwnerEmail?: string
+1 -1
View File
@@ -1,4 +1,4 @@
import { resolveTimezone } from "@/components/Calendar/TimezoneSelector";
import { resolveTimezone } from "@/utils/timezone";
import { formatDateTimeInTimezone } from "@/components/Event/utils/dateTimeFormatters";
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
import { browserDefaultTimeZone } from "@/utils/timezone";
+2 -4
View File
@@ -1,8 +1,6 @@
import { useAppDispatch, useAppSelector } from "@/app/hooks";
import {
getTimezoneOffset,
useTimeZoneList,
} from "@/components/Calendar/TimezoneSelector";
import { useTimeZoneList } from "@/components/Calendar/TimezoneSelector";
import { getTimezoneOffset } from "@/utils/timezone";
import { TimezoneAutocomplete } from "@/components/Timezone/TimezoneAutocomplete";
import { browserDefaultTimeZone } from "@/utils/timezone";
import {