/src/FreeRDP/winpr/libwinpr/timezone/timezone.c
Line | Count | Source |
1 | | /** |
2 | | * WinPR: Windows Portable Runtime |
3 | | * Time Zone |
4 | | * |
5 | | * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com> |
6 | | * Copyright 2024 Armin Novak <anovak@thincast.com> |
7 | | * Copyright 2024 Thincast Technologies GmbH |
8 | | * |
9 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
10 | | * you may not use this file except in compliance with the License. |
11 | | * You may obtain a copy of the License at |
12 | | * |
13 | | * http://www.apache.org/licenses/LICENSE-2.0 |
14 | | * |
15 | | * Unless required by applicable law or agreed to in writing, software |
16 | | * distributed under the License is distributed on an "AS IS" BASIS, |
17 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
18 | | * See the License for the specific language governing permissions and |
19 | | * limitations under the License. |
20 | | */ |
21 | | |
22 | | #include <winpr/config.h> |
23 | | |
24 | | #include <winpr/environment.h> |
25 | | #include <winpr/wtypes.h> |
26 | | #include <winpr/timezone.h> |
27 | | #include <winpr/crt.h> |
28 | | #include <winpr/assert.h> |
29 | | #include <winpr/file.h> |
30 | | #include "../log.h" |
31 | | #include "timezone.h" |
32 | | |
33 | 106k | #define TAG WINPR_TAG("timezone") |
34 | | |
35 | | #include "TimeZoneNameMap.h" |
36 | | #include "TimeZoneIanaAbbrevMap.h" |
37 | | |
38 | | #ifndef _WIN32 |
39 | | |
40 | | #include <time.h> |
41 | | #include <unistd.h> |
42 | | |
43 | | #endif |
44 | | |
45 | | #if !defined(_WIN32) |
46 | | static char* winpr_read_unix_timezone_identifier_from_file(FILE* fp) |
47 | 106k | { |
48 | 106k | const INT CHUNK_SIZE = 32; |
49 | 106k | size_t rc = 0; |
50 | 106k | size_t read = 0; |
51 | 106k | size_t length = CHUNK_SIZE; |
52 | | |
53 | 106k | char* tzid = malloc(length); |
54 | 106k | if (!tzid) |
55 | 0 | return nullptr; |
56 | | |
57 | 106k | do |
58 | 106k | { |
59 | 106k | rc = fread(tzid + read, 1, length - read - 1UL, fp); |
60 | 106k | if (rc > 0) |
61 | 106k | read += rc; |
62 | | |
63 | 106k | if (read < (length - 1UL)) |
64 | 106k | break; |
65 | | |
66 | 0 | if (read > length - 1UL) |
67 | 0 | { |
68 | 0 | free(tzid); |
69 | 0 | return nullptr; |
70 | 0 | } |
71 | | |
72 | 0 | length += CHUNK_SIZE; |
73 | 0 | char* tmp = (char*)realloc(tzid, length); |
74 | 0 | if (!tmp) |
75 | 0 | { |
76 | 0 | free(tzid); |
77 | 0 | return nullptr; |
78 | 0 | } |
79 | | |
80 | 0 | tzid = tmp; |
81 | 0 | } while (rc > 0); |
82 | | |
83 | 106k | if (ferror(fp)) |
84 | 0 | { |
85 | 0 | free(tzid); |
86 | 0 | return nullptr; |
87 | 0 | } |
88 | | |
89 | 106k | tzid[read] = '\0'; |
90 | 106k | if (read > 0) |
91 | 106k | { |
92 | 106k | if (tzid[read - 1] == '\n') |
93 | 106k | tzid[read - 1] = '\0'; |
94 | 106k | } |
95 | | |
96 | 106k | return tzid; |
97 | 106k | } |
98 | | |
99 | | static char* winpr_get_timezone_from_link(const char* links[], size_t count) |
100 | 106k | { |
101 | 106k | const char* _links[] = { "/etc/localtime", "/etc/TZ" }; |
102 | | |
103 | 106k | if (links == nullptr) |
104 | 0 | { |
105 | 0 | links = _links; |
106 | 0 | count = ARRAYSIZE(_links); |
107 | 0 | } |
108 | | |
109 | | /* |
110 | | * On linux distros such as Redhat or Archlinux, a symlink at /etc/localtime |
111 | | * will point to /usr/share/zoneinfo/region/place where region/place could be |
112 | | * America/Montreal for example. |
113 | | * Some distributions do have to symlink at /etc/TZ. |
114 | | */ |
115 | | |
116 | 106k | for (size_t x = 0; x < count; x++) |
117 | 106k | { |
118 | 106k | char* tzid = nullptr; |
119 | 106k | const char* link = links[x]; |
120 | 106k | char* buf = realpath(link, nullptr); |
121 | | |
122 | 106k | if (buf) |
123 | 106k | { |
124 | 106k | size_t sep = 0; |
125 | 106k | size_t alloc = 0; |
126 | 106k | size_t pos = 0; |
127 | 106k | size_t len = pos = strlen(buf); |
128 | | |
129 | | /* find the position of the 2nd to last "/" */ |
130 | 852k | for (size_t i = 1; i <= len; i++) |
131 | 852k | { |
132 | 852k | const size_t curpos = len - i; |
133 | 852k | const char cur = buf[curpos]; |
134 | | |
135 | 852k | if (cur == '/') |
136 | 213k | sep++; |
137 | 852k | if (sep >= 2) |
138 | 106k | { |
139 | 106k | alloc = i; |
140 | 106k | pos = len - i + 1; |
141 | 106k | break; |
142 | 106k | } |
143 | 852k | } |
144 | | |
145 | 106k | if ((len == 0) || (sep != 2)) |
146 | 0 | goto end; |
147 | | |
148 | 106k | tzid = (char*)calloc(alloc + 1, sizeof(char)); |
149 | | |
150 | 106k | if (!tzid) |
151 | 0 | goto end; |
152 | | |
153 | 106k | strncpy(tzid, &buf[pos], alloc); |
154 | 106k | WLog_DBG(TAG, "tzid: %s", tzid); |
155 | 106k | goto end; |
156 | 106k | } |
157 | | |
158 | 106k | end: |
159 | 106k | free(buf); |
160 | 106k | if (tzid) |
161 | 106k | return tzid; |
162 | 106k | } |
163 | | |
164 | 0 | return nullptr; |
165 | 106k | } |
166 | | |
167 | | #if defined(ANDROID) |
168 | | #include "../utils/android.h" |
169 | | |
170 | | static char* winpr_get_android_timezone_identifier(void) |
171 | | { |
172 | | char* tzid = nullptr; |
173 | | JNIEnv* jniEnv; |
174 | | |
175 | | /* Preferred: Try to get identifier from java TimeZone class */ |
176 | | if (jniVm && ((*jniVm)->GetEnv(jniVm, (void**)&jniEnv, JNI_VERSION_1_6) == JNI_OK)) |
177 | | { |
178 | | const char* raw; |
179 | | jclass jObjClass; |
180 | | jobject jObj; |
181 | | jmethodID jDefaultTimezone; |
182 | | jmethodID jTimezoneIdentifier; |
183 | | jstring tzJId; |
184 | | jboolean attached = (*jniVm)->AttachCurrentThread(jniVm, &jniEnv, nullptr); |
185 | | jObjClass = (*jniEnv)->FindClass(jniEnv, "java/util/TimeZone"); |
186 | | |
187 | | if (!jObjClass) |
188 | | goto fail; |
189 | | |
190 | | jDefaultTimezone = |
191 | | (*jniEnv)->GetStaticMethodID(jniEnv, jObjClass, "getDefault", "()Ljava/util/TimeZone;"); |
192 | | |
193 | | if (!jDefaultTimezone) |
194 | | goto fail; |
195 | | |
196 | | jObj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jObjClass, jDefaultTimezone); |
197 | | |
198 | | if (!jObj) |
199 | | goto fail; |
200 | | |
201 | | jTimezoneIdentifier = |
202 | | (*jniEnv)->GetMethodID(jniEnv, jObjClass, "getID", "()Ljava/lang/String;"); |
203 | | |
204 | | if (!jTimezoneIdentifier) |
205 | | goto fail; |
206 | | |
207 | | tzJId = (*jniEnv)->CallObjectMethod(jniEnv, jObj, jTimezoneIdentifier); |
208 | | |
209 | | if (!tzJId) |
210 | | goto fail; |
211 | | |
212 | | raw = (*jniEnv)->GetStringUTFChars(jniEnv, tzJId, 0); |
213 | | |
214 | | if (raw) |
215 | | tzid = _strdup(raw); |
216 | | |
217 | | (*jniEnv)->ReleaseStringUTFChars(jniEnv, tzJId, raw); |
218 | | fail: |
219 | | |
220 | | if (attached) |
221 | | (*jniVm)->DetachCurrentThread(jniVm); |
222 | | } |
223 | | |
224 | | /* Fall back to property, might not be available. */ |
225 | | if (!tzid) |
226 | | { |
227 | | FILE* fp = popen("getprop persist.sys.timezone", "r"); |
228 | | |
229 | | if (fp) |
230 | | { |
231 | | tzid = winpr_read_unix_timezone_identifier_from_file(fp); |
232 | | pclose(fp); |
233 | | } |
234 | | } |
235 | | |
236 | | return tzid; |
237 | | } |
238 | | #endif |
239 | | |
240 | | static char* winpr_get_unix_timezone_identifier_from_file(void) |
241 | 106k | { |
242 | | #if defined(ANDROID) |
243 | | return winpr_get_android_timezone_identifier(); |
244 | | #else |
245 | 106k | FILE* fp = nullptr; |
246 | 106k | char* tzid = nullptr; |
247 | | #if !defined(WINPR_TIMEZONE_FILE) |
248 | | #error \ |
249 | | "Please define WINPR_TIMEZONE_FILE with the path to your timezone file (e.g. /etc/timezone or similar)" |
250 | | #else |
251 | 106k | fp = winpr_fopen(WINPR_TIMEZONE_FILE, "r"); |
252 | 106k | #endif |
253 | | |
254 | 106k | if (nullptr == fp) |
255 | 0 | return nullptr; |
256 | | |
257 | 106k | tzid = winpr_read_unix_timezone_identifier_from_file(fp); |
258 | 106k | (void)fclose(fp); |
259 | 106k | if (tzid != nullptr) |
260 | 106k | WLog_DBG(TAG, "tzid: %s", tzid); |
261 | 106k | return tzid; |
262 | 106k | #endif |
263 | 106k | } |
264 | | |
265 | | static char* winpr_time_zone_from_env(void) |
266 | 106k | { |
267 | 106k | LPCSTR tz = "TZ"; |
268 | 106k | char* tzid = nullptr; |
269 | | |
270 | 106k | DWORD nSize = GetEnvironmentVariableA(tz, nullptr, 0); |
271 | 106k | if (nSize > 0) |
272 | 0 | { |
273 | 0 | tzid = (char*)calloc(nSize, sizeof(char)); |
274 | 0 | if (!tzid) |
275 | 0 | goto fail; |
276 | 0 | if (!GetEnvironmentVariableA(tz, tzid, nSize)) |
277 | 0 | goto fail; |
278 | 0 | else if (tzid[0] == ':') |
279 | 0 | { |
280 | | /* Remove leading colon, see tzset(3) */ |
281 | 0 | memmove(tzid, tzid + 1, nSize - sizeof(char)); |
282 | 0 | } |
283 | 0 | } |
284 | | |
285 | 106k | return tzid; |
286 | | |
287 | 0 | fail: |
288 | 0 | free(tzid); |
289 | 0 | return nullptr; |
290 | 106k | } |
291 | | |
292 | | static char* winpr_translate_time_zone(const char* tzid) |
293 | 106k | { |
294 | 106k | const char* zipath = "/usr/share/zoneinfo/"; |
295 | 106k | char* buf = nullptr; |
296 | 106k | const char* links[] = { buf }; |
297 | | |
298 | 106k | if (!tzid) |
299 | 0 | return nullptr; |
300 | | |
301 | 106k | if (tzid[0] == '/') |
302 | 0 | { |
303 | | /* Full path given in TZ */ |
304 | 0 | links[0] = tzid; |
305 | 0 | } |
306 | 106k | else |
307 | 106k | { |
308 | 106k | size_t bsize = 0; |
309 | 106k | winpr_asprintf(&buf, &bsize, "%s%s", zipath, tzid); |
310 | 106k | links[0] = buf; |
311 | 106k | } |
312 | | |
313 | 106k | char* ntzid = winpr_get_timezone_from_link(links, 1); |
314 | 106k | free(buf); |
315 | 106k | return ntzid; |
316 | 106k | } |
317 | | |
318 | | static char* winpr_guess_time_zone(void) |
319 | 106k | { |
320 | 106k | char* tzid = winpr_time_zone_from_env(); |
321 | 106k | if (tzid) |
322 | 0 | goto end; |
323 | 106k | tzid = winpr_get_unix_timezone_identifier_from_file(); |
324 | 106k | if (tzid) |
325 | 106k | goto end; |
326 | 0 | tzid = winpr_get_timezone_from_link(nullptr, 0); |
327 | 0 | if (tzid) |
328 | 0 | goto end; |
329 | | |
330 | 106k | end: |
331 | 106k | { |
332 | 106k | char* ntzid = winpr_translate_time_zone(tzid); |
333 | 106k | if (ntzid) |
334 | 106k | { |
335 | 106k | free(tzid); |
336 | 106k | return ntzid; |
337 | 106k | } |
338 | 0 | return tzid; |
339 | 106k | } |
340 | 106k | } |
341 | | |
342 | | static SYSTEMTIME tm2systemtime(const struct tm* t) |
343 | 213k | { |
344 | 213k | SYSTEMTIME st = WINPR_C_ARRAY_INIT; |
345 | | |
346 | 213k | if (t) |
347 | 0 | { |
348 | 0 | st.wYear = (WORD)(1900 + t->tm_year); |
349 | 0 | st.wMonth = (WORD)t->tm_mon + 1; |
350 | 0 | st.wDay = (WORD)t->tm_mday; |
351 | 0 | st.wDayOfWeek = (WORD)t->tm_wday; |
352 | 0 | st.wHour = (WORD)t->tm_hour; |
353 | 0 | st.wMinute = (WORD)t->tm_min; |
354 | 0 | st.wSecond = (WORD)t->tm_sec; |
355 | 0 | st.wMilliseconds = 0; |
356 | 0 | } |
357 | 213k | return st; |
358 | 213k | } |
359 | | |
360 | | static struct tm systemtime2tm(const SYSTEMTIME* st) |
361 | 0 | { |
362 | 0 | struct tm t = WINPR_C_ARRAY_INIT; |
363 | 0 | if (st) |
364 | 0 | { |
365 | 0 | if (st->wYear >= 1900) |
366 | 0 | t.tm_year = st->wYear - 1900; |
367 | 0 | if (st->wMonth > 0) |
368 | 0 | t.tm_mon = st->wMonth - 1; |
369 | 0 | t.tm_mday = st->wDay; |
370 | 0 | t.tm_wday = st->wDayOfWeek; |
371 | 0 | t.tm_hour = st->wHour; |
372 | 0 | t.tm_min = st->wMinute; |
373 | 0 | t.tm_sec = st->wSecond; |
374 | 0 | } |
375 | 0 | return t; |
376 | 0 | } |
377 | | |
378 | | static LONG get_gmtoff_min(const struct tm* t) |
379 | 213k | { |
380 | 213k | WINPR_ASSERT(t); |
381 | 213k | return -(LONG)(t->tm_gmtoff / 60l); |
382 | 213k | } |
383 | | |
384 | | static struct tm update_tm(const struct tm* start) |
385 | 116M | { |
386 | 116M | WINPR_ASSERT(start); |
387 | 116M | struct tm cur = *start; |
388 | 116M | const time_t t = mktime(&cur); |
389 | 116M | struct tm next = WINPR_C_ARRAY_INIT; |
390 | 116M | (void)localtime_r(&t, &next); |
391 | 116M | return next; |
392 | 116M | } |
393 | | |
394 | | static struct tm next_day(const struct tm* start) |
395 | 116M | { |
396 | 116M | struct tm cur = *start; |
397 | 116M | cur.tm_hour = 0; |
398 | 116M | cur.tm_min = 0; |
399 | 116M | cur.tm_sec = 0; |
400 | 116M | cur.tm_isdst = -1; |
401 | 116M | cur.tm_mday++; |
402 | 116M | return update_tm(&cur); |
403 | 116M | } |
404 | | |
405 | | static struct tm adjust_time(const struct tm* start, int hour, int minute) |
406 | 0 | { |
407 | 0 | struct tm cur = *start; |
408 | 0 | cur.tm_hour = hour; |
409 | 0 | cur.tm_min = minute; |
410 | 0 | cur.tm_sec = 0; |
411 | 0 | cur.tm_isdst = -1; |
412 | 0 | return update_tm(&cur); |
413 | 0 | } |
414 | | |
415 | | /* [MS-RDPBCGR] 2.2.1.11.1.1.1.1.1 System Time (TS_SYSTEMTIME) */ |
416 | | static WORD get_transition_weekday_occurrence(const SYSTEMTIME* st) |
417 | 0 | { |
418 | 0 | WORD count = 0; |
419 | 0 | struct tm start = systemtime2tm(st); |
420 | |
|
421 | 0 | WORD last = 0; |
422 | 0 | struct tm next = start; |
423 | 0 | next.tm_mday = 1; |
424 | 0 | next.tm_isdst = -1; |
425 | 0 | do |
426 | 0 | { |
427 | |
|
428 | 0 | const time_t t = mktime(&next); |
429 | 0 | next.tm_mday++; |
430 | |
|
431 | 0 | struct tm cur = WINPR_C_ARRAY_INIT; |
432 | 0 | (void)localtime_r(&t, &cur); |
433 | |
|
434 | 0 | if (cur.tm_mon + 1 != st->wMonth) |
435 | 0 | break; |
436 | | |
437 | 0 | if (cur.tm_wday == st->wDayOfWeek) |
438 | 0 | { |
439 | 0 | if (cur.tm_mday <= st->wDay) |
440 | 0 | count++; |
441 | 0 | last++; |
442 | 0 | } |
443 | |
|
444 | 0 | } while (TRUE); |
445 | |
|
446 | 0 | if (count == last) |
447 | 0 | count = 5; |
448 | |
|
449 | 0 | return count; |
450 | 0 | } |
451 | | |
452 | | static SYSTEMTIME tm2transitiontime(const struct tm* cur) |
453 | 213k | { |
454 | 213k | SYSTEMTIME t = tm2systemtime(cur); |
455 | 213k | if (cur) |
456 | 0 | { |
457 | 0 | t.wDay = get_transition_weekday_occurrence(&t); |
458 | 0 | t.wYear = 0; |
459 | 0 | } |
460 | | |
461 | 213k | return t; |
462 | 213k | } |
463 | | |
464 | | static SYSTEMTIME get_transition_time(const struct tm* start, BOOL toDst) |
465 | 0 | { |
466 | 0 | BOOL toggled = FALSE; |
467 | 0 | struct tm first = adjust_time(start, 0, 0); |
468 | 0 | for (int hour = 0; hour < 24; hour++) |
469 | 0 | { |
470 | 0 | struct tm cur = adjust_time(start, hour, 0); |
471 | 0 | if (cur.tm_isdst != first.tm_isdst) |
472 | 0 | toggled = TRUE; |
473 | |
|
474 | 0 | if (toggled) |
475 | 0 | { |
476 | 0 | if (toDst && (cur.tm_isdst > 0)) |
477 | 0 | return tm2transitiontime(&cur); |
478 | 0 | else if (!toDst && (cur.tm_isdst == 0)) |
479 | 0 | return tm2transitiontime(&cur); |
480 | 0 | } |
481 | 0 | } |
482 | 0 | return tm2transitiontime(start); |
483 | 0 | } |
484 | | |
485 | | static BOOL get_transition_date(const struct tm* pstart, BOOL toDst, SYSTEMTIME* pdate) |
486 | 213k | { |
487 | 213k | WINPR_ASSERT(pstart); |
488 | 213k | WINPR_ASSERT(pdate); |
489 | | |
490 | 213k | *pdate = tm2transitiontime(nullptr); |
491 | | |
492 | 213k | struct tm start = update_tm(pstart); |
493 | 213k | if (start.tm_isdst < 0) |
494 | 0 | return FALSE; |
495 | | |
496 | 213k | BOOL val = start.tm_isdst > 0; // the year starts with DST or not |
497 | 213k | BOOL toggled = FALSE; |
498 | 213k | struct tm cur = start; |
499 | 213k | struct tm last = cur; |
500 | 77.9M | for (int day = 1; day <= 365; day++) |
501 | 77.7M | { |
502 | 77.7M | last = cur; |
503 | 77.7M | cur = next_day(&cur); |
504 | 77.7M | const BOOL curDst = (cur.tm_isdst > 0); |
505 | 77.7M | if ((val != curDst) && !toggled) |
506 | 0 | toggled = TRUE; |
507 | | |
508 | 77.7M | if (toggled) |
509 | 0 | { |
510 | 0 | if (toDst && curDst) |
511 | 0 | { |
512 | 0 | *pdate = get_transition_time(&last, toDst); |
513 | 0 | return TRUE; |
514 | 0 | } |
515 | 0 | if (!toDst && !curDst) |
516 | 0 | { |
517 | 0 | *pdate = get_transition_time(&last, toDst); |
518 | 0 | return TRUE; |
519 | 0 | } |
520 | 0 | } |
521 | 77.7M | } |
522 | 213k | return FALSE; |
523 | 213k | } |
524 | | |
525 | | static LONG get_bias(const struct tm* start, BOOL dstBias) |
526 | 319k | { |
527 | 319k | if ((start->tm_isdst > 0) && dstBias) |
528 | 0 | return get_gmtoff_min(start); |
529 | 319k | if ((start->tm_isdst == 0) && !dstBias) |
530 | 213k | return get_gmtoff_min(start); |
531 | 106k | if (start->tm_isdst < 0) |
532 | 0 | return get_gmtoff_min(start); |
533 | | |
534 | 106k | struct tm cur = *start; |
535 | 38.9M | for (int day = 1; day <= 365; day++) |
536 | 38.8M | { |
537 | 38.8M | cur = next_day(&cur); |
538 | 38.8M | if ((cur.tm_isdst > 0) && dstBias) |
539 | 0 | return get_gmtoff_min(&cur); |
540 | 38.8M | else if ((cur.tm_isdst == 0) && !dstBias) |
541 | 0 | return get_gmtoff_min(&cur); |
542 | 38.8M | } |
543 | 106k | return 0; |
544 | 106k | } |
545 | | |
546 | | static BOOL map_iana_id(const char* iana, LPDYNAMIC_TIME_ZONE_INFORMATION tz) |
547 | 106k | { |
548 | 106k | const char* winId = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_ID); |
549 | 106k | const char* winStd = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_STANDARD); |
550 | 106k | const char* winDst = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_DAYLIGHT); |
551 | | |
552 | 106k | if (winStd) |
553 | 106k | (void)ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName)); |
554 | 106k | if (winDst) |
555 | 106k | (void)ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName)); |
556 | 106k | if (winId) |
557 | 106k | (void)ConvertUtf8ToWChar(winId, tz->TimeZoneKeyName, ARRAYSIZE(tz->TimeZoneKeyName)); |
558 | | |
559 | 106k | return winId != nullptr; |
560 | 106k | } |
561 | | |
562 | | static const char* weekday2str(WORD wDayOfWeek) |
563 | 0 | { |
564 | 0 | switch (wDayOfWeek) |
565 | 0 | { |
566 | 0 | case 0: |
567 | 0 | return "SUNDAY"; |
568 | 0 | case 1: |
569 | 0 | return "MONDAY"; |
570 | 0 | case 2: |
571 | 0 | return "TUESDAY"; |
572 | 0 | case 3: |
573 | 0 | return "WEDNESDAY"; |
574 | 0 | case 4: |
575 | 0 | return "THURSDAY"; |
576 | 0 | case 5: |
577 | 0 | return "FRIDAY"; |
578 | 0 | case 6: |
579 | 0 | return "SATURDAY"; |
580 | 0 | default: |
581 | 0 | return "DAY-OF-MAGIC"; |
582 | 0 | } |
583 | 0 | } |
584 | | |
585 | | static char* systemtime2str(const SYSTEMTIME* t, char* buffer, size_t len) |
586 | 213k | { |
587 | 213k | const SYSTEMTIME empty = WINPR_C_ARRAY_INIT; |
588 | | |
589 | 213k | if (memcmp(t, &empty, sizeof(SYSTEMTIME)) == 0) |
590 | 213k | (void)_snprintf(buffer, len, "{ not set }"); |
591 | 0 | else |
592 | 0 | { |
593 | 0 | (void)_snprintf(buffer, len, |
594 | 0 | "{ %" PRIu16 "-%" PRIu16 "-%" PRIu16 " [%s] %" PRIu16 ":%" PRIu16 |
595 | 0 | ":%" PRIu16 ".%" PRIu16 "}", |
596 | 0 | t->wYear, t->wMonth, t->wDay, weekday2str(t->wDayOfWeek), t->wHour, |
597 | 0 | t->wMinute, t->wSecond, t->wMilliseconds); |
598 | 0 | } |
599 | 213k | return buffer; |
600 | 213k | } |
601 | | |
602 | | WINPR_ATTR_FORMAT_ARG(6, 7) |
603 | | static void log_print(wLog* log, DWORD level, const char* file, const char* fkt, size_t line, |
604 | | WINPR_FORMAT_ARG const char* fmt, ...) |
605 | 1.27M | { |
606 | 1.27M | if (!WLog_IsLevelActive(log, level)) |
607 | 1.27M | return; |
608 | | |
609 | 0 | va_list ap = WINPR_C_ARRAY_INIT; |
610 | 0 | va_start(ap, fmt); |
611 | 0 | WLog_PrintTextMessageVA(log, level, line, file, fkt, fmt, ap); |
612 | 0 | va_end(ap); |
613 | 0 | } |
614 | | |
615 | 106k | #define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__) |
616 | | static void log_timezone_(const DYNAMIC_TIME_ZONE_INFORMATION* tzif, DWORD result, const char* file, |
617 | | const char* fkt, size_t line) |
618 | 106k | { |
619 | 106k | WINPR_ASSERT(tzif); |
620 | | |
621 | 106k | char buffer[130] = WINPR_C_ARRAY_INIT; |
622 | 106k | DWORD level = WLOG_TRACE; |
623 | 106k | wLog* log = WLog_Get(TAG); |
624 | 106k | log_print(log, level, file, fkt, line, "DYNAMIC_TIME_ZONE_INFORMATION {"); |
625 | | |
626 | 106k | log_print(log, level, file, fkt, line, " Bias=%" PRId32, tzif->Bias); |
627 | 106k | (void)ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer, |
628 | 106k | ARRAYSIZE(buffer)); |
629 | 106k | log_print(log, level, file, fkt, line, " StandardName=%s", buffer); |
630 | 106k | log_print(log, level, file, fkt, line, " StandardDate=%s", |
631 | 106k | systemtime2str(&tzif->StandardDate, buffer, sizeof(buffer))); |
632 | 106k | log_print(log, level, file, fkt, line, " StandardBias=%" PRId32, tzif->StandardBias); |
633 | | |
634 | 106k | (void)ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer, |
635 | 106k | ARRAYSIZE(buffer)); |
636 | 106k | log_print(log, level, file, fkt, line, " DaylightName=%s", buffer); |
637 | 106k | log_print(log, level, file, fkt, line, " DaylightDate=%s", |
638 | 106k | systemtime2str(&tzif->DaylightDate, buffer, sizeof(buffer))); |
639 | 106k | log_print(log, level, file, fkt, line, " DaylightBias=%" PRId32, tzif->DaylightBias); |
640 | 106k | (void)ConvertWCharNToUtf8(tzif->TimeZoneKeyName, ARRAYSIZE(tzif->TimeZoneKeyName), buffer, |
641 | 106k | ARRAYSIZE(buffer)); |
642 | 106k | log_print(log, level, file, fkt, line, " TimeZoneKeyName=%s", buffer); |
643 | 106k | log_print(log, level, file, fkt, line, " DynamicDaylightTimeDisabled=DST-%s", |
644 | 106k | tzif->DynamicDaylightTimeDisabled ? "disabled" : "enabled"); |
645 | 106k | switch (result) |
646 | 106k | { |
647 | 0 | case TIME_ZONE_ID_DAYLIGHT: |
648 | 0 | log_print(log, level, file, fkt, line, " DaylightDate in use"); |
649 | 0 | break; |
650 | 106k | case TIME_ZONE_ID_STANDARD: |
651 | 106k | log_print(log, level, file, fkt, line, " StandardDate in use"); |
652 | 106k | break; |
653 | 0 | default: |
654 | 0 | log_print(log, level, file, fkt, line, " UnknownDate in use"); |
655 | 0 | break; |
656 | 106k | } |
657 | 106k | log_print(log, level, file, fkt, line, "}"); |
658 | 106k | } |
659 | | |
660 | | DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation) |
661 | 53.2k | { |
662 | 53.2k | DYNAMIC_TIME_ZONE_INFORMATION dyn = WINPR_C_ARRAY_INIT; |
663 | 53.2k | DWORD rc = GetDynamicTimeZoneInformation(&dyn); |
664 | 53.2k | lpTimeZoneInformation->Bias = dyn.Bias; |
665 | 53.2k | lpTimeZoneInformation->DaylightBias = dyn.DaylightBias; |
666 | 53.2k | lpTimeZoneInformation->DaylightDate = dyn.DaylightDate; |
667 | 53.2k | lpTimeZoneInformation->StandardBias = dyn.StandardBias; |
668 | 53.2k | lpTimeZoneInformation->StandardDate = dyn.StandardDate; |
669 | 53.2k | memcpy(lpTimeZoneInformation->StandardName, dyn.StandardName, |
670 | 53.2k | sizeof(lpTimeZoneInformation->StandardName)); |
671 | 53.2k | memcpy(lpTimeZoneInformation->DaylightName, dyn.DaylightName, |
672 | 53.2k | sizeof(lpTimeZoneInformation->DaylightName)); |
673 | 53.2k | return rc; |
674 | 53.2k | } |
675 | | |
676 | | BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation) |
677 | 0 | { |
678 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
679 | 0 | return FALSE; |
680 | 0 | } |
681 | | |
682 | | BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime) |
683 | 0 | { |
684 | 0 | WINPR_UNUSED(lpSystemTime); |
685 | 0 | WINPR_UNUSED(lpFileTime); |
686 | 0 | return FALSE; |
687 | 0 | } |
688 | | |
689 | | BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime) |
690 | 0 | { |
691 | 0 | WINPR_UNUSED(lpFileTime); |
692 | 0 | WINPR_UNUSED(lpSystemTime); |
693 | 0 | return FALSE; |
694 | 0 | } |
695 | | |
696 | | BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone, |
697 | | LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime) |
698 | 0 | { |
699 | 0 | WINPR_UNUSED(lpTimeZone); |
700 | 0 | WINPR_UNUSED(lpUniversalTime); |
701 | 0 | WINPR_UNUSED(lpLocalTime); |
702 | 0 | return FALSE; |
703 | 0 | } |
704 | | |
705 | | BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation, |
706 | | LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime) |
707 | 0 | { |
708 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
709 | 0 | WINPR_UNUSED(lpLocalTime); |
710 | 0 | WINPR_UNUSED(lpUniversalTime); |
711 | 0 | return FALSE; |
712 | 0 | } |
713 | | |
714 | | #endif |
715 | | |
716 | | /* |
717 | | * GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A |
718 | | * and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs |
719 | | */ |
720 | | #if !defined(_WIN32) || \ |
721 | | (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \ |
722 | | !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */ |
723 | | |
724 | | typedef enum |
725 | | { |
726 | | HAVE_TRANSITION_DATES = 0, |
727 | | HAVE_NO_STANDARD_TRANSITION_DATE = 1, |
728 | | HAVE_NO_DAYLIGHT_TRANSITION_DATE = 2 |
729 | | } dyn_transition_result; |
730 | | |
731 | | static int dynamic_time_zone_from_localtime(const struct tm* local_time, |
732 | | PDYNAMIC_TIME_ZONE_INFORMATION tz) |
733 | 106k | { |
734 | 106k | WINPR_ASSERT(local_time); |
735 | 106k | WINPR_ASSERT(tz); |
736 | 106k | int rc = HAVE_TRANSITION_DATES; |
737 | | |
738 | 106k | tz->Bias = get_bias(local_time, FALSE); |
739 | | |
740 | | /* If the current time has (or had) DST */ |
741 | 106k | if (local_time->tm_isdst >= 0) |
742 | 106k | { |
743 | | /* DST bias is the difference between standard time and DST in minutes */ |
744 | 106k | const LONG d = get_bias(local_time, TRUE); |
745 | 106k | tz->DaylightBias = -1 * (tz->Bias - d); |
746 | 106k | struct tm newyear = *local_time; |
747 | 106k | newyear.tm_mday = 0; |
748 | 106k | newyear.tm_yday = 0; |
749 | 106k | newyear.tm_wday = 0; |
750 | 106k | newyear.tm_min = 0; |
751 | 106k | newyear.tm_sec = 0; |
752 | 106k | newyear.tm_mon = 0; |
753 | | |
754 | | /* Searching for transition dates. |
755 | | * |
756 | | * For the current DST setting, search from the local_time, for the other one |
757 | | * search from beginning of the year. |
758 | | * We want the dates in the same year, so searching both from local_time might lead to |
759 | | * issues. |
760 | | */ |
761 | 106k | const struct tm* stdtransition = local_time; |
762 | 106k | const struct tm* dsttransition = &newyear; |
763 | 106k | if (local_time->tm_isdst == 0) |
764 | 106k | { |
765 | 106k | stdtransition = &newyear; |
766 | 106k | dsttransition = local_time; |
767 | 106k | } |
768 | 106k | if (!get_transition_date(stdtransition, FALSE, &tz->StandardDate)) |
769 | 106k | { |
770 | 106k | rc |= HAVE_NO_STANDARD_TRANSITION_DATE; |
771 | 106k | tz->StandardBias = 0; |
772 | 106k | } |
773 | 106k | if (!get_transition_date(dsttransition, TRUE, &tz->DaylightDate)) |
774 | 106k | { |
775 | 106k | rc |= HAVE_NO_DAYLIGHT_TRANSITION_DATE; |
776 | 106k | tz->DaylightBias = 0; |
777 | 106k | } |
778 | 106k | } |
779 | 106k | return rc; |
780 | 106k | } |
781 | | |
782 | | DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation) |
783 | 106k | { |
784 | 106k | const char** list = nullptr; |
785 | 106k | char* tzid = nullptr; |
786 | 106k | const char* defaultName = "Client Local Time"; |
787 | 106k | DWORD res = TIME_ZONE_ID_UNKNOWN; |
788 | 106k | const DYNAMIC_TIME_ZONE_INFORMATION empty = WINPR_C_ARRAY_INIT; |
789 | | |
790 | 106k | WINPR_ASSERT(pTimeZoneInformation); |
791 | | |
792 | 106k | *pTimeZoneInformation = empty; |
793 | 106k | (void)ConvertUtf8ToWChar(defaultName, pTimeZoneInformation->StandardName, |
794 | 106k | ARRAYSIZE(pTimeZoneInformation->StandardName)); |
795 | | |
796 | 106k | const time_t t = time(nullptr); |
797 | 106k | struct tm tres = WINPR_C_ARRAY_INIT; |
798 | 106k | struct tm* local_time = localtime_r(&t, &tres); |
799 | 106k | if (!local_time) |
800 | 0 | goto out_error; |
801 | | |
802 | 106k | pTimeZoneInformation->Bias = get_bias(local_time, FALSE); |
803 | 106k | if (local_time->tm_isdst >= 0) |
804 | 106k | dynamic_time_zone_from_localtime(local_time, pTimeZoneInformation); |
805 | | |
806 | 106k | tzid = winpr_guess_time_zone(); |
807 | 106k | if (!map_iana_id(tzid, pTimeZoneInformation)) |
808 | 0 | { |
809 | 0 | const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone, nullptr, 0); |
810 | 0 | list = (const char**)calloc(len, sizeof(const char*)); |
811 | 0 | if (!list) |
812 | 0 | goto out_error; |
813 | 0 | const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len); |
814 | 0 | for (size_t x = 0; x < size; x++) |
815 | 0 | { |
816 | 0 | const char* id = list[x]; |
817 | 0 | if (map_iana_id(id, pTimeZoneInformation)) |
818 | 0 | { |
819 | 0 | res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD; |
820 | 0 | break; |
821 | 0 | } |
822 | 0 | } |
823 | 0 | } |
824 | 106k | else |
825 | 106k | res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD; |
826 | | |
827 | 106k | out_error: |
828 | 106k | free(tzid); |
829 | 106k | free((void*)list); |
830 | | |
831 | 106k | log_timezone(pTimeZoneInformation, res); |
832 | 106k | return res; |
833 | 106k | } |
834 | | |
835 | | BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation) |
836 | 0 | { |
837 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
838 | 0 | return FALSE; |
839 | 0 | } |
840 | | |
841 | | BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi, |
842 | | LPTIME_ZONE_INFORMATION ptzi) |
843 | 0 | { |
844 | 0 | WINPR_UNUSED(wYear); |
845 | 0 | WINPR_UNUSED(pdtzi); |
846 | 0 | WINPR_UNUSED(ptzi); |
847 | 0 | return FALSE; |
848 | 0 | } |
849 | | |
850 | | #endif |
851 | | |
852 | | #if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */ |
853 | | |
854 | | BOOL SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, |
855 | | const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime) |
856 | 0 | { |
857 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
858 | 0 | WINPR_UNUSED(lpUniversalTime); |
859 | 0 | WINPR_UNUSED(lpLocalTime); |
860 | 0 | return FALSE; |
861 | 0 | } |
862 | | |
863 | | BOOL TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, |
864 | | const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime) |
865 | 0 | { |
866 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
867 | 0 | WINPR_UNUSED(lpLocalTime); |
868 | 0 | WINPR_UNUSED(lpUniversalTime); |
869 | 0 | return FALSE; |
870 | 0 | } |
871 | | |
872 | | #endif |
873 | | |
874 | | #if !defined(_WIN32) |
875 | | |
876 | | DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex, |
877 | | PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation) |
878 | 0 | { |
879 | 0 | if (!lpTimeZoneInformation) |
880 | 0 | return ERROR_INVALID_PARAMETER; |
881 | 0 | const DYNAMIC_TIME_ZONE_INFORMATION empty = WINPR_C_ARRAY_INIT; |
882 | 0 | *lpTimeZoneInformation = empty; |
883 | |
|
884 | 0 | const TimeZoneNameMapEntry* entry = TimeZoneGetAt(dwIndex); |
885 | 0 | if (!entry) |
886 | 0 | return ERROR_NO_MORE_ITEMS; |
887 | | |
888 | 0 | if (entry->DaylightName) |
889 | 0 | (void)ConvertUtf8ToWChar(entry->DaylightName, lpTimeZoneInformation->DaylightName, |
890 | 0 | ARRAYSIZE(lpTimeZoneInformation->DaylightName)); |
891 | 0 | if (entry->StandardName) |
892 | 0 | (void)ConvertUtf8ToWChar(entry->StandardName, lpTimeZoneInformation->StandardName, |
893 | 0 | ARRAYSIZE(lpTimeZoneInformation->StandardName)); |
894 | 0 | if (entry->Id) |
895 | 0 | (void)ConvertUtf8ToWChar(entry->Id, lpTimeZoneInformation->TimeZoneKeyName, |
896 | 0 | ARRAYSIZE(lpTimeZoneInformation->TimeZoneKeyName)); |
897 | |
|
898 | 0 | const time_t t = time(nullptr); |
899 | 0 | struct tm tres = WINPR_C_ARRAY_INIT; |
900 | |
|
901 | 0 | char* tzcopy = entry->Iana ? setNewAndSaveOldTZ(entry->Iana) : nullptr; |
902 | |
|
903 | 0 | struct tm* local_time = localtime_r(&t, &tres); |
904 | |
|
905 | 0 | if (local_time) |
906 | 0 | dynamic_time_zone_from_localtime(local_time, lpTimeZoneInformation); |
907 | |
|
908 | 0 | if (entry->Iana) |
909 | 0 | restoreSavedTZ(tzcopy); |
910 | |
|
911 | 0 | return ERROR_SUCCESS; |
912 | 0 | } |
913 | | |
914 | | // NOLINTBEGIN(readability-non-const-parameter) |
915 | | DWORD GetDynamicTimeZoneInformationEffectiveYears( |
916 | | const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear) |
917 | | // NOLINTEND(readability-non-const-parameter) |
918 | 0 | { |
919 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
920 | 0 | WINPR_UNUSED(FirstYear); |
921 | 0 | WINPR_UNUSED(LastYear); |
922 | 0 | return ERROR_FILE_NOT_FOUND; |
923 | 0 | } |
924 | | |
925 | | #elif _WIN32_WINNT < 0x0602 /* Windows 8 */ |
926 | | DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex, |
927 | | PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation) |
928 | | { |
929 | | WINPR_UNUSED(dwIndex); |
930 | | WINPR_UNUSED(lpTimeZoneInformation); |
931 | | return ERROR_NO_MORE_ITEMS; |
932 | | } |
933 | | |
934 | | DWORD GetDynamicTimeZoneInformationEffectiveYears( |
935 | | const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear) |
936 | | { |
937 | | WINPR_UNUSED(lpTimeZoneInformation); |
938 | | WINPR_UNUSED(FirstYear); |
939 | | WINPR_UNUSED(LastYear); |
940 | | return ERROR_FILE_NOT_FOUND; |
941 | | } |
942 | | #endif |
943 | | |
944 | | #if !defined(_WIN32) |
945 | | char* setNewAndSaveOldTZ(const char* val) |
946 | 0 | { |
947 | | // NOLINTBEGIN(concurrency-mt-unsafe) |
948 | 0 | const char* otz = getenv("TZ"); |
949 | 0 | char* oldtz = nullptr; |
950 | 0 | if (otz) |
951 | 0 | oldtz = _strdup(otz); |
952 | 0 | setenv("TZ", val, 1); |
953 | 0 | tzset(); |
954 | | // NOLINTEND(concurrency-mt-unsafe) |
955 | 0 | return oldtz; |
956 | 0 | } |
957 | | |
958 | | void restoreSavedTZ(char* saved) |
959 | 0 | { |
960 | | // NOLINTBEGIN(concurrency-mt-unsafe) |
961 | 0 | if (saved) |
962 | 0 | { |
963 | 0 | setenv("TZ", saved, 1); |
964 | 0 | free(saved); |
965 | 0 | } |
966 | 0 | else |
967 | 0 | unsetenv("TZ"); |
968 | 0 | tzset(); |
969 | | // NOLINTEND(concurrency-mt-unsafe) |
970 | 0 | } |
971 | | #endif |