#+build windows #+private package timezone import "core:strings" import "core:sys/windows" import "core:time/datetime" TZ_Abbrev :: struct { std: string, dst: string, } @(rodata) tz_abbrevs := [?]struct{key: string, value: TZ_Abbrev}{ {"Egypt Standard Time", {"EET", "EEST"}}, // Africa/Cairo {"Morocco Standard Time", {"+00", "+01"}}, // Africa/Casablanca {"South Africa Standard Time", {"SAST", "SAST"}}, // Africa/Johannesburg {"South Sudan Standard Time", {"CAT", "CAT"}}, // Africa/Juba {"Sudan Standard Time", {"CAT", "CAT"}}, // Africa/Khartoum {"W. Central Africa Standard Time", {"WAT", "WAT"}}, // Africa/Lagos {"E. Africa Standard Time", {"EAT", "EAT"}}, // Africa/Nairobi {"Sao Tome Standard Time", {"GMT", "GMT"}}, // Africa/Sao_Tome {"Libya Standard Time", {"EET", "EET"}}, // Africa/Tripoli {"Namibia Standard Time", {"CAT", "CAT"}}, // Africa/Windhoek {"Aleutian Standard Time", {"HST", "HDT"}}, // America/Adak {"Alaskan Standard Time", {"AKST", "AKDT"}}, // America/Anchorage {"Tocantins Standard Time", {"-03", "-03"}}, // America/Araguaina {"Paraguay Standard Time", {"-04", "-03"}}, // America/Asuncion {"Bahia Standard Time", {"-03", "-03"}}, // America/Bahia {"SA Pacific Standard Time", {"-05", "-05"}}, // America/Bogota {"Argentina Standard Time", {"-03", "-03"}}, // America/Buenos_Aires {"Eastern Standard Time (Mexico)", {"EST", "EST"}}, // America/Cancun {"Venezuela Standard Time", {"-04", "-04"}}, // America/Caracas {"SA Eastern Standard Time", {"-03", "-03"}}, // America/Cayenne {"Central Standard Time", {"CST", "CDT"}}, // America/Chicago {"Central Brazilian Standard Time", {"-04", "-04"}}, // America/Cuiaba {"Mountain Standard Time", {"MST", "MDT"}}, // America/Denver {"Greenland Standard Time", {"-03", "-02"}}, // America/Godthab {"Turks And Caicos Standard Time", {"EST", "EDT"}}, // America/Grand_Turk {"Central America Standard Time", {"CST", "CST"}}, // America/Guatemala {"Atlantic Standard Time", {"AST", "ADT"}}, // America/Halifax {"Cuba Standard Time", {"CST", "CDT"}}, // America/Havana {"US Eastern Standard Time", {"EST", "EDT"}}, // America/Indianapolis {"SA Western Standard Time", {"-04", "-04"}}, // America/La_Paz {"Pacific Standard Time", {"PST", "PDT"}}, // America/Los_Angeles {"Mountain Standard Time (Mexico)", {"MST", "MST"}}, // America/Mazatlan {"Central Standard Time (Mexico)", {"CST", "CST"}}, // America/Mexico_City {"Saint Pierre Standard Time", {"-03", "-02"}}, // America/Miquelon {"Montevideo Standard Time", {"-03", "-03"}}, // America/Montevideo {"Eastern Standard Time", {"EST", "EDT"}}, // America/New_York {"US Mountain Standard Time", {"MST", "MST"}}, // America/Phoenix {"Haiti Standard Time", {"EST", "EDT"}}, // America/Port-au-Prince {"Magallanes Standard Time", {"-03", "-03"}}, // America/Punta_Arenas {"Canada Central Standard Time", {"CST", "CST"}}, // America/Regina {"Pacific SA Standard Time", {"-04", "-03"}}, // America/Santiago {"E. South America Standard Time", {"-03", "-03"}}, // America/Sao_Paulo {"Newfoundland Standard Time", {"NST", "NDT"}}, // America/St_Johns {"Pacific Standard Time (Mexico)", {"PST", "PDT"}}, // America/Tijuana {"Yukon Standard Time", {"MST", "MST"}}, // America/Whitehorse {"Central Asia Standard Time", {"+06", "+06"}}, // Asia/Almaty {"Jordan Standard Time", {"+03", "+03"}}, // Asia/Amman {"Arabic Standard Time", {"+03", "+03"}}, // Asia/Baghdad {"Azerbaijan Standard Time", {"+04", "+04"}}, // Asia/Baku {"SE Asia Standard Time", {"+07", "+07"}}, // Asia/Bangkok {"Altai Standard Time", {"+07", "+07"}}, // Asia/Barnaul {"Middle East Standard Time", {"EET", "EEST"}}, // Asia/Beirut {"India Standard Time", {"IST", "IST"}}, // Asia/Calcutta {"Transbaikal Standard Time", {"+09", "+09"}}, // Asia/Chita {"Sri Lanka Standard Time", {"+0530", "+0530"}}, // Asia/Colombo {"Syria Standard Time", {"+03", "+03"}}, // Asia/Damascus {"Bangladesh Standard Time", {"+06", "+06"}}, // Asia/Dhaka {"Arabian Standard Time", {"+04", "+04"}}, // Asia/Dubai {"West Bank Standard Time", {"EET", "EEST"}}, // Asia/Hebron {"W. Mongolia Standard Time", {"+07", "+07"}}, // Asia/Hovd {"North Asia East Standard Time", {"+08", "+08"}}, // Asia/Irkutsk {"Israel Standard Time", {"IST", "IDT"}}, // Asia/Jerusalem {"Afghanistan Standard Time", {"+0430", "+0430"}}, // Asia/Kabul {"Russia Time Zone 11", {"+12", "+12"}}, // Asia/Kamchatka {"Pakistan Standard Time", {"PKT", "PKT"}}, // Asia/Karachi {"Nepal Standard Time", {"+0545", "+0545"}}, // Asia/Katmandu {"North Asia Standard Time", {"+07", "+07"}}, // Asia/Krasnoyarsk {"Magadan Standard Time", {"+11", "+11"}}, // Asia/Magadan {"N. Central Asia Standard Time", {"+07", "+07"}}, // Asia/Novosibirsk {"Omsk Standard Time", {"+06", "+06"}}, // Asia/Omsk {"North Korea Standard Time", {"KST", "KST"}}, // Asia/Pyongyang {"Qyzylorda Standard Time", {"+05", "+05"}}, // Asia/Qyzylorda {"Myanmar Standard Time", {"+0630", "+0630"}}, // Asia/Rangoon {"Arab Standard Time", {"+03", "+03"}}, // Asia/Riyadh {"Sakhalin Standard Time", {"+11", "+11"}}, // Asia/Sakhalin {"Korea Standard Time", {"KST", "KST"}}, // Asia/Seoul {"China Standard Time", {"CST", "CST"}}, // Asia/Shanghai {"Singapore Standard Time", {"+08", "+08"}}, // Asia/Singapore {"Russia Time Zone 10", {"+11", "+11"}}, // Asia/Srednekolymsk {"Taipei Standard Time", {"CST", "CST"}}, // Asia/Taipei {"West Asia Standard Time", {"+05", "+05"}}, // Asia/Tashkent {"Georgian Standard Time", {"+04", "+04"}}, // Asia/Tbilisi {"Iran Standard Time", {"+0330", "+0330"}}, // Asia/Tehran {"Tokyo Standard Time", {"JST", "JST"}}, // Asia/Tokyo {"Tomsk Standard Time", {"+07", "+07"}}, // Asia/Tomsk {"Ulaanbaatar Standard Time", {"+08", "+08"}}, // Asia/Ulaanbaatar {"Vladivostok Standard Time", {"+10", "+10"}}, // Asia/Vladivostok {"Yakutsk Standard Time", {"+09", "+09"}}, // Asia/Yakutsk {"Ekaterinburg Standard Time", {"+05", "+05"}}, // Asia/Yekaterinburg {"Caucasus Standard Time", {"+04", "+04"}}, // Asia/Yerevan {"Azores Standard Time", {"-01", "+00"}}, // Atlantic/Azores {"Cape Verde Standard Time", {"-01", "-01"}}, // Atlantic/Cape_Verde {"Greenwich Standard Time", {"GMT", "GMT"}}, // Atlantic/Reykjavik {"Cen. Australia Standard Time", {"ACST", "ACDT"}}, // Australia/Adelaide {"E. Australia Standard Time", {"AEST", "AEST"}}, // Australia/Brisbane {"AUS Central Standard Time", {"ACST", "ACST"}}, // Australia/Darwin {"Aus Central W. Standard Time", {"+0845", "+0845"}}, // Australia/Eucla {"Tasmania Standard Time", {"AEST", "AEDT"}}, // Australia/Hobart {"Lord Howe Standard Time", {"+1030", "+11"}}, // Australia/Lord_Howe {"W. Australia Standard Time", {"AWST", "AWST"}}, // Australia/Perth {"AUS Eastern Standard Time", {"AEST", "AEDT"}}, // Australia/Sydney {"UTC-11", {"-11", "-11"}}, // Etc/GMT+11 {"Dateline Standard Time", {"-12", "-12"}}, // Etc/GMT+12 {"UTC-02", {"-02", "-02"}}, // Etc/GMT+2 {"UTC-08", {"-08", "-08"}}, // Etc/GMT+8 {"UTC-09", {"-09", "-09"}}, // Etc/GMT+9 {"UTC+12", {"+12", "+12"}}, // Etc/GMT-12 {"UTC+13", {"+13", "+13"}}, // Etc/GMT-13 {"UTC", {"UTC", "UTC"}}, // Etc/UTC {"Astrakhan Standard Time", {"+04", "+04"}}, // Europe/Astrakhan {"W. Europe Standard Time", {"CET", "CEST"}}, // Europe/Berlin {"GTB Standard Time", {"EET", "EEST"}}, // Europe/Bucharest {"Central Europe Standard Time", {"CET", "CEST"}}, // Europe/Budapest {"E. Europe Standard Time", {"EET", "EEST"}}, // Europe/Chisinau {"Turkey Standard Time", {"+03", "+03"}}, // Europe/Istanbul {"Kaliningrad Standard Time", {"EET", "EET"}}, // Europe/Kaliningrad {"FLE Standard Time", {"EET", "EEST"}}, // Europe/Kiev {"GMT Standard Time", {"GMT", "BST"}}, // Europe/London {"Belarus Standard Time", {"+03", "+03"}}, // Europe/Minsk {"Russian Standard Time", {"MSK", "MSK"}}, // Europe/Moscow {"Romance Standard Time", {"CET", "CEST"}}, // Europe/Paris {"Russia Time Zone 3", {"+04", "+04"}}, // Europe/Samara {"Saratov Standard Time", {"+04", "+04"}}, // Europe/Saratov {"Volgograd Standard Time", {"MSK", "MSK"}}, // Europe/Volgograd {"Central European Standard Time", {"CET", "CEST"}}, // Europe/Warsaw {"Mauritius Standard Time", {"+04", "+04"}}, // Indian/Mauritius {"Samoa Standard Time", {"+13", "+13"}}, // Pacific/Apia {"New Zealand Standard Time", {"NZST", "NZDT"}}, // Pacific/Auckland {"Bougainville Standard Time", {"+11", "+11"}}, // Pacific/Bougainville {"Chatham Islands Standard Time", {"+1245", "+1345"}}, // Pacific/Chatham {"Easter Island Standard Time", {"-06", "-05"}}, // Pacific/Easter {"Fiji Standard Time", {"+12", "+12"}}, // Pacific/Fiji {"Central Pacific Standard Time", {"+11", "+11"}}, // Pacific/Guadalcanal {"Hawaiian Standard Time", {"HST", "HST"}}, // Pacific/Honolulu {"Line Islands Standard Time", {"+14", "+14"}}, // Pacific/Kiritimati {"Marquesas Standard Time", {"-0930", "-0930"}}, // Pacific/Marquesas {"Norfolk Standard Time", {"+11", "+12"}}, // Pacific/Norfolk {"West Pacific Standard Time", {"+10", "+10"}}, // Pacific/Port_Moresby {"Tonga Standard Time", {"+13", "+13"}}, // Pacific/Tongatapu } iana_to_windows_tz :: proc(iana_name: string, allocator := context.allocator) -> (name: string, success: bool) { wintz_name_buffer: [128]u16 status: windows.UError iana_name_wstr := windows.utf8_to_wstring(iana_name, allocator) defer free(rawptr(iana_name_wstr), allocator) wintz_name_len := windows.ucal_getWindowsTimeZoneID(iana_name_wstr, -1, cstring16(raw_data(wintz_name_buffer[:])), len(wintz_name_buffer), &status) if status != .U_ZERO_ERROR { return } wintz_name, err := windows.utf16_to_utf8(wintz_name_buffer[:wintz_name_len], allocator) if err != nil { return } return wintz_name, true } local_tz_name :: proc(allocator := context.allocator) -> (name: string, success: bool) { iana_name_buffer: [128]u16 status: windows.UError zone_str_len := windows.ucal_getDefaultTimeZone(cstring16(raw_data(iana_name_buffer[:])), len(iana_name_buffer), &status) if status != .U_ZERO_ERROR { return } iana_name, err := windows.utf16_to_utf8(iana_name_buffer[:zone_str_len], allocator) if err != nil { return } return iana_name, true } REG_TZI_FORMAT :: struct #packed { bias: windows.LONG, std_bias: windows.LONG, dst_bias: windows.LONG, std_date: windows.SYSTEMTIME, dst_date: windows.SYSTEMTIME, } generate_rrule_from_tzi :: proc(tzi: ^REG_TZI_FORMAT, abbrevs: TZ_Abbrev, allocator := context.allocator) -> (rrule: datetime.TZ_RRule, ok: bool) { std_name, err := strings.clone(abbrevs.std, allocator) if err != nil { return } defer if err != nil { delete(std_name, allocator) } if (tzi.std_date.month == 0) { return datetime.TZ_RRule{ has_dst = false, std_name = std_name, std_offset = -(i64(tzi.bias) + i64(tzi.std_bias)) * 60, dst_date = datetime.TZ_Transition_Date{ type = .Month_Week_Day, month = u8(tzi.std_date.month), week = u8(tzi.std_date.day), day = tzi.std_date.day_of_week, time = (i64(tzi.std_date.hour) * 60 * 60) + (i64(tzi.std_date.minute) * 60) + i64(tzi.std_date.second), }, }, true } dst_name: string dst_name, err = strings.clone(abbrevs.dst, allocator) if err != nil { return } defer if err != nil { delete(dst_name, allocator) } return datetime.TZ_RRule{ has_dst = true, std_name = std_name, std_offset = -(i64(tzi.bias) + i64(tzi.std_bias)) * 60, dst_date = datetime.TZ_Transition_Date{ type = .Month_Week_Day, month = u8(tzi.std_date.month), week = u8(tzi.std_date.day), day = tzi.std_date.day_of_week, time = (i64(tzi.std_date.hour) * 60 * 60) + (i64(tzi.std_date.minute) * 60) + i64(tzi.std_date.second), }, dst_name = dst_name, dst_offset = -(i64(tzi.bias) + i64(tzi.dst_bias)) * 60, std_date = datetime.TZ_Transition_Date{ type = .Month_Week_Day, month = u8(tzi.dst_date.month), week = u8(tzi.dst_date.day), day = tzi.dst_date.day_of_week, time = (i64(tzi.dst_date.hour) * 60 * 60) + (i64(tzi.dst_date.minute) * 60) + i64(tzi.dst_date.second), }, }, true } _region_load :: proc(reg_str: string, allocator := context.allocator) -> (out_reg: ^datetime.TZ_Region, success: bool) { wintz_name: string iana_name: string if reg_str == "local" { ok := false iana_name = local_tz_name(allocator) or_return wintz_name, ok = iana_to_windows_tz(iana_name, allocator) if !ok { delete(iana_name, allocator) return } } else { wintz_name = iana_to_windows_tz(reg_str, allocator) or_return iana_name = strings.clone(reg_str, allocator) } defer delete(wintz_name, allocator) defer delete(iana_name, allocator) abbrevs: TZ_Abbrev abbrevs_ok: bool for pair in tz_abbrevs { if pair.key == wintz_name { abbrevs = pair.value abbrevs_ok = true break } } if !abbrevs_ok { return } if abbrevs.std == "UTC" && abbrevs.dst == abbrevs.std { return nil, true } key_base := `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones` tz_key := strings.join({key_base, wintz_name}, "\\", allocator = allocator) defer delete(tz_key, allocator) tz_key_wstr := windows.utf8_to_wstring(tz_key, allocator) defer free(rawptr(tz_key_wstr), allocator) key: windows.HKEY res := windows.RegOpenKeyExW(windows.HKEY_LOCAL_MACHINE, tz_key_wstr, 0, windows.KEY_READ, &key) if res != 0 { return } defer windows.RegCloseKey(key) tzi: REG_TZI_FORMAT size := u32(size_of(REG_TZI_FORMAT)) res = windows.RegGetValueW(key, nil, windows.L("TZI"), windows.RRF_RT_ANY, nil, &tzi, &size) if res != 0 { return } rrule := generate_rrule_from_tzi(&tzi, abbrevs, allocator) or_return region_name, err := strings.clone(iana_name, allocator) if err != nil { return } defer if err != nil { delete(region_name, allocator) } region: ^datetime.TZ_Region region, err = new_clone(datetime.TZ_Region{ name = region_name, rrule = rrule, }, allocator) if err != nil { return } return region, true }