@@ -289,13 +294,14 @@ function DayTimeField({ draft, edit, problems, day, time, label, hint }: DayTime
type TimeZoneFieldProps = {
value: string
+ saved: string
onChange: (zone: string) => void
error: string | undefined
}
// TimeZoneField shows the chosen zone and its current UTC offset; Change
// opens a searchable list of every IANA zone.
-function TimeZoneField({ value, onChange, error }: TimeZoneFieldProps) {
+function TimeZoneField({ value, saved, onChange, error }: TimeZoneFieldProps) {
const [open, setOpen] = useState(false)
const labelId = useId()
const errorId = useId()
@@ -323,6 +329,7 @@ function TimeZoneField({ value, onChange, error }: TimeZoneFieldProps) {
{open && (
setOpen(false)}
onChoose={(zone) => {
onChange(zone)
@@ -336,14 +343,16 @@ function TimeZoneField({ value, onChange, error }: TimeZoneFieldProps) {
type TimeZoneSheetProps = {
value: string
+ // The saved zone, offered even after another is chosen.
+ saved: string
onClose: () => void
onChoose: (zone: string) => void
}
-function TimeZoneSheet({ value, onClose, onChoose }: TimeZoneSheetProps) {
+function TimeZoneSheet({ value, saved, onClose, onChoose }: TimeZoneSheetProps) {
const [search, setSearch] = useState('')
const titleId = useId()
- const options = useMemo(() => timeZoneOptions(value, new Date()), [value])
+ const options = useMemo(() => timeZoneOptions([saved, value], new Date()), [saved, value])
const shown = options.filter((option) => matchesZone(option, search))
return (
diff --git a/web/src/settings/timezones.ts b/web/src/settings/timezones.ts
index 57097d9..a05bfb9 100644
--- a/web/src/settings/timezones.ts
+++ b/web/src/settings/timezones.ts
@@ -18,11 +18,15 @@ export function utcOffset(zone: string, instant: Date): string {
return `UTC${name.slice(3)}`
}
-// timeZoneOptions lists every zone the browser supports, plus current (the
-// saved zone, which may be an alias the list leaves out), sorted by name.
-export function timeZoneOptions(current: string, instant: Date): TimeZoneOption[] {
- const zones = new Set(Intl.supportedValuesOf('timeZone'))
- zones.add(current)
+// Zones the server accepts that Intl.supportedValuesOf('timeZone') leaves
+// out: it lists only canonical zones, and UTC is not one of them.
+const unlistedZones = ['UTC', 'Etc/UTC', 'GMT', 'Etc/GMT']
+
+// timeZoneOptions lists every zone the browser supports, the unlisted UTC
+// zones, and kept (the saved and chosen zones, which may be aliases the
+// list leaves out), sorted by name.
+export function timeZoneOptions(kept: string[], instant: Date): TimeZoneOption[] {
+ const zones = new Set([...Intl.supportedValuesOf('timeZone'), ...unlistedZones, ...kept])
return [...zones].sort().map((zone) => ({ zone, offset: utcOffset(zone, instant) }))
}