From dbdd166e13562d6e7d88883292d97e5d5f7c9141 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 19 Nov 2025 12:42:13 -0700 Subject: [PATCH 1/6] Generate grouped timezones --- src/lib/components/timezone-select.svelte | 9 +- src/lib/stores/time-format.test.ts | 5 +- src/lib/stores/time-format.ts | 85 ++++++++++--------- src/lib/utilities/format-date.ts | 5 +- .../query/search-attribute-filter.ts | 3 +- 5 files changed, 50 insertions(+), 57 deletions(-) diff --git a/src/lib/components/timezone-select.svelte b/src/lib/components/timezone-select.svelte index 51c2b42944..085ce82d11 100644 --- a/src/lib/components/timezone-select.svelte +++ b/src/lib/components/timezone-select.svelte @@ -15,13 +15,11 @@ import { translate } from '$lib/i18n/translate'; import { relativeTime, - type TimeFormat, timeFormat, type TimeFormatOptions, TimezoneOptions, Timezones, } from '$lib/stores/time-format'; - import { capitalize } from '$lib/utilities/format-camel-case'; import { formatUTCOffset, getLocalTime } from '$lib/utilities/format-date'; export let position: 'left' | 'right' = 'right'; @@ -49,7 +47,7 @@ ); }); - const selectTimezone = (value: TimeFormat) => { + const selectTimezone = (value: string) => { if ($relativeTime && value !== 'local') $relativeTime = false; $timeFormat = value; search = ''; @@ -62,10 +60,7 @@ } }; - $: timezone = - Timezones[$timeFormat]?.abbr ?? - Timezones[$timeFormat]?.label ?? - capitalize($timeFormat); + $: timezone = Timezones[$timeFormat]?.abbr ?? $timeFormat; onMount(() => { if (String($timeFormat) === 'relative') { diff --git a/src/lib/stores/time-format.test.ts b/src/lib/stores/time-format.test.ts index ef7097b524..11498e1e2e 100644 --- a/src/lib/stores/time-format.test.ts +++ b/src/lib/stores/time-format.test.ts @@ -8,7 +8,6 @@ import { getUTCOffset, relativeTime, timeFormat, - type TimeFormat, Timezones, } from './time-format'; @@ -54,7 +53,7 @@ describe('formatOffset', () => { describe('getUTCOffset', () => { test('should return a formatted UTC offset for all Timezone options', () => { Object.entries(Timezones).forEach(([format, { offset }]) => { - expect(getUTCOffset(format as TimeFormat)).toBe(formatOffset(offset)); + expect(getUTCOffset(format)).toBe(formatOffset(offset)); }); }); @@ -63,6 +62,6 @@ describe('getUTCOffset', () => { }); test('should return a formatted UTC offset for a timezone', () => { - expect(getUTCOffset('America/Phoenix' as TimeFormat)).toBe('-07:00'); + expect(getUTCOffset('America/Phoenix')).toBe('-07:00'); }); }); diff --git a/src/lib/stores/time-format.ts b/src/lib/stores/time-format.ts index ae25a73a81..57946070e0 100644 --- a/src/lib/stores/time-format.ts +++ b/src/lib/stores/time-format.ts @@ -1,4 +1,5 @@ import { startOfDay } from 'date-fns'; +import { enUS } from 'date-fns/locale'; import * as dateTz from 'date-fns-tz'; import { persistStore } from '$lib/stores/persist-store'; @@ -8,7 +9,7 @@ type TimeFormatTypes = 'relative' | 'absolute'; export const TIME_UNIT_OPTIONS = ['minutes', 'hours', 'days']; -export const timeFormat = persistStore('timeFormat', 'UTC' as TimeFormat); +export const timeFormat = persistStore('timeFormat', 'UTC'); export const timeFormatType = persistStore( 'timeFormatType', 'relative' as TimeFormatTypes, @@ -31,50 +32,51 @@ export const endHour = persistStore('endHour', ''); export const endMinute = persistStore('endMinute', ''); export const endSecond = persistStore('endSecond', ''); -export type TimeFormat = keyof typeof Timezones | 'UTC' | 'local'; +type TimezoneInfo = { + abbr: string; + offset: number; + zones: string[]; +}; type TimeFormatOption = { label: string; - value: TimeFormat; - abbr?: string; - offset?: number; - zones?: string[]; -}; + value: string; +} & Partial; export type TimeFormatOptions = TimeFormatOption[]; -// Use this snippet to generate the Timezones object -// import { enUS } from 'date-fns/locale'; -// import { utcToZonedTime, getTimezoneOffset, format } from 'date-fns-tz'; -// const generateTimezoneOptions = () => { -// const timeZones = Intl.supportedValuesOf('timeZone'); -// return timeZones.reduce((acc, timeZone) => { -// const zonedTime = utcToZonedTime(new Date(), timeZone); -// const zoneString = format(zonedTime, 'zzzz', { -// timeZone, -// locale: enUS, -// }); -// if (acc[zoneString]) { -// acc[zoneString].zones.push(timeZone); -// } else { -// const zoneAbbr = format(zonedTime, 'z', { -// timeZone, -// locale: enUS, -// }); -// const offset = Math.floor( -// (getTimezoneOffset(timeZone) / (1000 * 60 * 60)) % 24, -// ); -// acc[zoneString] = { -// abbr: zoneAbbr, -// offset, -// zones: [timeZone], -// }; -// } -// return acc; -// }, {}); -// }; +const getGroupedTimezones = (): { + [key: string]: TimezoneInfo; +} => { + return Intl.supportedValuesOf('timeZone').reduce((acc, timeZone) => { + const zonedTime = dateTz.utcToZonedTime(new Date(), timeZone); + const zoneString = dateTz.format(zonedTime, 'zzzz', { + timeZone, + locale: enUS, + }); + if (acc[zoneString]) { + acc[zoneString].zones.push(timeZone); + } else { + const zoneAbbr = dateTz.format(zonedTime, 'z', { + timeZone, + locale: enUS, + }); + const offset = Math.floor( + (dateTz.getTimezoneOffset(timeZone) / (1000 * 60 * 60)) % 24, + ); + acc[zoneString] = { + abbr: zoneAbbr, + offset, + zones: [timeZone], + }; + } + return acc; + }, {}); +}; + +export const Timezones = getGroupedTimezones(); -export const Timezones = { +const DEPRECATED_TIMEZONES = { 'Greenwich Mean Time': { abbr: 'GMT', offset: 0, @@ -1172,8 +1174,7 @@ export const Timezones = { } as const; export const TimezoneOptions: TimeFormatOptions = Object.entries(Timezones) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .map(([key, value]: [TimeFormat, any]) => ({ + .map(([key, value]: [string, TimezoneInfo]) => ({ label: key, value: key, ...value, @@ -1184,7 +1185,7 @@ export const TimezoneOptions: TimeFormatOptions = Object.entries(Timezones) return 0; }); -export const getTimezone = (timeFormat: TimeFormat): string => { +export const getTimezone = (timeFormat: string): string => { if (timeFormat === 'local') return getLocalTimezone(); return Timezones[timeFormat]?.zones[0] ?? timeFormat; }; @@ -1194,7 +1195,7 @@ export const formatOffset = (offset: number) => { return offset >= 0 ? `+${formattedOffset}:00` : `-${formattedOffset}:00`; }; -export const getUTCOffset = (timeFormat: TimeFormat): string => { +export const getUTCOffset = (timeFormat: string): string => { let offset: number | undefined = Timezones[timeFormat]?.offset; if (offset === undefined) { diff --git a/src/lib/utilities/format-date.ts b/src/lib/utilities/format-date.ts index e5184b033d..64107692e6 100644 --- a/src/lib/utilities/format-date.ts +++ b/src/lib/utilities/format-date.ts @@ -8,7 +8,6 @@ import * as dateTz from 'date-fns-tz'; // `build` script fails on importing some import { getTimezone, - type TimeFormat, TimezoneOptions, Timezones, } from '$lib/stores/time-format'; @@ -19,7 +18,7 @@ const pattern = 'yyyy-MM-dd z HH:mm:ss.SS'; export function formatDate( date: ValidTime | undefined | null, - timeFormat: TimeFormat = 'UTC', + timeFormat: string = 'UTC', options: { relative?: boolean; relativeLabel?: string; @@ -103,7 +102,7 @@ export function getLocalTime(): string { : localTimezone; } -export function getSelectedTimezone(timeFormat: TimeFormat): string { +export function getSelectedTimezone(timeFormat: string): string { if (timeFormat === 'local') return getLocalTime(); const selectedTimezone = Timezones[timeFormat]; diff --git a/src/lib/utilities/query/search-attribute-filter.ts b/src/lib/utilities/query/search-attribute-filter.ts index 046adabdf0..88c751145c 100644 --- a/src/lib/utilities/query/search-attribute-filter.ts +++ b/src/lib/utilities/query/search-attribute-filter.ts @@ -2,7 +2,6 @@ import { get } from 'svelte/store'; import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; import { searchAttributes } from '$lib/stores/search-attributes'; -import type { TimeFormat } from '$lib/stores/time-format'; import { SEARCH_ATTRIBUTE_TYPE } from '$lib/types/workflows'; import { formatDate } from '$lib/utilities/format-date'; @@ -102,7 +101,7 @@ export function formatListFilterValue(value: string | null): string[] { export const formatDateTimeRange = ( value: string, - format: TimeFormat, + format: string, relative: boolean, ) => { const [conditon, start, operator, end] = value.split(' '); From 970b21a405df33d4601ea7138f73ffe02835fb06 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 19 Nov 2025 13:05:57 -0700 Subject: [PATCH 2/6] Default to local if timeFormat isn't found --- src/lib/components/timezone-select.svelte | 16 +- .../workflow/workflow-filters.svelte | 13 +- src/lib/stores/time-format.ts | 1109 +---------------- src/lib/utilities/format-date.ts | 5 +- 4 files changed, 33 insertions(+), 1110 deletions(-) diff --git a/src/lib/components/timezone-select.svelte b/src/lib/components/timezone-select.svelte index 085ce82d11..6992502bf8 100644 --- a/src/lib/components/timezone-select.svelte +++ b/src/lib/components/timezone-select.svelte @@ -14,6 +14,7 @@ import ToggleSwitch from '$lib/holocene/toggle-switch.svelte'; import { translate } from '$lib/i18n/translate'; import { + BASE_TIME_FORMAT_OPTIONS, relativeTime, timeFormat, type TimeFormatOptions, @@ -29,9 +30,9 @@ const QuickTimezoneOptions: TimeFormatOptions = [ { label: translate('common.utc'), - value: 'UTC', + value: BASE_TIME_FORMAT_OPTIONS.UTC, }, - { label: translate('common.local'), value: 'local' }, + { label: translate('common.local'), value: BASE_TIME_FORMAT_OPTIONS.LOCAL }, ]; let search = ''; @@ -48,15 +49,16 @@ }); const selectTimezone = (value: string) => { - if ($relativeTime && value !== 'local') $relativeTime = false; + if ($relativeTime && value !== BASE_TIME_FORMAT_OPTIONS.LOCAL) + $relativeTime = false; $timeFormat = value; search = ''; }; const handleRelativeToggle = (e: Event) => { const target = e.target as HTMLInputElement; - if (target.checked && $timeFormat !== 'local') { - $timeFormat = 'local'; + if (target.checked && $timeFormat !== BASE_TIME_FORMAT_OPTIONS.LOCAL) { + $timeFormat = BASE_TIME_FORMAT_OPTIONS.LOCAL; } }; @@ -64,7 +66,7 @@ onMount(() => { if (String($timeFormat) === 'relative') { - $timeFormat = 'local'; + $timeFormat = BASE_TIME_FORMAT_OPTIONS.LOCAL; $relativeTime = true; } }); @@ -105,7 +107,7 @@ on:click={() => selectTimezone(value)} data-testid={`timezones-${value}`} selected={value === $timeFormat} - description={value === 'local' && localTime} + description={value === BASE_TIME_FORMAT_OPTIONS.LOCAL && localTime} > {label} diff --git a/src/lib/components/workflow/workflow-filters.svelte b/src/lib/components/workflow/workflow-filters.svelte index 43684e6623..b4114e2339 100644 --- a/src/lib/components/workflow/workflow-filters.svelte +++ b/src/lib/components/workflow/workflow-filters.svelte @@ -10,7 +10,10 @@ import Option from '$lib/holocene/select/simple-option.svelte'; import Select from '$lib/holocene/select/simple-select.svelte'; import { translate } from '$lib/i18n/translate'; - import { timeFormat } from '$lib/stores/time-format'; + import { + BASE_TIME_FORMAT_OPTIONS, + timeFormat, + } from '$lib/stores/time-format'; import { toListWorkflowQuery } from '$lib/utilities/query/list-workflow-query'; import { toListWorkflowParameters } from '$lib/utilities/query/to-list-workflow-parameters'; import { durations } from '$lib/utilities/to-duration'; @@ -148,8 +151,12 @@ label={translate('common.time-format')} > - - + + {/if} diff --git a/src/lib/stores/time-format.ts b/src/lib/stores/time-format.ts index 57946070e0..4f9d571f3f 100644 --- a/src/lib/stores/time-format.ts +++ b/src/lib/stores/time-format.ts @@ -9,7 +9,12 @@ type TimeFormatTypes = 'relative' | 'absolute'; export const TIME_UNIT_OPTIONS = ['minutes', 'hours', 'days']; -export const timeFormat = persistStore('timeFormat', 'UTC'); +export const BASE_TIME_FORMAT_OPTIONS = { + LOCAL: 'local', + UTC: 'UTC', +}; +const DEFAULT_TIME_FORMAT = BASE_TIME_FORMAT_OPTIONS.LOCAL; +export const timeFormat = persistStore('timeFormat', DEFAULT_TIME_FORMAT); export const timeFormatType = persistStore( 'timeFormatType', 'relative' as TimeFormatTypes, @@ -76,1102 +81,10 @@ const getGroupedTimezones = (): { export const Timezones = getGroupedTimezones(); -const DEPRECATED_TIMEZONES = { - 'Greenwich Mean Time': { - abbr: 'GMT', - offset: 0, - zones: [ - 'Africa/Abidjan', - 'Africa/Accra', - 'Africa/Bamako', - 'Africa/Banjul', - 'Africa/Bissau', - 'Africa/Conakry', - 'Africa/Dakar', - 'Africa/Freetown', - 'Africa/Lome', - 'Africa/Monrovia', - 'Africa/Nouakchott', - 'Africa/Ouagadougou', - 'Africa/Sao_Tome', - 'America/Danmarkshavn', - 'Atlantic/Reykjavik', - 'Atlantic/St_Helena', - ], - }, - 'East Africa Time': { - abbr: 'GMT+3', - offset: 3, - zones: [ - 'Africa/Addis_Ababa', - 'Africa/Asmera', - 'Africa/Dar_es_Salaam', - 'Africa/Djibouti', - 'Africa/Kampala', - 'Africa/Mogadishu', - 'Africa/Nairobi', - 'Indian/Antananarivo', - 'Indian/Comoro', - 'Indian/Mayotte', - ], - }, - 'Central European Standard Time': { - abbr: 'GMT+1', - offset: 1, - zones: ['Africa/Algiers', 'Africa/Tunis'], - }, - 'West Africa Standard Time': { - abbr: 'GMT+1', - offset: 1, - zones: [ - 'Africa/Bangui', - 'Africa/Brazzaville', - 'Africa/Douala', - 'Africa/Kinshasa', - 'Africa/Lagos', - 'Africa/Libreville', - 'Africa/Luanda', - 'Africa/Malabo', - 'Africa/Ndjamena', - 'Africa/Niamey', - 'Africa/Porto-Novo', - ], - }, - 'Central Africa Time': { - abbr: 'GMT+2', - offset: 2, - zones: [ - 'Africa/Blantyre', - 'Africa/Bujumbura', - 'Africa/Gaborone', - 'Africa/Harare', - 'Africa/Juba', - 'Africa/Khartoum', - 'Africa/Kigali', - 'Africa/Lubumbashi', - 'Africa/Lusaka', - 'Africa/Maputo', - 'Africa/Windhoek', - ], - }, - 'Eastern European Summer Time': { - abbr: 'GMT+3', - offset: 3, - zones: [ - 'Africa/Cairo', - 'Asia/Beirut', - 'Asia/Gaza', - 'Asia/Hebron', - 'Asia/Nicosia', - 'Europe/Athens', - 'Europe/Bucharest', - 'Europe/Chisinau', - 'Europe/Helsinki', - 'Europe/Kiev', - 'Europe/Mariehamn', - 'Europe/Riga', - 'Europe/Sofia', - 'Europe/Tallinn', - 'Europe/Uzhgorod', - 'Europe/Vilnius', - 'Europe/Zaporozhye', - ], - }, - 'GMT+01:00': { - abbr: 'GMT+1', - offset: 1, - zones: [ - 'Africa/Casablanca', - 'Africa/El_Aaiun', - 'Europe/Guernsey', - 'Europe/Isle_of_Man', - 'Europe/Jersey', - ], - }, - 'Central European Summer Time': { - abbr: 'GMT+2', - offset: 2, - zones: [ - 'Africa/Ceuta', - 'Arctic/Longyearbyen', - 'Europe/Amsterdam', - 'Europe/Andorra', - 'Europe/Belgrade', - 'Europe/Berlin', - 'Europe/Bratislava', - 'Europe/Brussels', - 'Europe/Budapest', - 'Europe/Busingen', - 'Europe/Copenhagen', - 'Europe/Gibraltar', - 'Europe/Ljubljana', - 'Europe/Luxembourg', - 'Europe/Madrid', - 'Europe/Malta', - 'Europe/Monaco', - 'Europe/Oslo', - 'Europe/Paris', - 'Europe/Podgorica', - 'Europe/Prague', - 'Europe/Rome', - 'Europe/San_Marino', - 'Europe/Sarajevo', - 'Europe/Skopje', - 'Europe/Stockholm', - 'Europe/Tirane', - 'Europe/Vaduz', - 'Europe/Vatican', - 'Europe/Vienna', - 'Europe/Warsaw', - 'Europe/Zagreb', - 'Europe/Zurich', - ], - }, - 'South Africa Standard Time': { - abbr: 'GMT+2', - offset: 2, - zones: ['Africa/Johannesburg', 'Africa/Maseru', 'Africa/Mbabane'], - }, - 'Eastern European Standard Time': { - abbr: 'GMT+2', - offset: 2, - zones: ['Africa/Tripoli', 'Europe/Kaliningrad'], - }, - 'Hawaii-Aleutian Daylight Time': { - abbr: 'HADT', - offset: -9, - zones: ['America/Adak'], - }, - 'Alaska Daylight Time': { - abbr: 'AKDT', - offset: -8, - zones: [ - 'America/Anchorage', - 'America/Juneau', - 'America/Metlakatla', - 'America/Nome', - 'America/Sitka', - 'America/Yakutat', - ], - }, - 'Atlantic Standard Time': { - abbr: 'AST', - offset: -4, - zones: [ - 'America/Anguilla', - 'America/Antigua', - 'America/Aruba', - 'America/Barbados', - 'America/Blanc-Sablon', - 'America/Curacao', - 'America/Dominica', - 'America/Grenada', - 'America/Guadeloupe', - 'America/Kralendijk', - 'America/Lower_Princes', - 'America/Marigot', - 'America/Martinique', - 'America/Montserrat', - 'America/Port_of_Spain', - 'America/Puerto_Rico', - 'America/Santo_Domingo', - 'America/St_Barthelemy', - 'America/St_Kitts', - 'America/St_Lucia', - 'America/St_Thomas', - 'America/St_Vincent', - 'America/Tortola', - ], - }, - 'Brasilia Standard Time': { - abbr: 'GMT-3', - offset: -3, - zones: [ - 'America/Araguaina', - 'America/Bahia', - 'America/Belem', - 'America/Fortaleza', - 'America/Maceio', - 'America/Recife', - 'America/Santarem', - 'America/Sao_Paulo', - ], - }, - 'Argentina Standard Time': { - abbr: 'GMT-3', - offset: -3, - zones: [ - 'America/Argentina/La_Rioja', - 'America/Argentina/Rio_Gallegos', - 'America/Argentina/Salta', - 'America/Argentina/San_Juan', - 'America/Argentina/San_Luis', - 'America/Argentina/Tucuman', - 'America/Argentina/Ushuaia', - 'America/Buenos_Aires', - 'America/Catamarca', - 'America/Cordoba', - 'America/Jujuy', - 'America/Mendoza', - ], - }, - 'Paraguay Standard Time': { - abbr: 'GMT-4', - offset: -4, - zones: ['America/Asuncion'], - }, - 'Central Standard Time': { - abbr: 'CST', - offset: -6, - zones: [ - 'America/Bahia_Banderas', - 'America/Belize', - 'America/Chihuahua', - 'America/Costa_Rica', - 'America/El_Salvador', - 'America/Guatemala', - 'America/Managua', - 'America/Merida', - 'America/Mexico_City', - 'America/Monterrey', - 'America/Regina', - 'America/Swift_Current', - 'America/Tegucigalpa', - ], - }, - 'Amazon Standard Time': { - abbr: 'GMT-4', - offset: -4, - zones: [ - 'America/Boa_Vista', - 'America/Campo_Grande', - 'America/Cuiaba', - 'America/Manaus', - 'America/Porto_Velho', - ], - }, - 'Colombia Standard Time': { - abbr: 'GMT-5', - offset: -5, - zones: ['America/Bogota'], - }, - 'Mountain Daylight Time': { - abbr: 'MDT', - offset: -6, - zones: [ - 'America/Boise', - 'America/Cambridge_Bay', - 'America/Ciudad_Juarez', - 'America/Denver', - 'America/Edmonton', - 'America/Inuvik', - 'America/Yellowknife', - ], - }, - 'Eastern Standard Time': { - abbr: 'EST', - offset: -5, - zones: [ - 'America/Cancun', - 'America/Cayman', - 'America/Coral_Harbour', - 'America/Jamaica', - 'America/Panama', - ], - }, - 'Venezuela Time': { - abbr: 'GMT-4', - offset: -4, - zones: ['America/Caracas'], - }, - 'French Guiana Time': { - abbr: 'GMT-3', - offset: -3, - zones: ['America/Cayenne'], - }, - 'Central Daylight Time': { - abbr: 'CDT', - offset: -5, - zones: [ - 'America/Chicago', - 'America/Indiana/Knox', - 'America/Indiana/Tell_City', - 'America/Matamoros', - 'America/Menominee', - 'America/North_Dakota/Beulah', - 'America/North_Dakota/Center', - 'America/North_Dakota/New_Salem', - 'America/Ojinaga', - 'America/Rainy_River', - 'America/Rankin_Inlet', - 'America/Resolute', - 'America/Winnipeg', - ], - }, - 'Mountain Standard Time': { - abbr: 'MST', - offset: -7, - zones: [ - 'America/Creston', - 'America/Dawson_Creek', - 'America/Fort_Nelson', - 'America/Phoenix', - ], - }, - 'Yukon Time': { - abbr: 'GMT-7', - offset: -7, - zones: ['America/Dawson', 'America/Whitehorse'], - }, - 'Eastern Daylight Time': { - abbr: 'EDT', - offset: -4, - zones: [ - 'America/Detroit', - 'America/Grand_Turk', - 'America/Indiana/Marengo', - 'America/Indiana/Petersburg', - 'America/Indiana/Vevay', - 'America/Indiana/Vincennes', - 'America/Indiana/Winamac', - 'America/Indianapolis', - 'America/Iqaluit', - 'America/Kentucky/Monticello', - 'America/Louisville', - 'America/Nassau', - 'America/New_York', - 'America/Nipigon', - 'America/Pangnirtung', - 'America/Port-au-Prince', - 'America/Thunder_Bay', - 'America/Toronto', - ], - }, - 'Acre Standard Time': { - abbr: 'GMT-5', - offset: -5, - zones: ['America/Eirunepe', 'America/Rio_Branco'], - }, - 'Atlantic Daylight Time': { - abbr: 'ADT', - offset: -3, - zones: [ - 'America/Glace_Bay', - 'America/Goose_Bay', - 'America/Halifax', - 'America/Moncton', - 'America/Thule', - 'Atlantic/Bermuda', - ], - }, - 'West Greenland Summer Time': { - abbr: 'GMT-2', - offset: -2, - zones: ['America/Godthab'], - }, - 'Ecuador Time': { - abbr: 'GMT-5', - offset: -5, - zones: ['America/Guayaquil'], - }, - 'Guyana Time': { - abbr: 'GMT-4', - offset: -4, - zones: ['America/Guyana'], - }, - 'Cuba Daylight Time': { - abbr: 'GMT-4', - offset: -4, - zones: ['America/Havana'], - }, - 'Mexican Pacific Standard Time': { - abbr: 'GMT-7', - offset: -7, - zones: ['America/Hermosillo', 'America/Mazatlan'], - }, - 'Bolivia Time': { - abbr: 'GMT-4', - offset: -4, - zones: ['America/La_Paz'], - }, - 'Peru Standard Time': { - abbr: 'GMT-5', - offset: -5, - zones: ['America/Lima'], - }, - 'Pacific Daylight Time': { - abbr: 'PDT', - offset: -7, - zones: ['America/Los_Angeles', 'America/Tijuana', 'America/Vancouver'], - }, - 'St. Pierre & Miquelon Daylight Time': { - abbr: 'GMT-2', - offset: -2, - zones: ['America/Miquelon'], - }, - 'Uruguay Standard Time': { - abbr: 'GMT-3', - offset: -3, - zones: ['America/Montevideo'], - }, - 'GMT-04:00': { - abbr: 'GMT-4', - offset: -4, - zones: ['America/Montreal'], - }, - 'Fernando de Noronha Standard Time': { - abbr: 'GMT-2', - offset: -2, - zones: ['America/Noronha'], - }, - 'Suriname Time': { - abbr: 'GMT-3', - offset: -3, - zones: ['America/Paramaribo'], - }, - 'GMT-03:00': { - abbr: 'GMT-3', - offset: -3, - zones: ['America/Punta_Arenas', 'Antarctica/Palmer'], - }, - 'Northwest Mexico Daylight Time': { - abbr: 'GMT-7', - offset: -7, - zones: ['America/Santa_Isabel'], - }, - 'Chile Standard Time': { - abbr: 'GMT-4', - offset: -4, - zones: ['America/Santiago'], - }, - 'East Greenland Summer Time': { - abbr: 'GMT', - offset: 0, - zones: ['America/Scoresbysund'], - }, - 'Newfoundland Daylight Time': { - abbr: 'GMT-2:30', - offset: -3, - zones: ['America/St_Johns'], - }, - 'Casey Time': { - abbr: 'GMT+11', - offset: 11, - zones: ['Antarctica/Casey'], - }, - 'Davis Time': { - abbr: 'GMT+7', - offset: 7, - zones: ['Antarctica/Davis'], - }, - 'Dumont-d’Urville Time': { - abbr: 'GMT+10', - offset: 10, - zones: ['Antarctica/DumontDUrville'], - }, - 'Australian Eastern Standard Time': { - abbr: 'GMT+10', - offset: 10, - zones: [ - 'Antarctica/Macquarie', - 'Australia/Brisbane', - 'Australia/Currie', - 'Australia/Hobart', - 'Australia/Lindeman', - 'Australia/Melbourne', - 'Australia/Sydney', - ], - }, - 'Mawson Time': { - abbr: 'GMT+5', - offset: 5, - zones: ['Antarctica/Mawson'], - }, - 'New Zealand Standard Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Antarctica/McMurdo', 'Pacific/Auckland'], - }, - 'Rothera Time': { - abbr: 'GMT-3', - offset: -3, - zones: ['Antarctica/Rothera'], - }, - 'Syowa Time': { - abbr: 'GMT+3', - offset: 3, - zones: ['Antarctica/Syowa'], - }, - 'GMT+02:00': { - abbr: 'GMT+2', - offset: 2, - zones: ['Antarctica/Troll'], - }, - 'Vostok Time': { - abbr: 'GMT+6', - offset: 6, - zones: ['Antarctica/Vostok'], - }, - 'Arabian Standard Time': { - abbr: 'GMT+3', - offset: 3, - zones: [ - 'Asia/Aden', - 'Asia/Baghdad', - 'Asia/Bahrain', - 'Asia/Kuwait', - 'Asia/Qatar', - 'Asia/Riyadh', - ], - }, - 'East Kazakhstan Time': { - abbr: 'GMT+6', - offset: 6, - zones: ['Asia/Almaty', 'Asia/Qostanay'], - }, - 'GMT+03:00': { - abbr: 'GMT+3', - offset: 3, - zones: [ - 'Asia/Amman', - 'Asia/Damascus', - 'Asia/Famagusta', - 'Europe/Istanbul', - 'Europe/Kirov', - ], - }, - 'Anadyr Standard Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Asia/Anadyr'], - }, - 'West Kazakhstan Time': { - abbr: 'GMT+5', - offset: 5, - zones: [ - 'Asia/Aqtau', - 'Asia/Aqtobe', - 'Asia/Atyrau', - 'Asia/Oral', - 'Asia/Qyzylorda', - ], - }, - 'Turkmenistan Standard Time': { - abbr: 'GMT+5', - offset: 5, - zones: ['Asia/Ashgabat'], - }, - 'Azerbaijan Standard Time': { - abbr: 'GMT+4', - offset: 4, - zones: ['Asia/Baku'], - }, - 'Indochina Time': { - abbr: 'GMT+7', - offset: 7, - zones: ['Asia/Bangkok', 'Asia/Phnom_Penh', 'Asia/Saigon', 'Asia/Vientiane'], - }, - 'GMT+07:00': { - abbr: 'GMT+7', - offset: 7, - zones: ['Asia/Barnaul', 'Asia/Tomsk'], - }, - 'Kyrgyzstan Time': { - abbr: 'GMT+6', - offset: 6, - zones: ['Asia/Bishkek'], - }, - 'Brunei Darussalam Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Brunei'], - }, - 'India Standard Time': { - abbr: 'GMT+5:30', - offset: 5, - zones: ['Asia/Calcutta', 'Asia/Colombo'], - }, - 'Yakutsk Standard Time': { - abbr: 'GMT+9', - offset: 9, - zones: ['Asia/Chita', 'Asia/Khandyga', 'Asia/Yakutsk'], - }, - 'Ulaanbaatar Standard Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Choibalsan', 'Asia/Ulaanbaatar'], - }, - 'Bangladesh Standard Time': { - abbr: 'GMT+6', - offset: 6, - zones: ['Asia/Dhaka'], - }, - 'East Timor Time': { - abbr: 'GMT+9', - offset: 9, - zones: ['Asia/Dili'], - }, - 'Gulf Standard Time': { - abbr: 'GMT+4', - offset: 4, - zones: ['Asia/Dubai', 'Asia/Muscat'], - }, - 'Tajikistan Time': { - abbr: 'GMT+5', - offset: 5, - zones: ['Asia/Dushanbe'], - }, - 'Hong Kong Standard Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Hong_Kong'], - }, - 'Hovd Standard Time': { - abbr: 'GMT+7', - offset: 7, - zones: ['Asia/Hovd'], - }, - 'Irkutsk Standard Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Irkutsk'], - }, - 'Western Indonesia Time': { - abbr: 'GMT+7', - offset: 7, - zones: ['Asia/Jakarta', 'Asia/Pontianak'], - }, - 'Eastern Indonesia Time': { - abbr: 'GMT+9', - offset: 9, - zones: ['Asia/Jayapura'], - }, - 'Israel Daylight Time': { - abbr: 'GMT+3', - offset: 3, - zones: ['Asia/Jerusalem'], - }, - 'Afghanistan Time': { - abbr: 'GMT+4:30', - offset: 4, - zones: ['Asia/Kabul'], - }, - 'Petropavlovsk-Kamchatski Standard Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Asia/Kamchatka'], - }, - 'Pakistan Standard Time': { - abbr: 'GMT+5', - offset: 5, - zones: ['Asia/Karachi'], - }, - 'Nepal Time': { - abbr: 'GMT+5:45', - offset: 5, - zones: ['Asia/Katmandu'], - }, - 'Krasnoyarsk Standard Time': { - abbr: 'GMT+7', - offset: 7, - zones: ['Asia/Krasnoyarsk', 'Asia/Novokuznetsk'], - }, - 'Malaysia Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Kuala_Lumpur', 'Asia/Kuching'], - }, - 'China Standard Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Macau', 'Asia/Shanghai'], - }, - 'Magadan Standard Time': { - abbr: 'GMT+11', - offset: 11, - zones: ['Asia/Magadan'], - }, - 'Central Indonesia Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Makassar'], - }, - 'Philippine Standard Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Manila'], - }, - 'Novosibirsk Standard Time': { - abbr: 'GMT+7', - offset: 7, - zones: ['Asia/Novosibirsk'], - }, - 'Omsk Standard Time': { - abbr: 'GMT+6', - offset: 6, - zones: ['Asia/Omsk'], - }, - 'Korean Standard Time': { - abbr: 'GMT+9', - offset: 9, - zones: ['Asia/Pyongyang', 'Asia/Seoul'], - }, - 'Myanmar Time': { - abbr: 'GMT+6:30', - offset: 6, - zones: ['Asia/Rangoon'], - }, - 'Sakhalin Standard Time': { - abbr: 'GMT+11', - offset: 11, - zones: ['Asia/Sakhalin'], - }, - 'Uzbekistan Standard Time': { - abbr: 'GMT+5', - offset: 5, - zones: ['Asia/Samarkand', 'Asia/Tashkent'], - }, - 'Singapore Standard Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Singapore'], - }, - 'GMT+11:00': { - abbr: 'GMT+11', - offset: 11, - zones: ['Asia/Srednekolymsk', 'Pacific/Bougainville'], - }, - 'Taipei Standard Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Asia/Taipei'], - }, - 'Georgia Standard Time': { - abbr: 'GMT+4', - offset: 4, - zones: ['Asia/Tbilisi'], - }, - 'Iran Standard Time': { - abbr: 'GMT+3:30', - offset: 3, - zones: ['Asia/Tehran'], - }, - 'Bhutan Time': { - abbr: 'GMT+6', - offset: 6, - zones: ['Asia/Thimphu'], - }, - 'Japan Standard Time': { - abbr: 'GMT+9', - offset: 9, - zones: ['Asia/Tokyo'], - }, - 'GMT+06:00': { - abbr: 'GMT+6', - offset: 6, - zones: ['Asia/Urumqi'], - }, - 'Vladivostok Standard Time': { - abbr: 'GMT+10', - offset: 10, - zones: ['Asia/Ust-Nera', 'Asia/Vladivostok'], - }, - 'Yekaterinburg Standard Time': { - abbr: 'GMT+5', - offset: 5, - zones: ['Asia/Yekaterinburg'], - }, - 'Armenia Standard Time': { - abbr: 'GMT+4', - offset: 4, - zones: ['Asia/Yerevan'], - }, - 'Azores Summer Time': { - abbr: 'GMT', - offset: 0, - zones: ['Atlantic/Azores'], - }, - 'Western European Summer Time': { - abbr: 'GMT+1', - offset: 1, - zones: [ - 'Atlantic/Canary', - 'Atlantic/Faeroe', - 'Atlantic/Madeira', - 'Europe/Lisbon', - ], - }, - 'Cape Verde Standard Time': { - abbr: 'GMT-1', - offset: -1, - zones: ['Atlantic/Cape_Verde'], - }, - 'South Georgia Time': { - abbr: 'GMT-2', - offset: -2, - zones: ['Atlantic/South_Georgia'], - }, - 'Falkland Islands Standard Time': { - abbr: 'GMT-3', - offset: -3, - zones: ['Atlantic/Stanley'], - }, - 'Australian Central Standard Time': { - abbr: 'GMT+9:30', - offset: 9, - zones: ['Australia/Adelaide', 'Australia/Broken_Hill', 'Australia/Darwin'], - }, - 'Australian Central Western Standard Time': { - abbr: 'GMT+8:45', - offset: 8, - zones: ['Australia/Eucla'], - }, - 'Lord Howe Standard Time': { - abbr: 'GMT+10:30', - offset: 10, - zones: ['Australia/Lord_Howe'], - }, - 'Australian Western Standard Time': { - abbr: 'GMT+8', - offset: 8, - zones: ['Australia/Perth'], - }, - 'GMT+04:00': { - abbr: 'GMT+4', - offset: 4, - zones: ['Europe/Astrakhan', 'Europe/Saratov', 'Europe/Ulyanovsk'], - }, - 'Irish Standard Time': { - abbr: 'GMT+1', - offset: 1, - zones: ['Europe/Dublin'], - }, - 'British Summer Time': { - abbr: 'GMT+1', - offset: 1, - zones: ['Europe/London'], - }, - 'Moscow Standard Time': { - abbr: 'GMT+3', - offset: 3, - zones: ['Europe/Minsk', 'Europe/Moscow', 'Europe/Simferopol'], - }, - 'Samara Standard Time': { - abbr: 'GMT+4', - offset: 4, - zones: ['Europe/Samara'], - }, - 'Volgograd Standard Time': { - abbr: 'GMT+3', - offset: 3, - zones: ['Europe/Volgograd'], - }, - 'Indian Ocean Time': { - abbr: 'GMT+6', - offset: 6, - zones: ['Indian/Chagos'], - }, - 'Christmas Island Time': { - abbr: 'GMT+7', - offset: 7, - zones: ['Indian/Christmas'], - }, - 'Cocos Islands Time': { - abbr: 'GMT+6:30', - offset: 6, - zones: ['Indian/Cocos'], - }, - 'French Southern & Antarctic Time': { - abbr: 'GMT+5', - offset: 5, - zones: ['Indian/Kerguelen'], - }, - 'Seychelles Time': { - abbr: 'GMT+4', - offset: 4, - zones: ['Indian/Mahe'], - }, - 'Maldives Time': { - abbr: 'GMT+5', - offset: 5, - zones: ['Indian/Maldives'], - }, - 'Mauritius Standard Time': { - abbr: 'GMT+4', - offset: 4, - zones: ['Indian/Mauritius'], - }, - 'Réunion Time': { - abbr: 'GMT+4', - offset: 4, - zones: ['Indian/Reunion'], - }, - 'Apia Standard Time': { - abbr: 'GMT+13', - offset: 13, - zones: ['Pacific/Apia'], - }, - 'Chatham Standard Time': { - abbr: 'GMT+12:45', - offset: 12, - zones: ['Pacific/Chatham'], - }, - 'Easter Island Standard Time': { - abbr: 'GMT-6', - offset: -6, - zones: ['Pacific/Easter'], - }, - 'Vanuatu Standard Time': { - abbr: 'GMT+11', - offset: 11, - zones: ['Pacific/Efate'], - }, - 'Phoenix Islands Time': { - abbr: 'GMT+13', - offset: 13, - zones: ['Pacific/Enderbury'], - }, - 'Tokelau Time': { - abbr: 'GMT+13', - offset: 13, - zones: ['Pacific/Fakaofo'], - }, - 'Fiji Standard Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Pacific/Fiji'], - }, - 'Tuvalu Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Pacific/Funafuti'], - }, - 'Galapagos Time': { - abbr: 'GMT-6', - offset: -6, - zones: ['Pacific/Galapagos'], - }, - 'Gambier Time': { - abbr: 'GMT-9', - offset: -9, - zones: ['Pacific/Gambier'], - }, - 'Solomon Islands Time': { - abbr: 'GMT+11', - offset: 11, - zones: ['Pacific/Guadalcanal'], - }, - 'Chamorro Standard Time': { - abbr: 'GMT+10', - offset: 10, - zones: ['Pacific/Guam', 'Pacific/Saipan'], - }, - 'Hawaii-Aleutian Standard Time': { - abbr: 'HST', - offset: -10, - zones: ['Pacific/Honolulu', 'Pacific/Johnston'], - }, - 'Line Islands Time': { - abbr: 'GMT+14', - offset: 14, - zones: ['Pacific/Kiritimati'], - }, - 'Kosrae Time': { - abbr: 'GMT+11', - offset: 11, - zones: ['Pacific/Kosrae'], - }, - 'Marshall Islands Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Pacific/Kwajalein', 'Pacific/Majuro'], - }, - 'Marquesas Time': { - abbr: 'GMT-9:30', - offset: -10, - zones: ['Pacific/Marquesas'], - }, - 'Samoa Standard Time': { - abbr: 'GMT-11', - offset: -11, - zones: ['Pacific/Midway', 'Pacific/Pago_Pago'], - }, - 'Nauru Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Pacific/Nauru'], - }, - 'Niue Time': { - abbr: 'GMT-11', - offset: -11, - zones: ['Pacific/Niue'], - }, - 'Norfolk Island Standard Time': { - abbr: 'GMT+11', - offset: 11, - zones: ['Pacific/Norfolk'], - }, - 'New Caledonia Standard Time': { - abbr: 'GMT+11', - offset: 11, - zones: ['Pacific/Noumea'], - }, - 'Palau Time': { - abbr: 'GMT+9', - offset: 9, - zones: ['Pacific/Palau'], - }, - 'Pitcairn Time': { - abbr: 'GMT-8', - offset: -8, - zones: ['Pacific/Pitcairn'], - }, - 'Ponape Time': { - abbr: 'GMT+11', - offset: 11, - zones: ['Pacific/Ponape'], - }, - 'Papua New Guinea Time': { - abbr: 'GMT+10', - offset: 10, - zones: ['Pacific/Port_Moresby'], - }, - 'Cook Islands Standard Time': { - abbr: 'GMT-10', - offset: -10, - zones: ['Pacific/Rarotonga'], - }, - 'Tahiti Time': { - abbr: 'GMT-10', - offset: -10, - zones: ['Pacific/Tahiti'], - }, - 'Gilbert Islands Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Pacific/Tarawa'], - }, - 'Tonga Standard Time': { - abbr: 'GMT+13', - offset: 13, - zones: ['Pacific/Tongatapu'], - }, - 'Chuuk Time': { - abbr: 'GMT+10', - offset: 10, - zones: ['Pacific/Truk'], - }, - 'Wake Island Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Pacific/Wake'], - }, - 'Wallis & Futuna Time': { - abbr: 'GMT+12', - offset: 12, - zones: ['Pacific/Wallis'], - }, -} as const; +timeFormat.subscribe((value) => { + if (Object.values(BASE_TIME_FORMAT_OPTIONS).includes(value)) return; + if (!Timezones[value]) timeFormat.set(DEFAULT_TIME_FORMAT); +}); export const TimezoneOptions: TimeFormatOptions = Object.entries(Timezones) .map(([key, value]: [string, TimezoneInfo]) => ({ @@ -1186,7 +99,7 @@ export const TimezoneOptions: TimeFormatOptions = Object.entries(Timezones) }); export const getTimezone = (timeFormat: string): string => { - if (timeFormat === 'local') return getLocalTimezone(); + if (timeFormat === BASE_TIME_FORMAT_OPTIONS.LOCAL) return getLocalTimezone(); return Timezones[timeFormat]?.zones[0] ?? timeFormat; }; diff --git a/src/lib/utilities/format-date.ts b/src/lib/utilities/format-date.ts index 64107692e6..85bc3d9109 100644 --- a/src/lib/utilities/format-date.ts +++ b/src/lib/utilities/format-date.ts @@ -7,6 +7,7 @@ import { import * as dateTz from 'date-fns-tz'; // `build` script fails on importing some of named CommonJS modules import { + BASE_TIME_FORMAT_OPTIONS, getTimezone, TimezoneOptions, Timezones, @@ -50,7 +51,7 @@ export function formatDate( : 'yyyy-MM-dd HH:mm a' : pattern; - if (timeFormat === 'local') { + if (timeFormat === BASE_TIME_FORMAT_OPTIONS.LOCAL) { if (relative) return ( formatDistanceToNowStrict(parsed, { @@ -103,7 +104,7 @@ export function getLocalTime(): string { } export function getSelectedTimezone(timeFormat: string): string { - if (timeFormat === 'local') return getLocalTime(); + if (timeFormat === BASE_TIME_FORMAT_OPTIONS.LOCAL) return getLocalTime(); const selectedTimezone = Timezones[timeFormat]; if (selectedTimezone) return `${timeFormat} (${selectedTimezone.abbr})`; From d8866d008d977085581b2dc1d75a41bfc9276ba3 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Thu, 20 Nov 2025 16:08:57 -0700 Subject: [PATCH 3/6] Update tests --- src/lib/stores/time-format.test.ts | 6 +++--- src/lib/utilities/format-date.test.ts | 3 --- .../query/search-attribute-filter.test.ts | 4 ++-- ...kflows-search-attribute-filter.desktop.spec.ts | 15 +++++++++------ ...rkflows-search-attribute-filter.mobile.spec.ts | 10 +++++----- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/lib/stores/time-format.test.ts b/src/lib/stores/time-format.test.ts index 11498e1e2e..2bd4fe7056 100644 --- a/src/lib/stores/time-format.test.ts +++ b/src/lib/stores/time-format.test.ts @@ -12,8 +12,8 @@ import { } from './time-format'; describe('time format store', () => { - test('should return UTC as the default timeFormat', () => { - expect(get(timeFormat)).toBe('UTC'); + test('should return local as the default timeFormat', () => { + expect(get(timeFormat)).toBe('local'); }); test('should return false as the default for relativeTime', () => { expect(get(relativeTime)).toBe(false); @@ -22,7 +22,7 @@ describe('time format store', () => { describe('getTimezone', () => { test('should return the first zone for the specified time format in the Timezones object', () => { - expect(getTimezone('Pacific Daylight Time')).toBe('America/Los_Angeles'); + expect(getTimezone('Central Standard Time')).toBe('America/Bahia_Banderas'); expect(getTimezone('Greenwich Mean Time')).toBe('Africa/Abidjan'); }); diff --git a/src/lib/utilities/format-date.test.ts b/src/lib/utilities/format-date.test.ts index 7ec36affe4..ed77fd0be7 100644 --- a/src/lib/utilities/format-date.test.ts +++ b/src/lib/utilities/format-date.test.ts @@ -50,9 +50,6 @@ describe('formatDate', () => { expect(formatDate(date, 'Central Standard Time')).toEqual( 'Apr 13, 2022, 11:29:35.63 AM CDT', ); - expect(formatDate(date, 'Pacific Daylight Time')).toEqual( - 'Apr 13, 2022, 9:29:35.63 AM PDT', - ); }); it('should format already formatted strings', () => { diff --git a/src/lib/utilities/query/search-attribute-filter.test.ts b/src/lib/utilities/query/search-attribute-filter.test.ts index fe77f85506..6f94119782 100644 --- a/src/lib/utilities/query/search-attribute-filter.test.ts +++ b/src/lib/utilities/query/search-attribute-filter.test.ts @@ -209,11 +209,11 @@ describe('formatDateTimeRange', () => { expect( formatDateTimeRange( 'BETWEEN "2025-07-17T00:00:00.000Z" AND "2025-07-17T00:00:00.000Z"', - 'Pacific Daylight Time', + 'Greenwich Mean Time', false, ), ).toStrictEqual( - 'between 7/16/25, 5:00:00.00 PM PDT and 7/16/25, 5:00:00.00 PM PDT', + 'between 7/17/25, 12:00:00.00 AM GMT and 7/17/25, 12:00:00.00 AM GMT', ); }); }); diff --git a/tests/integration/workflows-search-attribute-filter.desktop.spec.ts b/tests/integration/workflows-search-attribute-filter.desktop.spec.ts index eacff65d10..8c69c715fc 100644 --- a/tests/integration/workflows-search-attribute-filter.desktop.spec.ts +++ b/tests/integration/workflows-search-attribute-filter.desktop.spec.ts @@ -32,8 +32,8 @@ test('it should update the datetime filter based on the selected timezone', asyn page, }) => { await page.getByTestId('timezones-menu-button').click(); - await page.getByTestId('top-nav').getByPlaceholder('Search').fill('PDT'); - await page.getByText('Pacific Daylight Time (PDT) UTC-07:00').click(); + await page.getByTestId('top-nav').getByPlaceholder('Search').fill('CST'); + await page.getByText('Central Standard Time (CST)').click(); await page.getByTestId('toggle-manual-query').click(); await page @@ -48,7 +48,7 @@ test('it should update the datetime filter based on the selected timezone', asyn await expect( page.getByRole('button', { - name: 'CloseTime >= 12/25/25, 4:00:00.00 AM PST', + name: 'CloseTime >= 12/25/25, 6:00:00.00 AM CST', }), ).toBeVisible(); @@ -58,12 +58,15 @@ test('it should update the datetime filter based on the selected timezone', asyn expect(getDatetime(query)).toMatch(validDatetime); await page.getByTestId('timezones-menu-button').click(); - await page.getByTestId('top-nav').getByPlaceholder('Search').fill('MDT'); - await page.getByText('Mountain Daylight Time (MDT) UTC-06:00').click(); + await page + .getByTestId('top-nav') + .getByPlaceholder('Search') + .fill('Greenwich Mean Time'); + await page.getByText('Greenwich Mean Time (GMT)').click(); await expect( page.getByRole('button', { - name: 'CloseTime >= 12/25/25, 5:00:00.00 AM MST', + name: 'CloseTime >= 12/25/25, 12:00:00.00 PM GMT', }), ).toBeVisible(); diff --git a/tests/integration/workflows-search-attribute-filter.mobile.spec.ts b/tests/integration/workflows-search-attribute-filter.mobile.spec.ts index 6e443bdae7..984e5580c1 100644 --- a/tests/integration/workflows-search-attribute-filter.mobile.spec.ts +++ b/tests/integration/workflows-search-attribute-filter.mobile.spec.ts @@ -41,9 +41,9 @@ test('it should update the datetime filter based on the selected timezone', asyn .locator('#timezones-menu') .locator('visible=true') .getByPlaceholder('Search') - .fill('PDT'); + .fill('CST'); await page - .getByText('Pacific Daylight Time (PDT) UTC-07:00') + .getByText('Central Standard Time (CST)') .locator('visible=true') .click(); @@ -62,7 +62,7 @@ test('it should update the datetime filter based on the selected timezone', asyn await expect( page.getByRole('button', { - name: 'CloseTime >= 12/25/25, 4:00:00.00 AM PST', + name: 'CloseTime >= 12/25/25, 6:00:00.00 AM CST', }), ).toBeVisible(); let query = await page.getByTestId('manual-search-input').inputValue(); @@ -78,9 +78,9 @@ test('it should update the datetime filter based on the selected timezone', asyn .locator('#timezones-menu') .locator('visible=true') .getByPlaceholder('Search') - .fill('MDT'); + .fill('Greenwich Mean Time'); await page - .getByText('Mountain Daylight Time (MDT) UTC-06:00') + .getByText('Greenwich Mean Time (GMT)') .locator('visible=true') .click(); From c97a377dc091d82087f970c1ae01091a1c59fa42 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Fri, 19 Dec 2025 18:13:01 -0800 Subject: [PATCH 4/6] Add util to get adjusted timeformat --- src/lib/stores/time-format.test.ts | 33 ++++++++++++++++++++++++++++++ src/lib/stores/time-format.ts | 24 +++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/lib/stores/time-format.test.ts b/src/lib/stores/time-format.test.ts index 2bd4fe7056..060691612e 100644 --- a/src/lib/stores/time-format.test.ts +++ b/src/lib/stores/time-format.test.ts @@ -4,6 +4,7 @@ import { describe, expect, test } from 'vitest'; import { formatOffset, + getAdjustedTimeformat, getTimezone, getUTCOffset, relativeTime, @@ -65,3 +66,35 @@ describe('getUTCOffset', () => { expect(getUTCOffset('America/Phoenix')).toBe('-07:00'); }); }); + +describe('getAdjustedTimeformat', () => { + test('should replace daylight with standard and vice versa', () => { + expect( + getAdjustedTimeformat('Mountain Daylight Time', { + 'Mountain Standard Time': { abbr: 'MST', offset: -7, zones: [] }, + }), + ).toBe('Mountain Standard Time'); + expect( + getAdjustedTimeformat('Mountain Standard Time', { + 'Mountain Daylight Time': { abbr: 'MDT', offset: -7, zones: [] }, + }), + ).toBe('Mountain Daylight Time'); + }); + + test('should replace standard with summer and vice versa', () => { + expect( + getAdjustedTimeformat('Central European Summer Time', { + 'Central European Standard Time': { + abbr: 'GMT+1', + offset: 1, + zones: [], + }, + }), + ).toBe('Central European Standard Time'); + expect( + getAdjustedTimeformat('Central European Standard Time', { + 'Central European Summer Time': { abbr: 'GMT+2', offset: 2, zones: [] }, + }), + ).toBe('Central European Summer Time'); + }); +}); diff --git a/src/lib/stores/time-format.ts b/src/lib/stores/time-format.ts index 95c6907275..eb38aac6aa 100644 --- a/src/lib/stores/time-format.ts +++ b/src/lib/stores/time-format.ts @@ -89,9 +89,31 @@ const getGroupedTimezones = (): { export const Timezones = getGroupedTimezones(); +export const getAdjustedTimeformat = (value: string, timezones = Timezones) => { + const descriptors = ['Summer', 'Standard', 'Daylight']; + const currentDescriptorIndex = descriptors.findIndex((descriptor) => + new RegExp(`\\b${descriptor}\\b`, 'i').test(value), + ); + + if (currentDescriptorIndex === -1) return; + const currentDescriptor = descriptors.splice(currentDescriptorIndex, 1)[0]; + for (let i = 0; i < descriptors.length; i++) { + const adjustedValue = value.replace( + new RegExp(`\\b${currentDescriptor}\\b`, 'i'), + descriptors[i], + ); + if (timezones[adjustedValue]) return adjustedValue; + } +}; + timeFormat.subscribe((value) => { if (Object.values(BASE_TIME_FORMAT_OPTIONS).includes(value)) return; - if (!Timezones[value]) timeFormat.set(DEFAULT_TIME_FORMAT); + if (!Timezones[value]) { + const adjustedTimeformat = getAdjustedTimeformat(value); + timeFormat.set( + adjustedTimeformat ? adjustedTimeformat : DEFAULT_TIME_FORMAT, + ); + } }); export const TimezoneOptions: TimeFormatOptions = Object.entries(Timezones) From c00006174d91d08723c2ccef752d7510d8f99fb4 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Mon, 22 Dec 2025 07:39:46 -0800 Subject: [PATCH 5/6] Fix strict errors --- src/lib/components/timezone-select.svelte | 8 ++-- src/lib/stores/time-format.ts | 52 ++++++++++++----------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/lib/components/timezone-select.svelte b/src/lib/components/timezone-select.svelte index 07067f885d..bbdee5d228 100644 --- a/src/lib/components/timezone-select.svelte +++ b/src/lib/components/timezone-select.svelte @@ -80,7 +80,7 @@ $timestampFormat = format; }; - $: timezone = Timezones[$timeFormat]?.abbr ?? $timeFormat; + $: timezone = Timezones[$timeFormat ?? '']?.abbr ?? $timeFormat; openUnsubscriber = open.subscribe((isOpen) => { if (isOpen) { @@ -185,9 +185,11 @@ {#each QuickTimezoneOptions as { value, label }} selectTimezone(value)} - data-testid={`timezones-${value}`} + data-testid="timezones-{value}" selected={value === $timeFormat} - description={value === BASE_TIME_FORMAT_OPTIONS.LOCAL && localTime} + description={value === BASE_TIME_FORMAT_OPTIONS.LOCAL + ? localTime + : undefined} > {label} diff --git a/src/lib/stores/time-format.ts b/src/lib/stores/time-format.ts index eb38aac6aa..05091c9a2c 100644 --- a/src/lib/stores/time-format.ts +++ b/src/lib/stores/time-format.ts @@ -58,33 +58,36 @@ type TimeFormatOption = { export type TimeFormatOptions = TimeFormatOption[]; -const getGroupedTimezones = (): { - [key: string]: TimezoneInfo; -} => { - return Intl.supportedValuesOf('timeZone').reduce((acc, timeZone) => { - const zonedTime = dateTz.utcToZonedTime(new Date(), timeZone); - const zoneString = dateTz.format(zonedTime, 'zzzz', { - timeZone, - locale: enUS, - }); - if (acc[zoneString]) { - acc[zoneString].zones.push(timeZone); - } else { - const zoneAbbr = dateTz.format(zonedTime, 'z', { +type GroupedTimezones = { [key: string]: TimezoneInfo }; + +const getGroupedTimezones = (): GroupedTimezones => { + return Intl.supportedValuesOf('timeZone').reduce( + (acc: GroupedTimezones, timeZone: string) => { + const zonedTime = dateTz.utcToZonedTime(new Date(), timeZone); + const zoneString = dateTz.format(zonedTime, 'zzzz', { timeZone, locale: enUS, }); - const offset = Math.floor( - (dateTz.getTimezoneOffset(timeZone) / (1000 * 60 * 60)) % 24, - ); - acc[zoneString] = { - abbr: zoneAbbr, - offset, - zones: [timeZone], - }; - } - return acc; - }, {}); + if (acc[zoneString]) { + acc[zoneString].zones.push(timeZone); + } else { + const zoneAbbr = dateTz.format(zonedTime, 'z', { + timeZone, + locale: enUS, + }); + const offset = Math.floor( + (dateTz.getTimezoneOffset(timeZone) / (1000 * 60 * 60)) % 24, + ); + acc[zoneString] = { + abbr: zoneAbbr, + offset, + zones: [timeZone], + }; + } + return acc; + }, + {}, + ); }; export const Timezones = getGroupedTimezones(); @@ -107,6 +110,7 @@ export const getAdjustedTimeformat = (value: string, timezones = Timezones) => { }; timeFormat.subscribe((value) => { + if (value === null) return; if (Object.values(BASE_TIME_FORMAT_OPTIONS).includes(value)) return; if (!Timezones[value]) { const adjustedTimeformat = getAdjustedTimeformat(value); From 4e7e22b74edfe1a48d0a12ebc75b380a623f1ff9 Mon Sep 17 00:00:00 2001 From: Laura Whitaker Date: Wed, 7 Jan 2026 18:09:26 -0700 Subject: [PATCH 6/6] Set custom subscribe method with validation --- src/lib/stores/time-format.ts | 41 +++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/lib/stores/time-format.ts b/src/lib/stores/time-format.ts index 05091c9a2c..42258237ac 100644 --- a/src/lib/stores/time-format.ts +++ b/src/lib/stores/time-format.ts @@ -1,3 +1,5 @@ +import { get, type Subscriber } from 'svelte/store'; + import { startOfDay } from 'date-fns'; import { enUS } from 'date-fns/locale'; import * as dateTz from 'date-fns-tz'; @@ -22,7 +24,7 @@ export const BASE_TIME_FORMAT_OPTIONS = { UTC: 'UTC', }; const DEFAULT_TIME_FORMAT = BASE_TIME_FORMAT_OPTIONS.LOCAL; -export const timeFormat = persistStore('timeFormat', DEFAULT_TIME_FORMAT); +const persistedTimeFormat = persistStore('timeFormat', DEFAULT_TIME_FORMAT); export const timeFormatType = persistStore( 'timeFormatType', 'relative' as TimeFormatTypes, @@ -109,16 +111,33 @@ export const getAdjustedTimeformat = (value: string, timezones = Timezones) => { } }; -timeFormat.subscribe((value) => { - if (value === null) return; - if (Object.values(BASE_TIME_FORMAT_OPTIONS).includes(value)) return; - if (!Timezones[value]) { - const adjustedTimeformat = getAdjustedTimeformat(value); - timeFormat.set( - adjustedTimeformat ? adjustedTimeformat : DEFAULT_TIME_FORMAT, - ); - } -}); +const getValidatedTimeFormat = () => { + const { subscribe, ...rest } = persistedTimeFormat; + let isValidated = false; + + const validate = () => { + if (isValidated) return; + isValidated = true; + + const value = get(persistedTimeFormat); + if (value === null) return; + if (Object.values(BASE_TIME_FORMAT_OPTIONS).includes(value)) return; + if (!Timezones[value]) { + const adjustedTimeformat = getAdjustedTimeformat(value); + persistedTimeFormat.set(adjustedTimeformat || DEFAULT_TIME_FORMAT); + } + }; + + return { + subscribe: (run: Subscriber, invalidate?: () => void) => { + validate(); + return subscribe(run, invalidate); + }, + ...rest, + }; +}; + +export const timeFormat = getValidatedTimeFormat(); export const TimezoneOptions: TimeFormatOptions = Object.entries(Timezones) .map(([key, value]: [string, TimezoneInfo]) => ({