Compare commits

...

1 Commits

Author SHA1 Message Date
Weiko f19e608a20 Fix deprecated IANA timezone names causing PostgreSQL errors on Debian Trixie
Debian Trixie moved deprecated IANA timezone aliases (e.g. Asia/Calcutta,
US/Eastern, Europe/Kiev) from the base tzdata package to tzdata-legacy.
Self-hosted PostgreSQL instances on Trixie reject these names in
AT TIME ZONE clauses.

JavaScript engines (Node.js, browsers) use CLDR/ICU data which still
considers deprecated names canonical (e.g. Intl returns Asia/Calcutta
instead of Asia/Kolkata), so the frontend sends deprecated names to
the backend.

Add a normalizeTimezone utility with a static map from the IANA backward
file to convert deprecated names to their canonical equivalents, and
apply it in useUserTimezone to normalize all timezone values before they
reach the backend.

Made-with: Cursor
2026-03-31 11:08:22 +02:00
5 changed files with 170 additions and 3 deletions
@@ -1,14 +1,19 @@
import { normalizeTimezone } from 'twenty-shared/utils';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
export const useUserTimezone = () => {
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const systemTimeZone = normalizeTimezone(
Intl.DateTimeFormat().resolvedOptions().timeZone,
);
const userTimezone =
const userTimezone = normalizeTimezone(
currentWorkspaceMember?.timeZone !== 'system'
? (currentWorkspaceMember?.timeZone ?? systemTimeZone)
: systemTimeZone;
: systemTimeZone,
);
const isSystemTimezone = userTimezone === systemTimeZone;
@@ -93,6 +93,7 @@ describe('getGroupByExpression', () => {
getGroupByExpression({ groupByField, columnNameWithQuotes }),
).toThrow();
});
});
describe('missing timezone handling', () => {
@@ -0,0 +1,23 @@
import { normalizeTimezone } from '../normalizeTimezone';
describe('normalizeTimezone', () => {
it('should normalize deprecated timezone names to canonical equivalents', () => {
expect(normalizeTimezone('Asia/Calcutta')).toBe('Asia/Kolkata');
expect(normalizeTimezone('US/Eastern')).toBe('America/New_York');
expect(normalizeTimezone('Asia/Saigon')).toBe('Asia/Ho_Chi_Minh');
expect(normalizeTimezone('Europe/Kiev')).toBe('Europe/Kyiv');
expect(normalizeTimezone('Canada/Eastern')).toBe('America/Toronto');
});
it('should return canonical timezone names unchanged', () => {
expect(normalizeTimezone('Asia/Kolkata')).toBe('Asia/Kolkata');
expect(normalizeTimezone('America/New_York')).toBe('America/New_York');
expect(normalizeTimezone('Europe/Paris')).toBe('Europe/Paris');
expect(normalizeTimezone('UTC')).toBe('UTC');
});
it('should return unknown timezone names unchanged', () => {
expect(normalizeTimezone('Not/A/Timezone')).toBe('Not/A/Timezone');
expect(normalizeTimezone('invalid')).toBe('invalid');
});
});
@@ -147,6 +147,7 @@ export { isAutoSelectModelId } from './isAutoSelectModelId';
export { fastDeepEqual } from './json/fast-deep-equal';
export { getAppPath } from './navigation/getAppPath';
export { getSettingsPath } from './navigation/getSettingsPath';
export { normalizeTimezone } from './normalizeTimezone';
export { parseJson } from './parseJson';
export { removePropertiesFromRecord } from './removePropertiesFromRecord';
export { removeUndefinedFields } from './removeUndefinedFields';
@@ -0,0 +1,137 @@
// Map of deprecated IANA timezone names to their canonical equivalents.
// Source: https://data.iana.org/time-zones/tzdb/backward
// Needed because Debian Trixie's base tzdata package no longer includes
// these deprecated names, causing PostgreSQL AT TIME ZONE to fail.
const DEPRECATED_TIMEZONE_MAP: Record<string, string> = {
'Africa/Asmera': 'Africa/Nairobi',
'Africa/Timbuktu': 'Africa/Abidjan',
'America/Argentina/ComodRivadavia': 'America/Argentina/Catamarca',
'America/Atka': 'America/Adak',
'America/Buenos_Aires': 'America/Argentina/Buenos_Aires',
'America/Catamarca': 'America/Argentina/Catamarca',
'America/Coral_Harbour': 'America/Panama',
'America/Cordoba': 'America/Argentina/Cordoba',
'America/Ensenada': 'America/Tijuana',
'America/Fort_Wayne': 'America/Indiana/Indianapolis',
'America/Godthab': 'America/Nuuk',
'America/Indianapolis': 'America/Indiana/Indianapolis',
'America/Jujuy': 'America/Argentina/Jujuy',
'America/Knox_IN': 'America/Indiana/Knox',
'America/Louisville': 'America/Kentucky/Louisville',
'America/Mendoza': 'America/Argentina/Mendoza',
'America/Montreal': 'America/Toronto',
'America/Nipigon': 'America/Toronto',
'America/Pangnirtung': 'America/Iqaluit',
'America/Porto_Acre': 'America/Rio_Branco',
'America/Rainy_River': 'America/Winnipeg',
'America/Rosario': 'America/Argentina/Cordoba',
'America/Santa_Isabel': 'America/Tijuana',
'America/Shiprock': 'America/Denver',
'America/Thunder_Bay': 'America/Toronto',
'America/Virgin': 'America/Puerto_Rico',
'America/Yellowknife': 'America/Edmonton',
'Antarctica/South_Pole': 'Pacific/Auckland',
'Asia/Ashkhabad': 'Asia/Ashgabat',
'Asia/Calcutta': 'Asia/Kolkata',
'Asia/Choibalsan': 'Asia/Ulaanbaatar',
'Asia/Chongqing': 'Asia/Shanghai',
'Asia/Chungking': 'Asia/Shanghai',
'Asia/Dacca': 'Asia/Dhaka',
'Asia/Harbin': 'Asia/Shanghai',
'Asia/Istanbul': 'Europe/Istanbul',
'Asia/Kashgar': 'Asia/Urumqi',
'Asia/Katmandu': 'Asia/Kathmandu',
'Asia/Macao': 'Asia/Macau',
'Asia/Rangoon': 'Asia/Yangon',
'Asia/Saigon': 'Asia/Ho_Chi_Minh',
'Asia/Tel_Aviv': 'Asia/Jerusalem',
'Asia/Thimbu': 'Asia/Thimphu',
'Asia/Ujung_Pandang': 'Asia/Makassar',
'Asia/Ulan_Bator': 'Asia/Ulaanbaatar',
'Atlantic/Faeroe': 'Atlantic/Faroe',
'Atlantic/Jan_Mayen': 'Europe/Berlin',
'Australia/ACT': 'Australia/Sydney',
'Australia/Canberra': 'Australia/Sydney',
'Australia/Currie': 'Australia/Hobart',
'Australia/LHI': 'Australia/Lord_Howe',
'Australia/NSW': 'Australia/Sydney',
'Australia/North': 'Australia/Darwin',
'Australia/Queensland': 'Australia/Brisbane',
'Australia/South': 'Australia/Adelaide',
'Australia/Tasmania': 'Australia/Hobart',
'Australia/Victoria': 'Australia/Melbourne',
'Australia/West': 'Australia/Perth',
'Australia/Yancowinna': 'Australia/Broken_Hill',
'Brazil/Acre': 'America/Rio_Branco',
'Brazil/DeNoronha': 'America/Noronha',
'Brazil/East': 'America/Sao_Paulo',
'Brazil/West': 'America/Manaus',
'Canada/Atlantic': 'America/Halifax',
'Canada/Central': 'America/Winnipeg',
'Canada/Eastern': 'America/Toronto',
'Canada/Mountain': 'America/Edmonton',
'Canada/Newfoundland': 'America/St_Johns',
'Canada/Pacific': 'America/Vancouver',
'Canada/Saskatchewan': 'America/Regina',
'Canada/Yukon': 'America/Whitehorse',
'Chile/Continental': 'America/Santiago',
'Chile/EasterIsland': 'Pacific/Easter',
Cuba: 'America/Havana',
Egypt: 'Africa/Cairo',
Eire: 'Europe/Dublin',
'Europe/Belfast': 'Europe/London',
'Europe/Kiev': 'Europe/Kyiv',
'Europe/Nicosia': 'Asia/Nicosia',
'Europe/Tiraspol': 'Europe/Chisinau',
'Europe/Uzhgorod': 'Europe/Kyiv',
'Europe/Zaporozhye': 'Europe/Kyiv',
GB: 'Europe/London',
'GB-Eire': 'Europe/London',
Hongkong: 'Asia/Hong_Kong',
Iceland: 'Africa/Abidjan',
Iran: 'Asia/Tehran',
Israel: 'Asia/Jerusalem',
Jamaica: 'America/Jamaica',
Japan: 'Asia/Tokyo',
Kwajalein: 'Pacific/Kwajalein',
Libya: 'Africa/Tripoli',
'Mexico/BajaNorte': 'America/Tijuana',
'Mexico/BajaSur': 'America/Mazatlan',
'Mexico/General': 'America/Mexico_City',
NZ: 'Pacific/Auckland',
'NZ-CHAT': 'Pacific/Chatham',
Navajo: 'America/Denver',
PRC: 'Asia/Shanghai',
'Pacific/Enderbury': 'Pacific/Kanton',
'Pacific/Johnston': 'Pacific/Honolulu',
'Pacific/Ponape': 'Pacific/Guadalcanal',
'Pacific/Samoa': 'Pacific/Pago_Pago',
'Pacific/Truk': 'Pacific/Port_Moresby',
'Pacific/Yap': 'Pacific/Port_Moresby',
Poland: 'Europe/Warsaw',
Portugal: 'Europe/Lisbon',
ROC: 'Asia/Taipei',
ROK: 'Asia/Seoul',
Singapore: 'Asia/Singapore',
Turkey: 'Europe/Istanbul',
UCT: 'Etc/UTC',
'US/Alaska': 'America/Anchorage',
'US/Aleutian': 'America/Adak',
'US/Arizona': 'America/Phoenix',
'US/Central': 'America/Chicago',
'US/East-Indiana': 'America/Indiana/Indianapolis',
'US/Eastern': 'America/New_York',
'US/Hawaii': 'Pacific/Honolulu',
'US/Indiana-Starke': 'America/Indiana/Knox',
'US/Michigan': 'America/Detroit',
'US/Mountain': 'America/Denver',
'US/Pacific': 'America/Los_Angeles',
'US/Samoa': 'Pacific/Pago_Pago',
Universal: 'Etc/UTC',
'W-SU': 'Europe/Moscow',
Zulu: 'Etc/UTC',
};
export const normalizeTimezone = (timezone: string): string => {
return DEPRECATED_TIMEZONE_MAP[timezone] ?? timezone;
};