/** * Часовые пояса для настроек профиля и раздела «Язык и регион». * * Значение — идентификатор IANA, как его принимает сервер; подпись собирается * из названия зоны и текущего смещения (смещение считает `Intl`, поэтому * переход на летнее время учитывается автоматически). */ /** Пояс по умолчанию: Москва (МСК). */ export const DEFAULT_TIMEZONE = 'Europe/Moscow'; export const timezoneOptions = [ 'Europe/Kaliningrad', 'Europe/Moscow', 'Europe/Samara', 'Asia/Yekaterinburg', 'Asia/Almaty', 'Asia/Tashkent', 'Asia/Novosibirsk', 'Asia/Krasnoyarsk', 'Asia/Irkutsk', 'Asia/Yakutsk', 'Asia/Vladivostok', 'Asia/Magadan', 'Asia/Kamchatka', 'Europe/London', 'Europe/Berlin', 'Europe/Paris', 'Asia/Tbilisi', 'Asia/Yerevan', 'Asia/Dubai', 'Asia/Tokyo', 'Asia/Shanghai', 'America/New_York', 'America/Los_Angeles', 'UTC', ] as const; /** Смещение вида `UTC+03:00` (для зоны, которой нет в ICU, — пустая строка). */ export function timezoneOffset(timeZone: string, locale = 'ru'): string { try { const parts = new Intl.DateTimeFormat(locale, { timeZone, timeZoneName: 'shortOffset', }).formatToParts(new Date()); return parts.find((part) => part.type === 'timeZoneName')?.value ?? ''; } catch { return ''; } } /** Подпись пункта списка: `Europe/Moscow (UTC+03:00)`. */ export function timezoneLabel(timeZone: string, locale = 'ru'): string { const offset = timezoneOffset(timeZone, locale); return offset === '' ? timeZone : `${timeZone} (${offset})`; } /** * Все зоны списка плюс значение из профиля, если его там ещё нет: селект не * должен «терять» часовой пояс, выставленный с другого клиента. */ export function timezoneChoices(value: string): string[] { const options: string[] = [...timezoneOptions]; if (value !== '' && !options.includes(value)) { options.unshift(value); } return options; }