timezone.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package timezone // import "miniflux.app/v2/internal/timezone"
  4. import (
  5. "iter"
  6. "slices"
  7. "sync"
  8. "time"
  9. )
  10. var (
  11. tzCache = sync.Map{} // Cache for time locations to avoid loading them multiple times.
  12. timezones = []string{ // This list is taken from Postgres on Debian Trixie.
  13. "Africa/Abidjan",
  14. "Africa/Accra",
  15. "Africa/Addis_Ababa",
  16. "Africa/Algiers",
  17. "Africa/Asmara",
  18. "Africa/Bamako",
  19. "Africa/Bangui",
  20. "Africa/Banjul",
  21. "Africa/Bissau",
  22. "Africa/Blantyre",
  23. "Africa/Brazzaville",
  24. "Africa/Bujumbura",
  25. "Africa/Cairo",
  26. "Africa/Casablanca",
  27. "Africa/Ceuta",
  28. "Africa/Conakry",
  29. "Africa/Dakar",
  30. "Africa/Dar_es_Salaam",
  31. "Africa/Djibouti",
  32. "Africa/Douala",
  33. "Africa/El_Aaiun",
  34. "Africa/Freetown",
  35. "Africa/Gaborone",
  36. "Africa/Harare",
  37. "Africa/Johannesburg",
  38. "Africa/Juba",
  39. "Africa/Kampala",
  40. "Africa/Khartoum",
  41. "Africa/Kigali",
  42. "Africa/Kinshasa",
  43. "Africa/Lagos",
  44. "Africa/Libreville",
  45. "Africa/Lome",
  46. "Africa/Luanda",
  47. "Africa/Lubumbashi",
  48. "Africa/Lusaka",
  49. "Africa/Malabo",
  50. "Africa/Maputo",
  51. "Africa/Maseru",
  52. "Africa/Mbabane",
  53. "Africa/Mogadishu",
  54. "Africa/Monrovia",
  55. "Africa/Nairobi",
  56. "Africa/Ndjamena",
  57. "Africa/Niamey",
  58. "Africa/Nouakchott",
  59. "Africa/Ouagadougou",
  60. "Africa/Porto-Novo",
  61. "Africa/Sao_Tome",
  62. "Africa/Timbuktu",
  63. "Africa/Tripoli",
  64. "Africa/Tunis",
  65. "Africa/Windhoek",
  66. "America/Adak",
  67. "America/Anchorage",
  68. "America/Anguilla",
  69. "America/Antigua",
  70. "America/Araguaina",
  71. "America/Argentina/Buenos_Aires",
  72. "America/Argentina/Catamarca",
  73. "America/Argentina/Cordoba",
  74. "America/Argentina/Jujuy",
  75. "America/Argentina/La_Rioja",
  76. "America/Argentina/Mendoza",
  77. "America/Argentina/Rio_Gallegos",
  78. "America/Argentina/Salta",
  79. "America/Argentina/San_Juan",
  80. "America/Argentina/San_Luis",
  81. "America/Argentina/Tucuman",
  82. "America/Argentina/Ushuaia",
  83. "America/Aruba",
  84. "America/Asuncion",
  85. "America/Atikokan",
  86. "America/Atka",
  87. "America/Bahia",
  88. "America/Bahia_Banderas",
  89. "America/Barbados",
  90. "America/Belem",
  91. "America/Belize",
  92. "America/Blanc-Sablon",
  93. "America/Boa_Vista",
  94. "America/Bogota",
  95. "America/Boise",
  96. "America/Cambridge_Bay",
  97. "America/Campo_Grande",
  98. "America/Cancun",
  99. "America/Caracas",
  100. "America/Cayenne",
  101. "America/Cayman",
  102. "America/Chicago",
  103. "America/Chihuahua",
  104. "America/Ciudad_Juarez",
  105. "America/Coral_Harbour",
  106. "America/Costa_Rica",
  107. "America/Coyhaique",
  108. "America/Creston",
  109. "America/Cuiaba",
  110. "America/Curacao",
  111. "America/Danmarkshavn",
  112. "America/Dawson",
  113. "America/Dawson_Creek",
  114. "America/Denver",
  115. "America/Detroit",
  116. "America/Dominica",
  117. "America/Edmonton",
  118. "America/Eirunepe",
  119. "America/El_Salvador",
  120. "America/Ensenada",
  121. "America/Fortaleza",
  122. "America/Fort_Nelson",
  123. "America/Glace_Bay",
  124. "America/Goose_Bay",
  125. "America/Grand_Turk",
  126. "America/Grenada",
  127. "America/Guadeloupe",
  128. "America/Guatemala",
  129. "America/Guayaquil",
  130. "America/Guyana",
  131. "America/Halifax",
  132. "America/Havana",
  133. "America/Hermosillo",
  134. "America/Indiana/Indianapolis",
  135. "America/Indiana/Knox",
  136. "America/Indiana/Marengo",
  137. "America/Indiana/Petersburg",
  138. "America/Indiana/Tell_City",
  139. "America/Indiana/Vevay",
  140. "America/Indiana/Vincennes",
  141. "America/Indiana/Winamac",
  142. "America/Inuvik",
  143. "America/Iqaluit",
  144. "America/Jamaica",
  145. "America/Juneau",
  146. "America/Kentucky/Louisville",
  147. "America/Kentucky/Monticello",
  148. "America/Kralendijk",
  149. "America/La_Paz",
  150. "America/Lima",
  151. "America/Los_Angeles",
  152. "America/Lower_Princes",
  153. "America/Maceio",
  154. "America/Managua",
  155. "America/Manaus",
  156. "America/Marigot",
  157. "America/Martinique",
  158. "America/Matamoros",
  159. "America/Mazatlan",
  160. "America/Menominee",
  161. "America/Merida",
  162. "America/Metlakatla",
  163. "America/Mexico_City",
  164. "America/Miquelon",
  165. "America/Moncton",
  166. "America/Monterrey",
  167. "America/Montevideo",
  168. "America/Montreal",
  169. "America/Montserrat",
  170. "America/Nassau",
  171. "America/New_York",
  172. "America/Nipigon",
  173. "America/Nome",
  174. "America/Noronha",
  175. "America/North_Dakota/Beulah",
  176. "America/North_Dakota/Center",
  177. "America/North_Dakota/New_Salem",
  178. "America/Nuuk",
  179. "America/Ojinaga",
  180. "America/Panama",
  181. "America/Pangnirtung",
  182. "America/Paramaribo",
  183. "America/Phoenix",
  184. "America/Port-au-Prince",
  185. "America/Porto_Acre",
  186. "America/Port_of_Spain",
  187. "America/Porto_Velho",
  188. "America/Puerto_Rico",
  189. "America/Punta_Arenas",
  190. "America/Rainy_River",
  191. "America/Rankin_Inlet",
  192. "America/Recife",
  193. "America/Regina",
  194. "America/Resolute",
  195. "America/Rio_Branco",
  196. "America/Santa_Isabel",
  197. "America/Santarem",
  198. "America/Santiago",
  199. "America/Santo_Domingo",
  200. "America/Sao_Paulo",
  201. "America/Scoresbysund",
  202. "America/Shiprock",
  203. "America/Sitka",
  204. "America/St_Barthelemy",
  205. "America/St_Johns",
  206. "America/St_Kitts",
  207. "America/St_Lucia",
  208. "America/St_Thomas",
  209. "America/St_Vincent",
  210. "America/Swift_Current",
  211. "America/Tegucigalpa",
  212. "America/Thule",
  213. "America/Thunder_Bay",
  214. "America/Tijuana",
  215. "America/Toronto",
  216. "America/Tortola",
  217. "America/Vancouver",
  218. "America/Virgin",
  219. "America/Whitehorse",
  220. "America/Winnipeg",
  221. "America/Yakutat",
  222. "America/Yellowknife",
  223. "Antarctica/Casey",
  224. "Antarctica/Davis",
  225. "Antarctica/DumontDUrville",
  226. "Antarctica/Macquarie",
  227. "Antarctica/Mawson",
  228. "Antarctica/McMurdo",
  229. "Antarctica/Palmer",
  230. "Antarctica/Rothera",
  231. "Antarctica/Syowa",
  232. "Antarctica/Troll",
  233. "Antarctica/Vostok",
  234. "Arctic/Longyearbyen",
  235. "Asia/Aden",
  236. "Asia/Almaty",
  237. "Asia/Amman",
  238. "Asia/Anadyr",
  239. "Asia/Aqtau",
  240. "Asia/Aqtobe",
  241. "Asia/Ashgabat",
  242. "Asia/Atyrau",
  243. "Asia/Baghdad",
  244. "Asia/Bahrain",
  245. "Asia/Baku",
  246. "Asia/Bangkok",
  247. "Asia/Barnaul",
  248. "Asia/Beirut",
  249. "Asia/Bishkek",
  250. "Asia/Brunei",
  251. "Asia/Chita",
  252. "Asia/Chongqing",
  253. "Asia/Colombo",
  254. "Asia/Damascus",
  255. "Asia/Dhaka",
  256. "Asia/Dili",
  257. "Asia/Dubai",
  258. "Asia/Dushanbe",
  259. "Asia/Famagusta",
  260. "Asia/Gaza",
  261. "Asia/Harbin",
  262. "Asia/Hebron",
  263. "Asia/Ho_Chi_Minh",
  264. "Asia/Hong_Kong",
  265. "Asia/Hovd",
  266. "Asia/Irkutsk",
  267. "Asia/Istanbul",
  268. "Asia/Jakarta",
  269. "Asia/Jayapura",
  270. "Asia/Jerusalem",
  271. "Asia/Kabul",
  272. "Asia/Kamchatka",
  273. "Asia/Karachi",
  274. "Asia/Kashgar",
  275. "Asia/Kathmandu",
  276. "Asia/Khandyga",
  277. "Asia/Kolkata",
  278. "Asia/Krasnoyarsk",
  279. "Asia/Kuala_Lumpur",
  280. "Asia/Kuching",
  281. "Asia/Kuwait",
  282. "Asia/Macau",
  283. "Asia/Magadan",
  284. "Asia/Makassar",
  285. "Asia/Manila",
  286. "Asia/Muscat",
  287. "Asia/Nicosia",
  288. "Asia/Novokuznetsk",
  289. "Asia/Novosibirsk",
  290. "Asia/Omsk",
  291. "Asia/Oral",
  292. "Asia/Phnom_Penh",
  293. "Asia/Pontianak",
  294. "Asia/Pyongyang",
  295. "Asia/Qatar",
  296. "Asia/Qostanay",
  297. "Asia/Qyzylorda",
  298. "Asia/Riyadh",
  299. "Asia/Sakhalin",
  300. "Asia/Samarkand",
  301. "Asia/Seoul",
  302. "Asia/Shanghai",
  303. "Asia/Singapore",
  304. "Asia/Srednekolymsk",
  305. "Asia/Taipei",
  306. "Asia/Tashkent",
  307. "Asia/Tbilisi",
  308. "Asia/Tehran",
  309. "Asia/Tel_Aviv",
  310. "Asia/Thimphu",
  311. "Asia/Tokyo",
  312. "Asia/Tomsk",
  313. "Asia/Ulaanbaatar",
  314. "Asia/Urumqi",
  315. "Asia/Ust-Nera",
  316. "Asia/Vientiane",
  317. "Asia/Vladivostok",
  318. "Asia/Yakutsk",
  319. "Asia/Yangon",
  320. "Asia/Yekaterinburg",
  321. "Asia/Yerevan",
  322. "Atlantic/Azores",
  323. "Atlantic/Bermuda",
  324. "Atlantic/Canary",
  325. "Atlantic/Cape_Verde",
  326. "Atlantic/Faroe",
  327. "Atlantic/Jan_Mayen",
  328. "Atlantic/Madeira",
  329. "Atlantic/Reykjavik",
  330. "Atlantic/South_Georgia",
  331. "Atlantic/Stanley",
  332. "Atlantic/St_Helena",
  333. "Australia/Adelaide",
  334. "Australia/Brisbane",
  335. "Australia/Broken_Hill",
  336. "Australia/Canberra",
  337. "Australia/Currie",
  338. "Australia/Darwin",
  339. "Australia/Eucla",
  340. "Australia/Hobart",
  341. "Australia/Lindeman",
  342. "Australia/Lord_Howe",
  343. "Australia/Melbourne",
  344. "Australia/Perth",
  345. "Australia/Sydney",
  346. "Australia/Yancowinna",
  347. "Europe/Amsterdam",
  348. "Europe/Andorra",
  349. "Europe/Astrakhan",
  350. "Europe/Athens",
  351. "Europe/Belfast",
  352. "Europe/Belgrade",
  353. "Europe/Berlin",
  354. "Europe/Bratislava",
  355. "Europe/Brussels",
  356. "Europe/Bucharest",
  357. "Europe/Budapest",
  358. "Europe/Busingen",
  359. "Europe/Chisinau",
  360. "Europe/Copenhagen",
  361. "Europe/Dublin",
  362. "Europe/Gibraltar",
  363. "Europe/Guernsey",
  364. "Europe/Helsinki",
  365. "Europe/Isle_of_Man",
  366. "Europe/Istanbul",
  367. "Europe/Jersey",
  368. "Europe/Kaliningrad",
  369. "Europe/Kirov",
  370. "Europe/Kyiv",
  371. "Europe/Lisbon",
  372. "Europe/Ljubljana",
  373. "Europe/London",
  374. "Europe/Luxembourg",
  375. "Europe/Madrid",
  376. "Europe/Malta",
  377. "Europe/Mariehamn",
  378. "Europe/Minsk",
  379. "Europe/Monaco",
  380. "Europe/Moscow",
  381. "Europe/Nicosia",
  382. "Europe/Oslo",
  383. "Europe/Paris",
  384. "Europe/Podgorica",
  385. "Europe/Prague",
  386. "Europe/Riga",
  387. "Europe/Rome",
  388. "Europe/Samara",
  389. "Europe/San_Marino",
  390. "Europe/Sarajevo",
  391. "Europe/Saratov",
  392. "Europe/Simferopol",
  393. "Europe/Skopje",
  394. "Europe/Sofia",
  395. "Europe/Stockholm",
  396. "Europe/Tallinn",
  397. "Europe/Tirane",
  398. "Europe/Tiraspol",
  399. "Europe/Ulyanovsk",
  400. "Europe/Vaduz",
  401. "Europe/Vatican",
  402. "Europe/Vienna",
  403. "Europe/Vilnius",
  404. "Europe/Volgograd",
  405. "Europe/Warsaw",
  406. "Europe/Zagreb",
  407. "Europe/Zurich",
  408. "GMT",
  409. "Indian/Antananarivo",
  410. "Indian/Chagos",
  411. "Indian/Christmas",
  412. "Indian/Cocos",
  413. "Indian/Comoro",
  414. "Indian/Kerguelen",
  415. "Indian/Mahe",
  416. "Indian/Maldives",
  417. "Indian/Mauritius",
  418. "Indian/Mayotte",
  419. "Indian/Reunion",
  420. "Pacific/Apia",
  421. "Pacific/Auckland",
  422. "Pacific/Bougainville",
  423. "Pacific/Chatham",
  424. "Pacific/Chuuk",
  425. "Pacific/Easter",
  426. "Pacific/Efate",
  427. "Pacific/Fakaofo",
  428. "Pacific/Fiji",
  429. "Pacific/Funafuti",
  430. "Pacific/Galapagos",
  431. "Pacific/Gambier",
  432. "Pacific/Guadalcanal",
  433. "Pacific/Guam",
  434. "Pacific/Honolulu",
  435. "Pacific/Johnston",
  436. "Pacific/Kanton",
  437. "Pacific/Kiritimati",
  438. "Pacific/Kosrae",
  439. "Pacific/Kwajalein",
  440. "Pacific/Majuro",
  441. "Pacific/Marquesas",
  442. "Pacific/Midway",
  443. "Pacific/Nauru",
  444. "Pacific/Niue",
  445. "Pacific/Norfolk",
  446. "Pacific/Noumea",
  447. "Pacific/Pago_Pago",
  448. "Pacific/Palau",
  449. "Pacific/Pitcairn",
  450. "Pacific/Pohnpei",
  451. "Pacific/Port_Moresby",
  452. "Pacific/Rarotonga",
  453. "Pacific/Saipan",
  454. "Pacific/Samoa",
  455. "Pacific/Tahiti",
  456. "Pacific/Tarawa",
  457. "Pacific/Tongatapu",
  458. "Pacific/Wake",
  459. "Pacific/Wallis",
  460. "Pacific/Yap",
  461. "UTC",
  462. }
  463. )
  464. // Convert returns the provided time expressed in the given timezone.
  465. func Convert(tz string, t time.Time) time.Time {
  466. userTimezone := getLocation(tz)
  467. if t.Location().String() == "" {
  468. if t.Before(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)) {
  469. return time.Date(0, time.January, 1, 0, 0, 0, 0, userTimezone)
  470. }
  471. // In this case, the provided date is already converted to the user timezone by Postgres,
  472. // but the timezone information is not set in the time struct.
  473. // We cannot use time.In() because the date will be converted a second time.
  474. return time.Date(
  475. t.Year(),
  476. t.Month(),
  477. t.Day(),
  478. t.Hour(),
  479. t.Minute(),
  480. t.Second(),
  481. t.Nanosecond(),
  482. userTimezone,
  483. )
  484. } else if t.Location().String() != userTimezone.String() {
  485. return t.In(userTimezone)
  486. }
  487. return t
  488. }
  489. // Now returns the current time in the given timezone.
  490. func Now(tz string) time.Time {
  491. return time.Now().In(getLocation(tz))
  492. }
  493. func getLocation(tz string) *time.Location {
  494. if loc, ok := tzCache.Load(tz); ok {
  495. return loc.(*time.Location)
  496. }
  497. loc, err := time.LoadLocation(tz)
  498. if err != nil {
  499. loc = time.Local
  500. }
  501. tzCache.Store(tz, loc)
  502. return loc
  503. }
  504. // IsValid reports whether the timezone string is in the supported list.
  505. func IsValid(timezone string) bool {
  506. _, found := slices.BinarySearch(timezones, timezone)
  507. return found
  508. }
  509. // AvailableTimezones returns an iterator over supported timezone names.
  510. func AvailableTimezones() iter.Seq[string] {
  511. return slices.Values(timezones)
  512. }