浏览代码

fix(timezone): make sure legacy time zones are no longer used

Debian Trixie has removed several time zones. This change makes sure only the current IANA time zones are in use.
Frédéric Guillot 7 月之前
父节点
当前提交
b1742168e1

+ 183 - 0
internal/database/migrations.go

@@ -1158,4 +1158,187 @@ var migrations = [...]func(tx *sql.Tx) error{
 		_, err = tx.Exec(sql)
 		return err
 	},
+	// This migration replaces deprecated timezones by their equivalent on Debian Trixie.
+	func(tx *sql.Tx) (err error) {
+		var deprecatedTimeZoneMap = map[string]string{
+			// Africa
+			"Africa/Asmera": "Africa/Asmara",
+
+			// America - Argentina
+			"America/Argentina/ComodRivadavia": "America/Argentina/Catamarca",
+			"America/Buenos_Aires":             "America/Argentina/Buenos_Aires",
+			"America/Catamarca":                "America/Argentina/Catamarca",
+			"America/Cordoba":                  "America/Argentina/Cordoba",
+			"America/Jujuy":                    "America/Argentina/Jujuy",
+			"America/Mendoza":                  "America/Argentina/Mendoza",
+			"America/Rosario":                  "America/Argentina/Cordoba",
+
+			// America - US
+			"America/Fort_Wayne":   "America/Indiana/Indianapolis",
+			"America/Indianapolis": "America/Indiana/Indianapolis",
+			"America/Knox_IN":      "America/Indiana/Knox",
+			"America/Louisville":   "America/Kentucky/Louisville",
+
+			// America - Greenland
+			"America/Godthab": "America/Nuuk",
+
+			// Antarctica
+			"Antarctica/South_Pole": "Pacific/Auckland",
+
+			// Asia
+			"Asia/Ashkhabad":     "Asia/Ashgabat",
+			"Asia/Calcutta":      "Asia/Kolkata",
+			"Asia/Choibalsan":    "Asia/Ulaanbaatar",
+			"Asia/Chungking":     "Asia/Chongqing",
+			"Asia/Dacca":         "Asia/Dhaka",
+			"Asia/Katmandu":      "Asia/Kathmandu",
+			"Asia/Macao":         "Asia/Macau",
+			"Asia/Rangoon":       "Asia/Yangon",
+			"Asia/Saigon":        "Asia/Ho_Chi_Minh",
+			"Asia/Thimbu":        "Asia/Thimphu",
+			"Asia/Ujung_Pandang": "Asia/Makassar",
+			"Asia/Ulan_Bator":    "Asia/Ulaanbaatar",
+
+			// Atlantic
+			"Atlantic/Faeroe": "Atlantic/Faroe",
+
+			// Australia
+			"Australia/ACT":        "Australia/Sydney",
+			"Australia/LHI":        "Australia/Lord_Howe",
+			"Australia/North":      "Australia/Darwin",
+			"Australia/NSW":        "Australia/Sydney",
+			"Australia/Queensland": "Australia/Brisbane",
+			"Australia/South":      "Australia/Adelaide",
+			"Australia/Tasmania":   "Australia/Hobart",
+			"Australia/Victoria":   "Australia/Melbourne",
+			"Australia/West":       "Australia/Perth",
+
+			// Brazil
+			"Brazil/Acre":      "America/Rio_Branco",
+			"Brazil/DeNoronha": "America/Noronha",
+			"Brazil/East":      "America/Sao_Paulo",
+			"Brazil/West":      "America/Manaus",
+
+			// Canada
+			"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",
+
+			// Europe
+			"CET":               "Europe/Paris",
+			"EET":               "Europe/Sofia",
+			"Europe/Kiev":       "Europe/Kyiv",
+			"Europe/Uzhgorod":   "Europe/Kyiv",
+			"Europe/Zaporozhye": "Europe/Kyiv",
+			"MET":               "Europe/Paris",
+			"WET":               "Europe/Lisbon",
+
+			// Chile
+			"Chile/Continental":  "America/Santiago",
+			"Chile/EasterIsland": "Pacific/Easter",
+
+			// Fixed offset and generic zones
+			"CST6CDT": "America/Chicago",
+			"EST":     "America/New_York",
+			"EST5EDT": "America/New_York",
+			"HST":     "Pacific/Honolulu",
+			"MST":     "America/Denver",
+			"MST7MDT": "America/Denver",
+			"PST8PDT": "America/Los_Angeles",
+
+			// Countries/Regions
+			"Cuba":      "America/Havana",
+			"Egypt":     "Africa/Cairo",
+			"Eire":      "Europe/Dublin",
+			"GB":        "Europe/London",
+			"GB-Eire":   "Europe/London",
+			"Hongkong":  "Asia/Hong_Kong",
+			"Iceland":   "Atlantic/Reykjavik",
+			"Iran":      "Asia/Tehran",
+			"Israel":    "Asia/Jerusalem",
+			"Jamaica":   "America/Jamaica",
+			"Japan":     "Asia/Tokyo",
+			"Libya":     "Africa/Tripoli",
+			"Poland":    "Europe/Warsaw",
+			"Portugal":  "Europe/Lisbon",
+			"PRC":       "Asia/Shanghai",
+			"ROC":       "Asia/Taipei",
+			"ROK":       "Asia/Seoul",
+			"Singapore": "Asia/Singapore",
+			"Turkey":    "Europe/Istanbul",
+
+			// GMT variations
+			"GMT+0":     "GMT",
+			"GMT-0":     "GMT",
+			"GMT0":      "GMT",
+			"Greenwich": "GMT",
+			"UCT":       "UTC",
+			"Universal": "UTC",
+			"Zulu":      "UTC",
+
+			// Mexico
+			"Mexico/BajaNorte": "America/Tijuana",
+			"Mexico/BajaSur":   "America/Mazatlan",
+			"Mexico/General":   "America/Mexico_City",
+
+			// US zones
+			"Navajo":            "America/Denver",
+			"US/Alaska":         "America/Anchorage",
+			"US/Aleutian":       "America/Adak",
+			"US/Arizona":        "America/Phoenix",
+			"US/Central":        "America/Chicago",
+			"US/Eastern":        "America/New_York",
+			"US/East-Indiana":   "America/Indiana/Indianapolis",
+			"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",
+
+			// Pacific
+			"Kwajalein":         "Pacific/Kwajalein",
+			"NZ":                "Pacific/Auckland",
+			"NZ-CHAT":           "Pacific/Chatham",
+			"Pacific/Enderbury": "Pacific/Kanton",
+			"Pacific/Ponape":    "Pacific/Pohnpei",
+			"Pacific/Truk":      "Pacific/Chuuk",
+
+			// Special cases
+			"Factory": "UTC", // Factory is used for unconfigured systems
+			"W-SU":    "Europe/Moscow",
+		}
+
+		// Loop through each user and correct the timezone
+		rows, err := tx.Query(`SELECT id, timezone FROM users`)
+		if err != nil {
+			return err
+		}
+
+		userTimezoneMap := make(map[int64]string)
+		for rows.Next() {
+			var userID int64
+			var userTimezone string
+			if err := rows.Scan(&userID, &userTimezone); err != nil {
+				return err
+			}
+			userTimezoneMap[userID] = userTimezone
+		}
+		rows.Close()
+
+		for userID, userTimezone := range userTimezoneMap {
+			if newTimezone, found := deprecatedTimeZoneMap[userTimezone]; found {
+				if _, err := tx.Exec(`UPDATE users SET timezone = $1 WHERE id = $2`, newTimezone, userID); err != nil {
+					return err
+				}
+			}
+		}
+
+		return nil
+	},
 }

+ 0 - 32
internal/storage/timezone.go

@@ -1,32 +0,0 @@
-// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
-// SPDX-License-Identifier: Apache-2.0
-
-package storage // import "miniflux.app/v2/internal/storage"
-
-import (
-	"fmt"
-	"strings"
-)
-
-// Timezones returns all timezones supported by the database.
-func (s *Storage) Timezones() (map[string]string, error) {
-	timezones := make(map[string]string)
-	rows, err := s.db.Query(`SELECT name FROM pg_timezone_names ORDER BY name ASC`)
-	if err != nil {
-		return nil, fmt.Errorf(`store: unable to fetch timezones: %v`, err)
-	}
-	defer rows.Close()
-
-	for rows.Next() {
-		var timezone string
-		if err := rows.Scan(&timezone); err != nil {
-			return nil, fmt.Errorf(`store: unable to fetch timezones row: %v`, err)
-		}
-
-		if !strings.HasPrefix(timezone, "posix") && !strings.HasPrefix(timezone, "SystemV") && timezone != "localtime" {
-			timezones[timezone] = timezone
-		}
-	}
-
-	return timezones, nil
-}

+ 461 - 0
internal/timezone/timezone.go

@@ -59,3 +59,464 @@ func getLocation(tz string) *time.Location {
 	tzCache.Store(tz, loc)
 	return loc
 }
+
+// AvailableTimezones returns a map of supported timezones.
+// This list is taken from Postgres on Debian Trixie.
+func AvailableTimezones() map[string]string {
+	timezones := []string{
+		"Africa/Abidjan",
+		"Africa/Accra",
+		"Africa/Addis_Ababa",
+		"Africa/Algiers",
+		"Africa/Asmara",
+		"Africa/Bamako",
+		"Africa/Bangui",
+		"Africa/Banjul",
+		"Africa/Bissau",
+		"Africa/Blantyre",
+		"Africa/Brazzaville",
+		"Africa/Bujumbura",
+		"Africa/Cairo",
+		"Africa/Casablanca",
+		"Africa/Ceuta",
+		"Africa/Conakry",
+		"Africa/Dakar",
+		"Africa/Dar_es_Salaam",
+		"Africa/Djibouti",
+		"Africa/Douala",
+		"Africa/El_Aaiun",
+		"Africa/Freetown",
+		"Africa/Gaborone",
+		"Africa/Harare",
+		"Africa/Johannesburg",
+		"Africa/Juba",
+		"Africa/Kampala",
+		"Africa/Khartoum",
+		"Africa/Kigali",
+		"Africa/Kinshasa",
+		"Africa/Lagos",
+		"Africa/Libreville",
+		"Africa/Lome",
+		"Africa/Luanda",
+		"Africa/Lubumbashi",
+		"Africa/Lusaka",
+		"Africa/Malabo",
+		"Africa/Maputo",
+		"Africa/Maseru",
+		"Africa/Mbabane",
+		"Africa/Mogadishu",
+		"Africa/Monrovia",
+		"Africa/Nairobi",
+		"Africa/Ndjamena",
+		"Africa/Niamey",
+		"Africa/Nouakchott",
+		"Africa/Ouagadougou",
+		"Africa/Porto-Novo",
+		"Africa/Sao_Tome",
+		"Africa/Timbuktu",
+		"Africa/Tripoli",
+		"Africa/Tunis",
+		"Africa/Windhoek",
+		"America/Adak",
+		"America/Anchorage",
+		"America/Anguilla",
+		"America/Antigua",
+		"America/Araguaina",
+		"America/Argentina/Buenos_Aires",
+		"America/Argentina/Catamarca",
+		"America/Argentina/Cordoba",
+		"America/Argentina/Jujuy",
+		"America/Argentina/La_Rioja",
+		"America/Argentina/Mendoza",
+		"America/Argentina/Rio_Gallegos",
+		"America/Argentina/Salta",
+		"America/Argentina/San_Juan",
+		"America/Argentina/San_Luis",
+		"America/Argentina/Tucuman",
+		"America/Argentina/Ushuaia",
+		"America/Aruba",
+		"America/Asuncion",
+		"America/Atikokan",
+		"America/Atka",
+		"America/Bahia",
+		"America/Bahia_Banderas",
+		"America/Barbados",
+		"America/Belem",
+		"America/Belize",
+		"America/Blanc-Sablon",
+		"America/Boa_Vista",
+		"America/Bogota",
+		"America/Boise",
+		"America/Cambridge_Bay",
+		"America/Campo_Grande",
+		"America/Cancun",
+		"America/Caracas",
+		"America/Cayenne",
+		"America/Cayman",
+		"America/Chicago",
+		"America/Chihuahua",
+		"America/Ciudad_Juarez",
+		"America/Coral_Harbour",
+		"America/Costa_Rica",
+		"America/Coyhaique",
+		"America/Creston",
+		"America/Cuiaba",
+		"America/Curacao",
+		"America/Danmarkshavn",
+		"America/Dawson",
+		"America/Dawson_Creek",
+		"America/Denver",
+		"America/Detroit",
+		"America/Dominica",
+		"America/Edmonton",
+		"America/Eirunepe",
+		"America/El_Salvador",
+		"America/Ensenada",
+		"America/Fortaleza",
+		"America/Fort_Nelson",
+		"America/Glace_Bay",
+		"America/Goose_Bay",
+		"America/Grand_Turk",
+		"America/Grenada",
+		"America/Guadeloupe",
+		"America/Guatemala",
+		"America/Guayaquil",
+		"America/Guyana",
+		"America/Halifax",
+		"America/Havana",
+		"America/Hermosillo",
+		"America/Indiana/Indianapolis",
+		"America/Indiana/Knox",
+		"America/Indiana/Marengo",
+		"America/Indiana/Petersburg",
+		"America/Indiana/Tell_City",
+		"America/Indiana/Vevay",
+		"America/Indiana/Vincennes",
+		"America/Indiana/Winamac",
+		"America/Inuvik",
+		"America/Iqaluit",
+		"America/Jamaica",
+		"America/Juneau",
+		"America/Kentucky/Louisville",
+		"America/Kentucky/Monticello",
+		"America/Kralendijk",
+		"America/La_Paz",
+		"America/Lima",
+		"America/Los_Angeles",
+		"America/Lower_Princes",
+		"America/Maceio",
+		"America/Managua",
+		"America/Manaus",
+		"America/Marigot",
+		"America/Martinique",
+		"America/Matamoros",
+		"America/Mazatlan",
+		"America/Menominee",
+		"America/Merida",
+		"America/Metlakatla",
+		"America/Mexico_City",
+		"America/Miquelon",
+		"America/Moncton",
+		"America/Monterrey",
+		"America/Montevideo",
+		"America/Montreal",
+		"America/Montserrat",
+		"America/Nassau",
+		"America/New_York",
+		"America/Nipigon",
+		"America/Nome",
+		"America/Noronha",
+		"America/North_Dakota/Beulah",
+		"America/North_Dakota/Center",
+		"America/North_Dakota/New_Salem",
+		"America/Nuuk",
+		"America/Ojinaga",
+		"America/Panama",
+		"America/Pangnirtung",
+		"America/Paramaribo",
+		"America/Phoenix",
+		"America/Port-au-Prince",
+		"America/Porto_Acre",
+		"America/Port_of_Spain",
+		"America/Porto_Velho",
+		"America/Puerto_Rico",
+		"America/Punta_Arenas",
+		"America/Rainy_River",
+		"America/Rankin_Inlet",
+		"America/Recife",
+		"America/Regina",
+		"America/Resolute",
+		"America/Rio_Branco",
+		"America/Santa_Isabel",
+		"America/Santarem",
+		"America/Santiago",
+		"America/Santo_Domingo",
+		"America/Sao_Paulo",
+		"America/Scoresbysund",
+		"America/Shiprock",
+		"America/Sitka",
+		"America/St_Barthelemy",
+		"America/St_Johns",
+		"America/St_Kitts",
+		"America/St_Lucia",
+		"America/St_Thomas",
+		"America/St_Vincent",
+		"America/Swift_Current",
+		"America/Tegucigalpa",
+		"America/Thule",
+		"America/Thunder_Bay",
+		"America/Tijuana",
+		"America/Toronto",
+		"America/Tortola",
+		"America/Vancouver",
+		"America/Virgin",
+		"America/Whitehorse",
+		"America/Winnipeg",
+		"America/Yakutat",
+		"America/Yellowknife",
+		"Antarctica/Casey",
+		"Antarctica/Davis",
+		"Antarctica/DumontDUrville",
+		"Antarctica/Macquarie",
+		"Antarctica/Mawson",
+		"Antarctica/McMurdo",
+		"Antarctica/Palmer",
+		"Antarctica/Rothera",
+		"Antarctica/Syowa",
+		"Antarctica/Troll",
+		"Antarctica/Vostok",
+		"Arctic/Longyearbyen",
+		"Asia/Aden",
+		"Asia/Almaty",
+		"Asia/Amman",
+		"Asia/Anadyr",
+		"Asia/Aqtau",
+		"Asia/Aqtobe",
+		"Asia/Ashgabat",
+		"Asia/Atyrau",
+		"Asia/Baghdad",
+		"Asia/Bahrain",
+		"Asia/Baku",
+		"Asia/Bangkok",
+		"Asia/Barnaul",
+		"Asia/Beirut",
+		"Asia/Bishkek",
+		"Asia/Brunei",
+		"Asia/Chita",
+		"Asia/Chongqing",
+		"Asia/Colombo",
+		"Asia/Damascus",
+		"Asia/Dhaka",
+		"Asia/Dili",
+		"Asia/Dubai",
+		"Asia/Dushanbe",
+		"Asia/Famagusta",
+		"Asia/Gaza",
+		"Asia/Harbin",
+		"Asia/Hebron",
+		"Asia/Ho_Chi_Minh",
+		"Asia/Hong_Kong",
+		"Asia/Hovd",
+		"Asia/Irkutsk",
+		"Asia/Istanbul",
+		"Asia/Jakarta",
+		"Asia/Jayapura",
+		"Asia/Jerusalem",
+		"Asia/Kabul",
+		"Asia/Kamchatka",
+		"Asia/Karachi",
+		"Asia/Kashgar",
+		"Asia/Kathmandu",
+		"Asia/Khandyga",
+		"Asia/Kolkata",
+		"Asia/Krasnoyarsk",
+		"Asia/Kuala_Lumpur",
+		"Asia/Kuching",
+		"Asia/Kuwait",
+		"Asia/Macau",
+		"Asia/Magadan",
+		"Asia/Makassar",
+		"Asia/Manila",
+		"Asia/Muscat",
+		"Asia/Nicosia",
+		"Asia/Novokuznetsk",
+		"Asia/Novosibirsk",
+		"Asia/Omsk",
+		"Asia/Oral",
+		"Asia/Phnom_Penh",
+		"Asia/Pontianak",
+		"Asia/Pyongyang",
+		"Asia/Qatar",
+		"Asia/Qostanay",
+		"Asia/Qyzylorda",
+		"Asia/Riyadh",
+		"Asia/Sakhalin",
+		"Asia/Samarkand",
+		"Asia/Seoul",
+		"Asia/Shanghai",
+		"Asia/Singapore",
+		"Asia/Srednekolymsk",
+		"Asia/Taipei",
+		"Asia/Tashkent",
+		"Asia/Tbilisi",
+		"Asia/Tehran",
+		"Asia/Tel_Aviv",
+		"Asia/Thimphu",
+		"Asia/Tokyo",
+		"Asia/Tomsk",
+		"Asia/Ulaanbaatar",
+		"Asia/Urumqi",
+		"Asia/Ust-Nera",
+		"Asia/Vientiane",
+		"Asia/Vladivostok",
+		"Asia/Yakutsk",
+		"Asia/Yangon",
+		"Asia/Yekaterinburg",
+		"Asia/Yerevan",
+		"Atlantic/Azores",
+		"Atlantic/Bermuda",
+		"Atlantic/Canary",
+		"Atlantic/Cape_Verde",
+		"Atlantic/Faroe",
+		"Atlantic/Jan_Mayen",
+		"Atlantic/Madeira",
+		"Atlantic/Reykjavik",
+		"Atlantic/South_Georgia",
+		"Atlantic/Stanley",
+		"Atlantic/St_Helena",
+		"Australia/Adelaide",
+		"Australia/Brisbane",
+		"Australia/Broken_Hill",
+		"Australia/Canberra",
+		"Australia/Currie",
+		"Australia/Darwin",
+		"Australia/Eucla",
+		"Australia/Hobart",
+		"Australia/Lindeman",
+		"Australia/Lord_Howe",
+		"Australia/Melbourne",
+		"Australia/Perth",
+		"Australia/Sydney",
+		"Australia/Yancowinna",
+		"Europe/Amsterdam",
+		"Europe/Andorra",
+		"Europe/Astrakhan",
+		"Europe/Athens",
+		"Europe/Belfast",
+		"Europe/Belgrade",
+		"Europe/Berlin",
+		"Europe/Bratislava",
+		"Europe/Brussels",
+		"Europe/Bucharest",
+		"Europe/Budapest",
+		"Europe/Busingen",
+		"Europe/Chisinau",
+		"Europe/Copenhagen",
+		"Europe/Dublin",
+		"Europe/Gibraltar",
+		"Europe/Guernsey",
+		"Europe/Helsinki",
+		"Europe/Isle_of_Man",
+		"Europe/Istanbul",
+		"Europe/Jersey",
+		"Europe/Kaliningrad",
+		"Europe/Kirov",
+		"Europe/Kyiv",
+		"Europe/Lisbon",
+		"Europe/Ljubljana",
+		"Europe/London",
+		"Europe/Luxembourg",
+		"Europe/Madrid",
+		"Europe/Malta",
+		"Europe/Mariehamn",
+		"Europe/Minsk",
+		"Europe/Monaco",
+		"Europe/Moscow",
+		"Europe/Nicosia",
+		"Europe/Oslo",
+		"Europe/Paris",
+		"Europe/Podgorica",
+		"Europe/Prague",
+		"Europe/Riga",
+		"Europe/Rome",
+		"Europe/Samara",
+		"Europe/San_Marino",
+		"Europe/Sarajevo",
+		"Europe/Saratov",
+		"Europe/Simferopol",
+		"Europe/Skopje",
+		"Europe/Sofia",
+		"Europe/Stockholm",
+		"Europe/Tallinn",
+		"Europe/Tirane",
+		"Europe/Tiraspol",
+		"Europe/Ulyanovsk",
+		"Europe/Vaduz",
+		"Europe/Vatican",
+		"Europe/Vienna",
+		"Europe/Vilnius",
+		"Europe/Volgograd",
+		"Europe/Warsaw",
+		"Europe/Zagreb",
+		"Europe/Zurich",
+		"GMT",
+		"Indian/Antananarivo",
+		"Indian/Chagos",
+		"Indian/Christmas",
+		"Indian/Cocos",
+		"Indian/Comoro",
+		"Indian/Kerguelen",
+		"Indian/Mahe",
+		"Indian/Maldives",
+		"Indian/Mauritius",
+		"Indian/Mayotte",
+		"Indian/Reunion",
+		"Pacific/Apia",
+		"Pacific/Auckland",
+		"Pacific/Bougainville",
+		"Pacific/Chatham",
+		"Pacific/Chuuk",
+		"Pacific/Easter",
+		"Pacific/Efate",
+		"Pacific/Fakaofo",
+		"Pacific/Fiji",
+		"Pacific/Funafuti",
+		"Pacific/Galapagos",
+		"Pacific/Gambier",
+		"Pacific/Guadalcanal",
+		"Pacific/Guam",
+		"Pacific/Honolulu",
+		"Pacific/Johnston",
+		"Pacific/Kanton",
+		"Pacific/Kiritimati",
+		"Pacific/Kosrae",
+		"Pacific/Kwajalein",
+		"Pacific/Majuro",
+		"Pacific/Marquesas",
+		"Pacific/Midway",
+		"Pacific/Nauru",
+		"Pacific/Niue",
+		"Pacific/Norfolk",
+		"Pacific/Noumea",
+		"Pacific/Pago_Pago",
+		"Pacific/Palau",
+		"Pacific/Pitcairn",
+		"Pacific/Pohnpei",
+		"Pacific/Port_Moresby",
+		"Pacific/Rarotonga",
+		"Pacific/Saipan",
+		"Pacific/Samoa",
+		"Pacific/Tahiti",
+		"Pacific/Tarawa",
+		"Pacific/Tongatapu",
+		"Pacific/Wake",
+		"Pacific/Wallis",
+		"Pacific/Yap",
+		"UTC",
+	}
+	availableTimezones := make(map[string]string, len(timezones))
+	for _, tz := range timezones {
+		availableTimezones[tz] = tz
+	}
+	return availableTimezones
+}

+ 2 - 7
internal/ui/settings_show.go

@@ -10,6 +10,7 @@ import (
 	"miniflux.app/v2/internal/http/response/html"
 	"miniflux.app/v2/internal/locale"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/timezone"
 	"miniflux.app/v2/internal/ui/form"
 	"miniflux.app/v2/internal/ui/session"
 	"miniflux.app/v2/internal/ui/view"
@@ -50,12 +51,6 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
 		OpenExternalLinksInNewTab: user.OpenExternalLinksInNewTab,
 	}
 
-	timezones, err := h.store.Timezones()
-	if err != nil {
-		html.ServerError(w, r, err)
-		return
-	}
-
 	creds, err := h.store.WebAuthnCredentialsByUserID(user.ID)
 	if err != nil {
 		html.ServerError(w, r, err)
@@ -73,7 +68,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
 	})
 	view.Set("themes", model.Themes())
 	view.Set("languages", locale.AvailableLanguages)
-	view.Set("timezones", timezones)
+	view.Set("timezones", timezone.AvailableTimezones())
 	view.Set("menu", "settings")
 	view.Set("user", user)
 	view.Set("countUnread", h.store.CountUnreadEntries(user.ID))

+ 2 - 7
internal/ui/settings_update.go

@@ -11,6 +11,7 @@ import (
 	"miniflux.app/v2/internal/http/route"
 	"miniflux.app/v2/internal/locale"
 	"miniflux.app/v2/internal/model"
+	"miniflux.app/v2/internal/timezone"
 	"miniflux.app/v2/internal/ui/form"
 	"miniflux.app/v2/internal/ui/session"
 	"miniflux.app/v2/internal/ui/view"
@@ -24,12 +25,6 @@ func (h *handler) updateSettings(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	timezones, err := h.store.Timezones()
-	if err != nil {
-		html.ServerError(w, r, err)
-		return
-	}
-
 	creds, err := h.store.WebAuthnCredentialsByUserID(user.ID)
 	if err != nil {
 		html.ServerError(w, r, err)
@@ -49,7 +44,7 @@ func (h *handler) updateSettings(w http.ResponseWriter, r *http.Request) {
 	})
 	view.Set("themes", model.Themes())
 	view.Set("languages", locale.AvailableLanguages)
-	view.Set("timezones", timezones)
+	view.Set("timezones", timezone.AvailableTimezones())
 	view.Set("menu", "settings")
 	view.Set("user", user)
 	view.Set("countUnread", h.store.CountUnreadEntries(user.ID))

+ 4 - 8
internal/validator/user.go

@@ -11,6 +11,7 @@ import (
 	"miniflux.app/v2/internal/locale"
 	"miniflux.app/v2/internal/model"
 	"miniflux.app/v2/internal/storage"
+	"miniflux.app/v2/internal/timezone"
 )
 
 // ValidateUserCreationWithPassword validates user creation with a password.
@@ -63,7 +64,7 @@ func ValidateUserModification(store *storage.Storage, userID int64, changes *mod
 	}
 
 	if changes.Timezone != nil {
-		if err := validateTimezone(store, *changes.Timezone); err != nil {
+		if err := validateTimezone(*changes.Timezone); err != nil {
 			return err
 		}
 	}
@@ -200,13 +201,8 @@ func validateLanguage(language string) *locale.LocalizedError {
 	return nil
 }
 
-func validateTimezone(store *storage.Storage, timezone string) *locale.LocalizedError {
-	timezones, err := store.Timezones()
-	if err != nil {
-		return locale.NewLocalizedError(err.Error())
-	}
-
-	if _, found := timezones[timezone]; !found {
+func validateTimezone(timezoneValue string) *locale.LocalizedError {
+	if _, found := timezone.AvailableTimezones()[timezoneValue]; !found {
 		return locale.NewLocalizedError("error.invalid_timezone")
 	}
 	return nil