aboutsummaryrefslogtreecommitdiff
path: root/core/time/timezone/tz_windows.odin
blob: fe00719a2e9a89bfb3b80f97b3e55e825dc648d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
#+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
}