diff --git a/src/TIMEZONES.ts b/src/TIMEZONES.ts index 0fb340a2d88d..83f6531f85bb 100644 --- a/src/TIMEZONES.ts +++ b/src/TIMEZONES.ts @@ -427,7 +427,7 @@ const TIMEZONES = [ * The timezones supported in browser and on native devices differ, so we must map each timezone to its supported equivalent. * Data sourced from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones */ -const timezoneBackwardMap: Record> = { +const timezoneBackwardToNewMap: Record> = { 'Africa/Asmera': 'Africa/Nairobi', 'Africa/Timbuktu': 'Africa/Abidjan', 'America/Argentina/ComodRivadavia': 'America/Argentina/Catamarca', @@ -564,6 +564,11 @@ const timezoneBackwardMap: Record> = { WET: 'Europe/Lisbon', }; -export {timezoneBackwardMap}; +const timezoneNewToBackwardMap = Object.fromEntries(Object.entries(timezoneBackwardToNewMap).map(([oldTimezone, newTimezone]) => [newTimezone, oldTimezone])) as Record< + TupleToUnion, + string +>; + +export {timezoneBackwardToNewMap, timezoneNewToBackwardMap}; export default TIMEZONES; diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index b31b36d812d6..573b32d886dc 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -40,13 +40,14 @@ import type {ValueOf} from 'type-fest'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {timezoneBackwardMap} from '@src/TIMEZONES'; +import {timezoneBackwardToNewMap, timezoneNewToBackwardMap} from '@src/TIMEZONES'; import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails'; import {setCurrentDate} from './actions/CurrentDate'; import {setNetworkLastOffline} from './actions/Network'; import {translate, translateLocal} from './Localize'; import BaseLocaleListener from './Localize/LocaleListener/BaseLocaleListener'; import Log from './Log'; +import memoize from './memoize'; type CustomStatusTypes = ValueOf; type Locale = ValueOf; @@ -147,7 +148,8 @@ function setLocale(localeString: Locale) { * Gets the user's stored time zone NVP and returns a localized * Date object for the given ISO-formatted datetime string */ -function getLocalDateFromDatetime(locale: Locale, datetime?: string, currentSelectedTimezone: SelectedTimezone = timezone.selected): Date { +// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents +function getLocalDateFromDatetime(locale: Locale, datetime?: string, currentSelectedTimezone: string | SelectedTimezone = timezone.selected): Date { setLocale(locale); if (!datetime) { const res = toZonedTime(new Date(), currentSelectedTimezone); @@ -214,6 +216,22 @@ function isYesterday(date: Date, timeZone: SelectedTimezone): boolean { return isSameDay(date, yesterdayInTimeZone); } +/** + * We have to fall back to older timezone names for native platforms that do not ship with newer timezone names to avoid a crash. + * Memoize to prevent unnecessary calculation as timezone support will not change on runtime on a platform. + */ +// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents +const fallbackToSupportedTimezone = memoize((timezoneInput: SelectedTimezone): SelectedTimezone | string => { + try { + const date = new Date(); + const testDate = toZonedTime(date, timezoneInput); + format(testDate, CONST.DATE.FNS_FORMAT_STRING); + return timezoneInput; + } catch (error) { + return timezoneNewToBackwardMap[timezoneInput]; + } +}); + /** * Formats an ISO-formatted datetime string to local date and time string * @@ -223,7 +241,7 @@ function isYesterday(date: Date, timeZone: SelectedTimezone): boolean { * Jan 20, 2019 at 5:30 PM anything over 1 year ago */ function datetimeToCalendarTime(locale: Locale, datetime: string, includeTimeZone = false, currentSelectedTimezone: SelectedTimezone = timezone.selected, isLowercase = false): string { - const date = getLocalDateFromDatetime(locale, datetime, currentSelectedTimezone); + const date = getLocalDateFromDatetime(locale, datetime, fallbackToSupportedTimezone(currentSelectedTimezone)); const tz = includeTimeZone ? ' [UTC]Z' : ''; let todayAt = translate(locale, 'common.todayAt'); let tomorrowAt = translate(locale, 'common.tomorrowAt'); @@ -757,7 +775,7 @@ function formatWithUTCTimeZone(datetime: string, dateFormat: string = CONST.DATE /** * * @param timezone - * function format unsupported timezone to supported timezone + * Convert unsupported old timezone to app supported timezone * @returns Timezone */ function formatToSupportedTimezone(timezoneInput: Timezone): Timezone { @@ -765,7 +783,7 @@ function formatToSupportedTimezone(timezoneInput: Timezone): Timezone { return timezoneInput; } return { - selected: timezoneBackwardMap[timezoneInput.selected] ?? timezoneInput.selected, + selected: timezoneBackwardToNewMap[timezoneInput.selected] ?? timezoneInput.selected, automatic: timezoneInput.automatic, }; } @@ -967,7 +985,7 @@ const formatInTimeZoneWithFallback: typeof formatInTimeZone = (date, timeZone, f // On macOs and iOS devices some platform use deprecated old timezone values which results in invalid time string error. // Try with backward timezone values on error. } catch { - return formatInTimeZone(date, timezoneBackwardMap[timeZone], formatStr, options); + return formatInTimeZone(date, timezoneNewToBackwardMap[timeZone as SelectedTimezone], formatStr, options); } };