* fix: handle invalid timezones gracefully in getLuckyUser Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * refactor: move timezone validation to getCalendarsEvents with offset conversion - Move invalid timezone handling from getLuckyUser.ts to getCalendarsEvents.ts - Add convertOffsetToEtcGmt() to convert offset formats (GMT-05:00, UTC+08:00) to valid IANA Etc/GMT timezones - Preserves timezone precision for fairness calculations instead of falling back to UTC - Add unit tests for timezone conversion scenarios * refactor: extract timezone conversion to dedicated file - Create timezone-conversion.ts with concise convertOffsetToIanaTimezone and normalizeTimezone functions - Update getCalendarsEvents.ts to import from the new file - Follow kebab-case naming convention for utility files * test: add unit tests for timezone conversion proving Intl.DateTimeFormat behavior - Add tests proving Intl.DateTimeFormat throws RangeError for offset formats (GMT-05:00, UTC+08:00) - Add tests for convertOffsetToIanaTimezone function - Add tests for normalizeTimezone function --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
import logger from "@calcom/lib/logger";
|
|
|
|
const log = logger.getSubLogger({ prefix: ["timezoneConversion"] });
|
|
|
|
/**
|
|
* Converts offset-based timezone formats (GMT±HH:MM, UTC±HH:MM) to IANA Etc/GMT timezones.
|
|
* Etc/GMT uses inverted signs: Etc/GMT+5 = UTC-5, Etc/GMT-8 = UTC+8.
|
|
*/
|
|
export function convertOffsetToIanaTimezone(timezone: string): string | null {
|
|
const match = timezone.match(/^(?:GMT|UTC)([+-])(\d{1,2})(?::(\d{2}))?$/i);
|
|
if (!match) return null;
|
|
|
|
const [, sign, hoursStr, minutesStr] = match;
|
|
const hours = parseInt(hoursStr, 10);
|
|
const minutes = minutesStr ? parseInt(minutesStr, 10) : 0;
|
|
|
|
// Etc/GMT only supports whole hours up to 14
|
|
if (minutes !== 0 || hours > 14) return null;
|
|
|
|
if (hours === 0) return "Etc/GMT";
|
|
return `Etc/GMT${sign === "+" ? "-" : "+"}${hours}`;
|
|
}
|
|
|
|
/**
|
|
* Validates and normalizes a timezone string to a valid IANA timezone.
|
|
* Converts offset formats to Etc/GMT, falls back to UTC if invalid.
|
|
*/
|
|
export function normalizeTimezone(timezone: string | undefined): string {
|
|
if (!timezone) return "UTC";
|
|
|
|
try {
|
|
Intl.DateTimeFormat(undefined, { timeZone: timezone });
|
|
return timezone;
|
|
} catch {
|
|
const converted = convertOffsetToIanaTimezone(timezone);
|
|
if (converted) {
|
|
log.info(`Converted offset timezone to IANA format`, { original: timezone, converted });
|
|
return converted;
|
|
}
|
|
log.warn(`Invalid timezone format, falling back to UTC`, { invalidTimezone: timezone });
|
|
return "UTC";
|
|
}
|
|
}
|