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:
@@ -1,7 +1,10 @@
|
|||||||
import { browserDefaultTimeZone } from "@/utils/timezone";
|
import {
|
||||||
|
browserDefaultTimeZone,
|
||||||
|
getTimezoneOffset,
|
||||||
|
resolveTimezone,
|
||||||
|
} from "@/utils/timezone";
|
||||||
import { TIMEZONES } from "@/utils/timezone-data";
|
import { TIMEZONES } from "@/utils/timezone-data";
|
||||||
import { Button, Popover } from "@linagora/twake-mui";
|
import { Button, Popover } from "@linagora/twake-mui";
|
||||||
import moment from "moment";
|
|
||||||
import { MouseEvent, useMemo, useState } from "react";
|
import { MouseEvent, useMemo, useState } from "react";
|
||||||
import { useI18n } from "twake-i18n";
|
import { useI18n } from "twake-i18n";
|
||||||
import { TimezoneAutocomplete } from "../Timezone/TimezoneAutocomplete";
|
import { TimezoneAutocomplete } from "../Timezone/TimezoneAutocomplete";
|
||||||
@@ -99,28 +102,3 @@ export function useTimeZoneList() {
|
|||||||
return { zones, browserTz, getTimezoneOffset };
|
return { zones, browserTz, getTimezoneOffset };
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveTimezone(tzName: string): string {
|
|
||||||
if (TIMEZONES.zones[tzName]) {
|
|
||||||
return tzName;
|
|
||||||
}
|
|
||||||
if (TIMEZONES.aliases[tzName]) {
|
|
||||||
return TIMEZONES.aliases[tzName].aliasTo;
|
|
||||||
}
|
|
||||||
return tzName;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTimezoneOffset(
|
|
||||||
tzName: string,
|
|
||||||
date: Date = new Date()
|
|
||||||
): string {
|
|
||||||
const fmt = new Intl.DateTimeFormat(undefined, {
|
|
||||||
timeZone: tzName,
|
|
||||||
timeZoneName: "shortOffset",
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentDate = moment(date).isValid() ? date : new Date();
|
|
||||||
const parts = fmt.formatToParts(currentDate);
|
|
||||||
const offsetPart = parts.find((p) => p.type === "timeZoneName");
|
|
||||||
return offsetPart?.value.replace("GMT", "UTC") ?? "";
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,42 +1,15 @@
|
|||||||
import { detectDateTimeFormat } from "@/components/Event/utils/dateTimeHelpers";
|
|
||||||
import { refreshSingularCalendar } from "@/components/Event/utils/eventUtils";
|
import { refreshSingularCalendar } from "@/components/Event/utils/eventUtils";
|
||||||
import { Calendar } from "@/features/Calendars/CalendarTypes";
|
import { Calendar } from "@/features/Calendars/CalendarTypes";
|
||||||
import { getCalendarDetailAsync } from "@/features/Calendars/services";
|
import { getCalendarDetailAsync } from "@/features/Calendars/services";
|
||||||
import { CalendarEvent } from "@/features/Events/EventsTypes";
|
import { CalendarEvent } from "@/features/Events/EventsTypes";
|
||||||
import { formatDateToYYYYMMDDTHHMMSS } from "@/utils/dateUtils";
|
import { formatDateToYYYYMMDDTHHMMSS } from "@/utils/dateUtils";
|
||||||
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
||||||
|
import { convertEventDateTimeToISO } from "@/utils/timezone";
|
||||||
import { SlotLabelContentArg } from "@fullcalendar/core";
|
import { SlotLabelContentArg } from "@fullcalendar/core";
|
||||||
import { ThunkDispatch } from "@reduxjs/toolkit";
|
import { ThunkDispatch } from "@reduxjs/toolkit";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { useI18n } from "twake-i18n";
|
import { useI18n } from "twake-i18n";
|
||||||
|
|
||||||
function convertEventDateTimeToISO(
|
|
||||||
datetime: string,
|
|
||||||
eventTimezone: string,
|
|
||||||
isAllDay: boolean
|
|
||||||
): string {
|
|
||||||
if (!datetime || isAllDay) return datetime;
|
|
||||||
|
|
||||||
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 datetime;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 datetime;
|
|
||||||
}
|
|
||||||
return momentDate.toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateSlotLabelVisibility = (
|
export const updateSlotLabelVisibility = (
|
||||||
currentTime: Date,
|
currentTime: Date,
|
||||||
slotLabel: SlotLabelContentArg,
|
slotLabel: SlotLabelContentArg,
|
||||||
@@ -114,22 +87,18 @@ export const eventToFullCalendarFormat = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!isAllDay && e.start && eventTimezone) {
|
if (!isAllDay && e.start && eventTimezone) {
|
||||||
const startISO = convertEventDateTimeToISO(
|
const startISO = convertEventDateTimeToISO(e.start, eventTimezone, {
|
||||||
e.start,
|
isAllDay,
|
||||||
eventTimezone,
|
});
|
||||||
isAllDay
|
|
||||||
);
|
|
||||||
if (startISO) {
|
if (startISO) {
|
||||||
convertedEvent.start = startISO;
|
convertedEvent.start = startISO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAllDay && e.end && eventTimezone) {
|
if (!isAllDay && e.end && eventTimezone) {
|
||||||
const endISO = convertEventDateTimeToISO(
|
const endISO = convertEventDateTimeToISO(e.end, eventTimezone, {
|
||||||
e.end,
|
isAllDay,
|
||||||
eventTimezone,
|
});
|
||||||
isAllDay
|
|
||||||
);
|
|
||||||
if (endISO) {
|
if (endISO) {
|
||||||
convertedEvent.end = endISO;
|
convertedEvent.end = endISO;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { detectDateTimeFormat } from "@/components/Event/utils/dateTimeHelpers";
|
|
||||||
import { api } from "@/utils/apiUtils";
|
import { api } from "@/utils/apiUtils";
|
||||||
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
||||||
|
import { convertEventDateTimeToISO, resolveTimezoneId } from "@/utils/timezone";
|
||||||
import { TIMEZONES } from "@/utils/timezone-data";
|
import { TIMEZONES } from "@/utils/timezone-data";
|
||||||
import ICAL from "ical.js";
|
import ICAL from "ical.js";
|
||||||
import moment from "moment-timezone";
|
|
||||||
import { CalDavItem } from "../Calendars/api/types";
|
import { CalDavItem } from "../Calendars/api/types";
|
||||||
import { CalendarEvent } from "./EventsTypes";
|
import { CalendarEvent } from "./EventsTypes";
|
||||||
import {
|
import {
|
||||||
@@ -13,17 +12,6 @@ import {
|
|||||||
parseCalendarEvent,
|
parseCalendarEvent,
|
||||||
} from "./eventUtils";
|
} 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(
|
export async function reportEvent(
|
||||||
event: CalendarEvent,
|
event: CalendarEvent,
|
||||||
match: { start: string; end: string }
|
match: { start: string; end: string }
|
||||||
@@ -148,32 +136,6 @@ export async function getEvent(event: CalendarEvent, isMaster?: boolean) {
|
|||||||
return merged;
|
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) {
|
export async function dlEvent(event: CalendarEvent) {
|
||||||
const response = await api.get(`dav${event.URL}?export=`);
|
const response = await api.get(`dav${event.URL}?export=`);
|
||||||
const eventData = await response.text();
|
const eventData = await response.text();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
||||||
import { CalendarName } from "@/components/Calendar/CalendarName";
|
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 { formatEventChipTitle } from "@/components/Calendar/utils/calendarUtils";
|
||||||
import ResponsiveDialog from "@/components/Dialog/ResponsiveDialog";
|
import ResponsiveDialog from "@/components/Dialog/ResponsiveDialog";
|
||||||
import { EditModeDialog } from "@/components/Event/EditModeDialog";
|
import { EditModeDialog } from "@/components/Event/EditModeDialog";
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
||||||
import {
|
import { getTimezoneOffset, resolveTimezone } from "@/utils/timezone";
|
||||||
getTimezoneOffset,
|
|
||||||
resolveTimezone,
|
|
||||||
} from "@/components/Calendar/TimezoneSelector";
|
|
||||||
import { updateTempCalendar } from "@/components/Calendar/utils/calendarUtils";
|
import { updateTempCalendar } from "@/components/Calendar/utils/calendarUtils";
|
||||||
import { ResponsiveDialog } from "@/components/Dialog";
|
import { ResponsiveDialog } from "@/components/Dialog";
|
||||||
import EventFormFields from "@/components/Event/EventFormFields";
|
import EventFormFields from "@/components/Event/EventFormFields";
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ import {
|
|||||||
showErrorNotification,
|
showErrorNotification,
|
||||||
} from "@/utils/eventFormTempStorage";
|
} from "@/utils/eventFormTempStorage";
|
||||||
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
||||||
import { browserDefaultTimeZone } from "@/utils/timezone";
|
import {
|
||||||
|
browserDefaultTimeZone,
|
||||||
|
resolveTimezone,
|
||||||
|
getTimezoneOffset,
|
||||||
|
} from "@/utils/timezone";
|
||||||
import { TIMEZONES } from "@/utils/timezone-data";
|
import { TIMEZONES } from "@/utils/timezone-data";
|
||||||
import { addVideoConferenceToDescription } from "@/utils/videoConferenceUtils";
|
import { addVideoConferenceToDescription } from "@/utils/videoConferenceUtils";
|
||||||
import { Box, Button } from "@linagora/twake-mui";
|
import { Box, Button } from "@linagora/twake-mui";
|
||||||
@@ -108,39 +112,10 @@ function EventUpdateModal({
|
|||||||
);
|
);
|
||||||
}, [calendarsList, user.userData?.openpaasId]);
|
}, [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 timezoneList = useMemo(() => {
|
||||||
const zones = Object.keys(TIMEZONES.zones).sort();
|
const zones = Object.keys(TIMEZONES.zones).sort();
|
||||||
const browserTz = resolveTimezone(browserDefaultTimeZone);
|
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 };
|
return { zones, browserTz, getTimezoneOffset };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import {
|
import { convertFormDateTimeToISO } from "@/components/Event/utils/dateTimeHelpers";
|
||||||
convertFormDateTimeToISO,
|
|
||||||
detectDateTimeFormat,
|
|
||||||
} from "@/components/Event/utils/dateTimeHelpers";
|
|
||||||
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
||||||
|
import { resolveTimezoneId, convertEventDateTimeToISO } from "@/utils/timezone";
|
||||||
import { TIMEZONES } from "@/utils/timezone-data";
|
import { TIMEZONES } from "@/utils/timezone-data";
|
||||||
import ICAL from "ical.js";
|
import ICAL from "ical.js";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
@@ -11,18 +9,6 @@ import { createAttendee } from "../User/models/attendee.mapper";
|
|||||||
import { AlarmObject, CalendarEvent, RepetitionObject } from "./EventsTypes";
|
import { AlarmObject, CalendarEvent, RepetitionObject } from "./EventsTypes";
|
||||||
type RawEntry = [string, Record<string, string>, string, any];
|
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(
|
function inferTimezoneFromValue(
|
||||||
params: Record<string, string> | undefined,
|
params: Record<string, string> | undefined,
|
||||||
value: string
|
value: string
|
||||||
@@ -213,14 +199,14 @@ export function parseCalendarEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!event.allday && event.start && eventTimezone) {
|
if (!event.allday && event.start && eventTimezone) {
|
||||||
const startISO = convertDateTimeStringToISO(event.start, eventTimezone);
|
const startISO = convertEventDateTimeToISO(event.start, eventTimezone);
|
||||||
if (startISO) {
|
if (startISO) {
|
||||||
event.start = startISO;
|
event.start = startISO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!event.allday && event.end && eventTimezone) {
|
if (!event.allday && event.end && eventTimezone) {
|
||||||
const endISO = convertDateTimeStringToISO(event.end, eventTimezone);
|
const endISO = convertEventDateTimeToISO(event.end, eventTimezone);
|
||||||
if (endISO) {
|
if (endISO) {
|
||||||
event.end = endISO;
|
event.end = endISO;
|
||||||
}
|
}
|
||||||
@@ -229,32 +215,6 @@ export function parseCalendarEvent(
|
|||||||
return event as CalendarEvent;
|
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(
|
export function calendarEventToJCal(
|
||||||
event: CalendarEvent,
|
event: CalendarEvent,
|
||||||
calOwnerEmail?: string
|
calOwnerEmail?: string
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { resolveTimezone } from "@/components/Calendar/TimezoneSelector";
|
import { resolveTimezone } from "@/utils/timezone";
|
||||||
import { formatDateTimeInTimezone } from "@/components/Event/utils/dateTimeFormatters";
|
import { formatDateTimeInTimezone } from "@/components/Event/utils/dateTimeFormatters";
|
||||||
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
|
||||||
import { browserDefaultTimeZone } from "@/utils/timezone";
|
import { browserDefaultTimeZone } from "@/utils/timezone";
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
import { useAppDispatch, useAppSelector } from "@/app/hooks";
|
||||||
import {
|
import { useTimeZoneList } from "@/components/Calendar/TimezoneSelector";
|
||||||
getTimezoneOffset,
|
import { getTimezoneOffset } from "@/utils/timezone";
|
||||||
useTimeZoneList,
|
|
||||||
} from "@/components/Calendar/TimezoneSelector";
|
|
||||||
import { TimezoneAutocomplete } from "@/components/Timezone/TimezoneAutocomplete";
|
import { TimezoneAutocomplete } from "@/components/Timezone/TimezoneAutocomplete";
|
||||||
import { browserDefaultTimeZone } from "@/utils/timezone";
|
import { browserDefaultTimeZone } from "@/utils/timezone";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
// Ensure ICAL is imported before using this module.
|
// Ensure ICAL is imported before using this module.
|
||||||
import ICAL from "ical.js";
|
import ICAL from "ical.js";
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
import { detectDateTimeFormat } from "@/components/Event/utils/dateTimeHelpers";
|
||||||
|
|
||||||
// TIMEZONES data must be imported or defined separately.
|
// TIMEZONES data must be imported or defined separately.
|
||||||
import { TIMEZONES } from "./timezone-data";
|
import { TIMEZONES } from "./timezone-data";
|
||||||
@@ -39,3 +41,61 @@ function findTimezone(tzid: string): any {
|
|||||||
|
|
||||||
throw new Error(`Unknown timezone alias: ${tzid}`);
|
throw new Error(`Unknown timezone alias: ${tzid}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveTimezone(tzName: string): string {
|
||||||
|
if (TIMEZONES.zones[tzName]) {
|
||||||
|
return tzName;
|
||||||
|
}
|
||||||
|
if (TIMEZONES.aliases[tzName]) {
|
||||||
|
return TIMEZONES.aliases[tzName].aliasTo;
|
||||||
|
}
|
||||||
|
return tzName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveTimezoneId(tzid?: string): string | undefined {
|
||||||
|
if (!tzid) return undefined;
|
||||||
|
return resolveTimezone(tzid);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertEventDateTimeToISO(
|
||||||
|
datetime: string,
|
||||||
|
timezone: string,
|
||||||
|
options?: { isAllDay?: boolean }
|
||||||
|
): string | undefined {
|
||||||
|
if (!datetime || !timezone) return undefined;
|
||||||
|
if (options?.isAllDay) 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, timezone);
|
||||||
|
if (!momentDate.isValid()) {
|
||||||
|
console.warn(
|
||||||
|
`[convertEventDateTimeToISO] Invalid datetime: "${datetime}" with format "${format}" in timezone "${timezone}"`
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return momentDate.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTimezoneOffset(
|
||||||
|
tzName: string,
|
||||||
|
date: Date = new Date()
|
||||||
|
): string {
|
||||||
|
const fmt = new Intl.DateTimeFormat(undefined, {
|
||||||
|
timeZone: tzName,
|
||||||
|
timeZoneName: "shortOffset",
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentDate = moment(date).isValid() ? date : new Date();
|
||||||
|
const parts = fmt.formatToParts(currentDate);
|
||||||
|
const offsetPart = parts.find((p) => p.type === "timeZoneName");
|
||||||
|
return offsetPart?.value.replace("GMT", "UTC") ?? "";
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user