// Data ported from sqlglot/time.py (`TIMEZONES`), generated with a Python snippet.
///|
/// Lower-cased IANA time zone names (Python `sqlglot.time.TIMEZONES`).
pub let timezones : @set.Set[String] = @set.Set::from_array([
"africa/abidjan", "africa/accra", "africa/addis_ababa", "africa/algiers", "africa/asmara",
"africa/asmera", "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/comodrivadavia",
"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/buenos_aires", "america/cambridge_bay",
"america/campo_grande", "america/cancun", "america/caracas", "america/catamarca",
"america/cayenne", "america/cayman", "america/chicago", "america/chihuahua", "america/ciudad_juarez",
"america/coral_harbour", "america/cordoba", "america/costa_rica", "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/fort_nelson", "america/fort_wayne", "america/fortaleza", "america/glace_bay",
"america/godthab", "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/indianapolis", "america/inuvik", "america/iqaluit",
"america/jamaica", "america/jujuy", "america/juneau", "america/kentucky/louisville",
"america/kentucky/monticello", "america/knox_in", "america/kralendijk", "america/la_paz",
"america/lima", "america/los_angeles", "america/louisville", "america/lower_princes",
"america/maceio", "america/managua", "america/manaus", "america/marigot", "america/martinique",
"america/matamoros", "america/mazatlan", "america/mendoza", "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/port_of_spain", "america/porto_acre", "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/rosario",
"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/south_pole", "antarctica/syowa",
"antarctica/troll", "antarctica/vostok", "arctic/longyearbyen", "asia/aden", "asia/almaty",
"asia/amman", "asia/anadyr", "asia/aqtau", "asia/aqtobe", "asia/ashgabat", "asia/ashkhabad",
"asia/atyrau", "asia/baghdad", "asia/bahrain", "asia/baku", "asia/bangkok", "asia/barnaul",
"asia/beirut", "asia/bishkek", "asia/brunei", "asia/calcutta", "asia/chita", "asia/choibalsan",
"asia/chongqing", "asia/chungking", "asia/colombo", "asia/dacca", "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/katmandu", "asia/khandyga", "asia/kolkata", "asia/krasnoyarsk", "asia/kuala_lumpur",
"asia/kuching", "asia/kuwait", "asia/macao", "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/rangoon", "asia/riyadh",
"asia/saigon", "asia/sakhalin", "asia/samarkand", "asia/seoul", "asia/shanghai",
"asia/singapore", "asia/srednekolymsk", "asia/taipei", "asia/tashkent", "asia/tbilisi",
"asia/tehran", "asia/tel_aviv", "asia/thimbu", "asia/thimphu", "asia/tokyo", "asia/tomsk",
"asia/ujung_pandang", "asia/ulaanbaatar", "asia/ulan_bator", "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/faeroe", "atlantic/faroe", "atlantic/jan_mayen", "atlantic/madeira",
"atlantic/reykjavik", "atlantic/south_georgia", "atlantic/st_helena", "atlantic/stanley",
"australia/act", "australia/adelaide", "australia/brisbane", "australia/broken_hill",
"australia/canberra", "australia/currie", "australia/darwin", "australia/eucla",
"australia/hobart", "australia/lhi", "australia/lindeman", "australia/lord_howe",
"australia/melbourne", "australia/north", "australia/nsw", "australia/perth", "australia/queensland",
"australia/south", "australia/sydney", "australia/tasmania", "australia/victoria",
"australia/west", "australia/yancowinna", "brazil/acre", "brazil/denoronha", "brazil/east",
"brazil/west", "canada/atlantic", "canada/central", "canada/eastern", "canada/mountain",
"canada/newfoundland", "canada/pacific", "canada/saskatchewan", "canada/yukon",
"cet", "chile/continental", "chile/easterisland", "cst6cdt", "cuba", "eet", "egypt",
"eire", "est", "est5edt", "etc/gmt", "etc/gmt+0", "etc/gmt+1", "etc/gmt+10", "etc/gmt+11",
"etc/gmt+12", "etc/gmt+2", "etc/gmt+3", "etc/gmt+4", "etc/gmt+5", "etc/gmt+6",
"etc/gmt+7", "etc/gmt+8", "etc/gmt+9", "etc/gmt-0", "etc/gmt-1", "etc/gmt-10",
"etc/gmt-11", "etc/gmt-12", "etc/gmt-13", "etc/gmt-14", "etc/gmt-2", "etc/gmt-3",
"etc/gmt-4", "etc/gmt-5", "etc/gmt-6", "etc/gmt-7", "etc/gmt-8", "etc/gmt-9", "etc/gmt0",
"etc/greenwich", "etc/uct", "etc/universal", "etc/utc", "etc/zulu", "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/kiev", "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/uzhgorod",
"europe/vaduz", "europe/vatican", "europe/vienna", "europe/vilnius", "europe/volgograd",
"europe/warsaw", "europe/zagreb", "europe/zaporozhye", "europe/zurich", "gb", "gb-eire",
"gmt", "gmt+0", "gmt-0", "gmt0", "greenwich", "hongkong", "hst", "iceland", "indian/antananarivo",
"indian/chagos", "indian/christmas", "indian/cocos", "indian/comoro", "indian/kerguelen",
"indian/mahe", "indian/maldives", "indian/mauritius", "indian/mayotte", "indian/reunion",
"iran", "israel", "jamaica", "japan", "kwajalein", "libya", "met", "mexico/bajanorte",
"mexico/bajasur", "mexico/general", "mst", "mst7mdt", "navajo", "nz", "nz-chat",
"pacific/apia", "pacific/auckland", "pacific/bougainville", "pacific/chatham",
"pacific/chuuk", "pacific/easter", "pacific/efate", "pacific/enderbury", "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/ponape", "pacific/port_moresby",
"pacific/rarotonga", "pacific/saipan", "pacific/samoa", "pacific/tahiti", "pacific/tarawa",
"pacific/tongatapu", "pacific/truk", "pacific/wake", "pacific/wallis", "pacific/yap",
"poland", "portugal", "prc", "pst8pdt", "roc", "rok", "singapore", "turkey", "uct",
"universal", "us/alaska", "us/aleutian", "us/arizona", "us/central", "us/east-indiana",
"us/eastern", "us/hawaii", "us/indiana-starke", "us/michigan", "us/mountain", "us/pacific",
"us/samoa", "utc", "w-su", "wet", "zulu",
])
///|
fn iso_digits(chars : Array[Char], start : Int, n : Int) -> Int? {
if start < 0 || start + n > chars.length() {
return None
}
let mut v = 0
for i in start..<(start + n) {
let c = chars[i]
if !(c >= '0' && c <= '9') {
return None
}
v = v * 10 + (c.to_int() - '0'.to_int())
}
Some(v)
}
///|
fn iso_is_leap(year : Int) -> Bool {
(year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}
///|
fn iso_days_in_month(year : Int, month : Int) -> Int {
match month {
2 => if iso_is_leap(year) { 29 } else { 28 }
4 | 6 | 9 | 11 => 30
_ => 31
}
}
///|
/// Validates the date component of an ISO-8601 string, returning its length.
fn iso_date_len(chars : Array[Char]) -> Int? {
let n = chars.length()
guard iso_digits(chars, 0, 4) is Some(year) && year >= 1 else { return None }
if n >= 5 && chars[4] == '-' {
if n >= 6 && chars[5] == 'W' {
guard iso_digits(chars, 6, 2) is Some(week) && week >= 1 && week <= 53 else {
return None
}
if n >= 10 && chars[8] == '-' {
guard iso_digits(chars, 9, 1) is Some(d) && d >= 1 && d <= 7 else {
return None
}
return Some(10)
}
return Some(8)
}
guard iso_digits(chars, 5, 2) is Some(month) &&
month >= 1 &&
month <= 12 &&
n >= 10 &&
chars[7] == '-' &&
iso_digits(chars, 8, 2) is Some(day) &&
day >= 1 &&
day <= iso_days_in_month(year, month) else {
return None
}
return Some(10)
}
if n >= 5 && chars[4] == 'W' {
guard iso_digits(chars, 5, 2) is Some(week) && week >= 1 && week <= 53 else {
return None
}
if n >= 8 && chars[7] >= '0' && chars[7] <= '9' {
guard iso_digits(chars, 7, 1) is Some(d) && d >= 1 && d <= 7 else {
return None
}
return Some(8)
}
return Some(7)
}
guard iso_digits(chars, 4, 2) is Some(month) &&
month >= 1 &&
month <= 12 &&
iso_digits(chars, 6, 2) is Some(day) &&
day >= 1 &&
day <= iso_days_in_month(year, month) else {
return None
}
Some(8)
}
///|
/// Parses `HH[:MM[:SS[.ffffff]]]` (or the compact form) starting at `start` and ending
/// at `end`. Returns the fractional digits (possibly empty) if valid.
fn iso_time_fraction(
chars : Array[Char],
start : Int,
end : Int,
max_hour : Int,
) -> String? {
let mut i = start
guard iso_digits(chars, i, 2) is Some(hh) && hh <= max_hour else {
return None
}
i += 2
let mut has_colon : Bool? = None
let mut components = 1
while i < end && components < 3 {
let c = chars[i]
if c == ':' {
if has_colon is Some(false) {
return None
}
has_colon = Some(true)
i += 1
} else if c >= '0' && c <= '9' {
if has_colon is Some(true) {
return None
}
has_colon = Some(false)
} else {
break
}
guard iso_digits(chars, i, 2) is Some(v) && v <= 59 else { return None }
i += 2
components += 1
}
if i == end {
return Some("")
}
if components < 3 || !(chars[i] == '.' || chars[i] == ',') {
return None
}
i += 1
if i >= end {
return None
}
let sb = StringBuilder()
while i < end {
let c = chars[i]
if !(c >= '0' && c <= '9') {
return None
}
sb.write_char(c)
i += 1
}
Some(sb.to_string())
}
///|
/// Port of `sqlglot.time.subsecond_precision`: given an ISO-8601 timestamp literal, e.g.
/// '2023-01-01 12:13:14.123456+00:00', returns its subsecond precision (0, 3 or 6).
/// Mirrors Python's `datetime.fromisoformat`; invalid literals yield 0.
pub fn subsecond_precision(timestamp_literal : String) -> Int {
let chars = timestamp_literal.to_array()
let n = chars.length()
guard iso_date_len(chars) is Some(date_len) else { return 0 }
if n <= date_len + 1 {
return 0
}
// The time part starts after a single separator character.
let time_start = date_len + 1
let mut tz_start = n
for i in time_start..= n ||
iso_time_fraction(chars, tz_start + 1, n, 23) is None {
return 0
}
}
let digits = if fraction.length() > 6 {
fraction.view(end_offset=6).to_owned()
} else {
fraction + "0".repeat(6 - fraction.length())
}
let mut count = 6
while count > 0 && digits.get_char(count - 1) == Some('0') {
count -= 1
}
if count > 3 {
6
} else if count > 0 {
3
} else {
0
}
}