/src/FreeRDP/winpr/libwinpr/timezone/timezone.c
Line | Count | Source (jump to first uncovered line) |
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 | 0 | #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 | 0 | { |
48 | 0 | const INT CHUNK_SIZE = 32; |
49 | 0 | size_t rc = 0; |
50 | 0 | size_t read = 0; |
51 | 0 | size_t length = CHUNK_SIZE; |
52 | |
|
53 | 0 | char* tzid = malloc(length); |
54 | 0 | if (!tzid) |
55 | 0 | return NULL; |
56 | | |
57 | 0 | do |
58 | 0 | { |
59 | 0 | rc = fread(tzid + read, 1, length - read - 1UL, fp); |
60 | 0 | if (rc > 0) |
61 | 0 | read += rc; |
62 | |
|
63 | 0 | if (read < (length - 1UL)) |
64 | 0 | break; |
65 | | |
66 | 0 | if (read > length - 1UL) |
67 | 0 | { |
68 | 0 | free(tzid); |
69 | 0 | return NULL; |
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 NULL; |
78 | 0 | } |
79 | | |
80 | 0 | tzid = tmp; |
81 | 0 | } while (rc > 0); |
82 | | |
83 | 0 | if (ferror(fp)) |
84 | 0 | { |
85 | 0 | free(tzid); |
86 | 0 | return NULL; |
87 | 0 | } |
88 | | |
89 | 0 | tzid[read] = '\0'; |
90 | 0 | if (read > 0) |
91 | 0 | { |
92 | 0 | if (tzid[read - 1] == '\n') |
93 | 0 | tzid[read - 1] = '\0'; |
94 | 0 | } |
95 | |
|
96 | 0 | return tzid; |
97 | 0 | } |
98 | | |
99 | | static char* winpr_get_timezone_from_link(const char* links[], size_t count) |
100 | 0 | { |
101 | 0 | const char* _links[] = { "/etc/localtime", "/etc/TZ" }; |
102 | |
|
103 | 0 | if (links == NULL) |
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 | 0 | for (size_t x = 0; x < count; x++) |
117 | 0 | { |
118 | 0 | char* tzid = NULL; |
119 | 0 | const char* link = links[x]; |
120 | 0 | char* buf = realpath(link, NULL); |
121 | |
|
122 | 0 | if (buf) |
123 | 0 | { |
124 | 0 | size_t sep = 0; |
125 | 0 | size_t alloc = 0; |
126 | 0 | size_t pos = 0; |
127 | 0 | size_t len = pos = strlen(buf); |
128 | | |
129 | | /* find the position of the 2nd to last "/" */ |
130 | 0 | for (size_t i = 1; i <= len; i++) |
131 | 0 | { |
132 | 0 | const size_t curpos = len - i; |
133 | 0 | const char cur = buf[curpos]; |
134 | |
|
135 | 0 | if (cur == '/') |
136 | 0 | sep++; |
137 | 0 | if (sep >= 2) |
138 | 0 | { |
139 | 0 | alloc = i; |
140 | 0 | pos = len - i + 1; |
141 | 0 | break; |
142 | 0 | } |
143 | 0 | } |
144 | |
|
145 | 0 | if ((len == 0) || (sep != 2)) |
146 | 0 | goto end; |
147 | | |
148 | 0 | tzid = (char*)calloc(alloc + 1, sizeof(char)); |
149 | |
|
150 | 0 | if (!tzid) |
151 | 0 | goto end; |
152 | | |
153 | 0 | strncpy(tzid, &buf[pos], alloc); |
154 | 0 | WLog_DBG(TAG, "tzid: %s", tzid); |
155 | 0 | goto end; |
156 | 0 | } |
157 | | |
158 | 0 | end: |
159 | 0 | free(buf); |
160 | 0 | if (tzid) |
161 | 0 | return tzid; |
162 | 0 | } |
163 | | |
164 | 0 | return NULL; |
165 | 0 | } |
166 | | |
167 | | #if defined(ANDROID) |
168 | | #include "../utils/android.h" |
169 | | |
170 | | static char* winpr_get_android_timezone_identifier(void) |
171 | | { |
172 | | char* tzid = NULL; |
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, NULL); |
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 | 0 | { |
242 | | #if defined(ANDROID) |
243 | | return winpr_get_android_timezone_identifier(); |
244 | | #else |
245 | 0 | FILE* fp = NULL; |
246 | 0 | char* tzid = NULL; |
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 | 0 | fp = winpr_fopen(WINPR_TIMEZONE_FILE, "r"); |
252 | 0 | #endif |
253 | |
|
254 | 0 | if (NULL == fp) |
255 | 0 | return NULL; |
256 | | |
257 | 0 | tzid = winpr_read_unix_timezone_identifier_from_file(fp); |
258 | 0 | (void)fclose(fp); |
259 | 0 | if (tzid != NULL) |
260 | 0 | WLog_DBG(TAG, "tzid: %s", tzid); |
261 | 0 | return tzid; |
262 | 0 | #endif |
263 | 0 | } |
264 | | |
265 | | static char* winpr_time_zone_from_env(void) |
266 | 0 | { |
267 | 0 | LPCSTR tz = "TZ"; |
268 | 0 | char* tzid = NULL; |
269 | |
|
270 | 0 | DWORD nSize = GetEnvironmentVariableA(tz, NULL, 0); |
271 | 0 | 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 | 0 | return tzid; |
286 | | |
287 | 0 | fail: |
288 | 0 | free(tzid); |
289 | 0 | return NULL; |
290 | 0 | } |
291 | | |
292 | | static char* winpr_translate_time_zone(const char* tzid) |
293 | 0 | { |
294 | 0 | const char* zipath = "/usr/share/zoneinfo/"; |
295 | 0 | char* buf = NULL; |
296 | 0 | const char* links[] = { buf }; |
297 | |
|
298 | 0 | if (!tzid) |
299 | 0 | return NULL; |
300 | | |
301 | 0 | if (tzid[0] == '/') |
302 | 0 | { |
303 | | /* Full path given in TZ */ |
304 | 0 | links[0] = tzid; |
305 | 0 | } |
306 | 0 | else |
307 | 0 | { |
308 | 0 | size_t bsize = 0; |
309 | 0 | winpr_asprintf(&buf, &bsize, "%s%s", zipath, tzid); |
310 | 0 | links[0] = buf; |
311 | 0 | } |
312 | |
|
313 | 0 | char* ntzid = winpr_get_timezone_from_link(links, 1); |
314 | 0 | free(buf); |
315 | 0 | return ntzid; |
316 | 0 | } |
317 | | |
318 | | static char* winpr_guess_time_zone(void) |
319 | 0 | { |
320 | 0 | char* tzid = winpr_time_zone_from_env(); |
321 | 0 | if (tzid) |
322 | 0 | goto end; |
323 | 0 | tzid = winpr_get_unix_timezone_identifier_from_file(); |
324 | 0 | if (tzid) |
325 | 0 | goto end; |
326 | 0 | tzid = winpr_get_timezone_from_link(NULL, 0); |
327 | 0 | if (tzid) |
328 | 0 | goto end; |
329 | | |
330 | 0 | end: |
331 | 0 | { |
332 | 0 | char* ntzid = winpr_translate_time_zone(tzid); |
333 | 0 | if (ntzid) |
334 | 0 | { |
335 | 0 | free(tzid); |
336 | 0 | return ntzid; |
337 | 0 | } |
338 | 0 | return tzid; |
339 | 0 | } |
340 | 0 | } |
341 | | |
342 | | static SYSTEMTIME tm2systemtime(const struct tm* t) |
343 | 0 | { |
344 | 0 | SYSTEMTIME st = { 0 }; |
345 | |
|
346 | 0 | 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 | 0 | return st; |
358 | 0 | } |
359 | | |
360 | | static struct tm systemtime2tm(const SYSTEMTIME* st) |
361 | 0 | { |
362 | 0 | struct tm t = { 0 }; |
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 | 0 | { |
380 | 0 | WINPR_ASSERT(t); |
381 | 0 | return -(LONG)(t->tm_gmtoff / 60l); |
382 | 0 | } |
383 | | |
384 | | static struct tm next_day(const struct tm* start) |
385 | 0 | { |
386 | 0 | struct tm cur = *start; |
387 | 0 | cur.tm_hour = 0; |
388 | 0 | cur.tm_min = 0; |
389 | 0 | cur.tm_sec = 0; |
390 | 0 | cur.tm_isdst = -1; |
391 | 0 | cur.tm_mday++; |
392 | 0 | const time_t t = mktime(&cur); |
393 | 0 | (void)localtime_r(&t, &cur); |
394 | 0 | return cur; |
395 | 0 | } |
396 | | |
397 | | static struct tm adjust_time(const struct tm* start, int hour, int minute) |
398 | 0 | { |
399 | 0 | struct tm cur = *start; |
400 | 0 | cur.tm_hour = hour; |
401 | 0 | cur.tm_min = minute; |
402 | 0 | cur.tm_sec = 0; |
403 | 0 | cur.tm_isdst = -1; |
404 | 0 | const time_t t = mktime(&cur); |
405 | 0 | (void)localtime_r(&t, &cur); |
406 | 0 | return cur; |
407 | 0 | } |
408 | | |
409 | | /* [MS-RDPBCGR] 2.2.1.11.1.1.1.1.1 System Time (TS_SYSTEMTIME) */ |
410 | | static WORD get_transition_weekday_occurrence(const SYSTEMTIME* st) |
411 | 0 | { |
412 | 0 | WORD count = 0; |
413 | 0 | struct tm start = systemtime2tm(st); |
414 | |
|
415 | 0 | WORD last = 0; |
416 | 0 | struct tm next = start; |
417 | 0 | next.tm_mday = 1; |
418 | 0 | next.tm_isdst = -1; |
419 | 0 | do |
420 | 0 | { |
421 | |
|
422 | 0 | const time_t t = mktime(&next); |
423 | 0 | next.tm_mday++; |
424 | |
|
425 | 0 | struct tm cur = { 0 }; |
426 | 0 | (void)localtime_r(&t, &cur); |
427 | |
|
428 | 0 | if (cur.tm_mon + 1 != st->wMonth) |
429 | 0 | break; |
430 | | |
431 | 0 | if (cur.tm_wday == st->wDayOfWeek) |
432 | 0 | { |
433 | 0 | if (cur.tm_mday <= st->wDay) |
434 | 0 | count++; |
435 | 0 | last++; |
436 | 0 | } |
437 | |
|
438 | 0 | } while (TRUE); |
439 | | |
440 | 0 | if (count == last) |
441 | 0 | count = 5; |
442 | |
|
443 | 0 | return count; |
444 | 0 | } |
445 | | |
446 | | static SYSTEMTIME tm2transitiontime(const struct tm* cur) |
447 | 0 | { |
448 | 0 | SYSTEMTIME t = tm2systemtime(cur); |
449 | 0 | if (cur) |
450 | 0 | { |
451 | 0 | t.wDay = get_transition_weekday_occurrence(&t); |
452 | 0 | t.wYear = 0; |
453 | 0 | } |
454 | |
|
455 | 0 | return t; |
456 | 0 | } |
457 | | |
458 | | static SYSTEMTIME get_transition_time(const struct tm* start, BOOL toDst) |
459 | 0 | { |
460 | 0 | BOOL toggled = FALSE; |
461 | 0 | struct tm first = adjust_time(start, 0, 0); |
462 | 0 | for (int hour = 0; hour < 24; hour++) |
463 | 0 | { |
464 | 0 | struct tm cur = adjust_time(start, hour, 0); |
465 | 0 | if (cur.tm_isdst != first.tm_isdst) |
466 | 0 | toggled = TRUE; |
467 | |
|
468 | 0 | if (toggled) |
469 | 0 | { |
470 | 0 | if (toDst && (cur.tm_isdst > 0)) |
471 | 0 | return tm2transitiontime(&cur); |
472 | 0 | else if (!toDst && (cur.tm_isdst == 0)) |
473 | 0 | return tm2transitiontime(&cur); |
474 | 0 | } |
475 | 0 | } |
476 | 0 | return tm2transitiontime(start); |
477 | 0 | } |
478 | | |
479 | | static BOOL get_transition_date(const struct tm* start, BOOL toDst, SYSTEMTIME* pdate) |
480 | 0 | { |
481 | 0 | WINPR_ASSERT(start); |
482 | 0 | WINPR_ASSERT(pdate); |
483 | | |
484 | 0 | *pdate = tm2transitiontime(NULL); |
485 | |
|
486 | 0 | if (start->tm_isdst < 0) |
487 | 0 | return FALSE; |
488 | | |
489 | 0 | BOOL val = start->tm_isdst > 0; // the year starts with DST or not |
490 | 0 | BOOL toggled = FALSE; |
491 | 0 | struct tm cur = *start; |
492 | 0 | struct tm last = cur; |
493 | 0 | for (int day = 1; day <= 365; day++) |
494 | 0 | { |
495 | 0 | last = cur; |
496 | 0 | cur = next_day(&cur); |
497 | 0 | const BOOL curDst = (cur.tm_isdst > 0); |
498 | 0 | if ((val != curDst) && !toggled) |
499 | 0 | toggled = TRUE; |
500 | |
|
501 | 0 | if (toggled) |
502 | 0 | { |
503 | 0 | if (toDst && curDst) |
504 | 0 | { |
505 | 0 | *pdate = get_transition_time(&last, toDst); |
506 | 0 | return TRUE; |
507 | 0 | } |
508 | 0 | if (!toDst && !curDst) |
509 | 0 | { |
510 | 0 | *pdate = get_transition_time(&last, toDst); |
511 | 0 | return TRUE; |
512 | 0 | } |
513 | 0 | } |
514 | 0 | } |
515 | 0 | return FALSE; |
516 | 0 | } |
517 | | |
518 | | static LONG get_bias(const struct tm* start, BOOL dstBias) |
519 | 0 | { |
520 | 0 | if ((start->tm_isdst > 0) && dstBias) |
521 | 0 | return get_gmtoff_min(start); |
522 | 0 | if ((start->tm_isdst == 0) && !dstBias) |
523 | 0 | return get_gmtoff_min(start); |
524 | 0 | if (start->tm_isdst < 0) |
525 | 0 | return get_gmtoff_min(start); |
526 | | |
527 | 0 | struct tm cur = *start; |
528 | 0 | for (int day = 1; day <= 365; day++) |
529 | 0 | { |
530 | 0 | cur = next_day(&cur); |
531 | 0 | if ((cur.tm_isdst > 0) && dstBias) |
532 | 0 | return get_gmtoff_min(&cur); |
533 | 0 | else if ((cur.tm_isdst == 0) && !dstBias) |
534 | 0 | return get_gmtoff_min(&cur); |
535 | 0 | } |
536 | 0 | return 0; |
537 | 0 | } |
538 | | |
539 | | static BOOL map_iana_id(const char* iana, LPDYNAMIC_TIME_ZONE_INFORMATION tz) |
540 | 0 | { |
541 | 0 | const char* winId = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_ID); |
542 | 0 | const char* winStd = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_STANDARD); |
543 | 0 | const char* winDst = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_DAYLIGHT); |
544 | |
|
545 | 0 | if (winStd) |
546 | 0 | (void)ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName)); |
547 | 0 | if (winDst) |
548 | 0 | (void)ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName)); |
549 | 0 | if (winId) |
550 | 0 | (void)ConvertUtf8ToWChar(winId, tz->TimeZoneKeyName, ARRAYSIZE(tz->TimeZoneKeyName)); |
551 | |
|
552 | 0 | return winId != NULL; |
553 | 0 | } |
554 | | |
555 | | static const char* weekday2str(WORD wDayOfWeek) |
556 | 0 | { |
557 | 0 | switch (wDayOfWeek) |
558 | 0 | { |
559 | 0 | case 0: |
560 | 0 | return "SUNDAY"; |
561 | 0 | case 1: |
562 | 0 | return "MONDAY"; |
563 | 0 | case 2: |
564 | 0 | return "TUESDAY"; |
565 | 0 | case 3: |
566 | 0 | return "WEDNESDAY"; |
567 | 0 | case 4: |
568 | 0 | return "THURSDAY"; |
569 | 0 | case 5: |
570 | 0 | return "FRIDAY"; |
571 | 0 | case 6: |
572 | 0 | return "SATURDAY"; |
573 | 0 | default: |
574 | 0 | return "DAY-OF-MAGIC"; |
575 | 0 | } |
576 | 0 | } |
577 | | |
578 | | static char* systemtime2str(const SYSTEMTIME* t, char* buffer, size_t len) |
579 | 0 | { |
580 | 0 | const SYSTEMTIME empty = { 0 }; |
581 | |
|
582 | 0 | if (memcmp(t, &empty, sizeof(SYSTEMTIME)) == 0) |
583 | 0 | (void)_snprintf(buffer, len, "{ not set }"); |
584 | 0 | else |
585 | 0 | { |
586 | 0 | (void)_snprintf(buffer, len, |
587 | 0 | "{ %" PRIu16 "-%" PRIu16 "-%" PRIu16 " [%s] %" PRIu16 ":%" PRIu16 |
588 | 0 | ":%" PRIu16 ".%" PRIu16 "}", |
589 | 0 | t->wYear, t->wMonth, t->wDay, weekday2str(t->wDayOfWeek), t->wHour, |
590 | 0 | t->wMinute, t->wSecond, t->wMilliseconds); |
591 | 0 | } |
592 | 0 | return buffer; |
593 | 0 | } |
594 | | |
595 | | static void log_print(wLog* log, DWORD level, const char* file, const char* fkt, size_t line, ...) |
596 | 0 | { |
597 | 0 | if (!WLog_IsLevelActive(log, level)) |
598 | 0 | return; |
599 | | |
600 | 0 | va_list ap = { 0 }; |
601 | 0 | va_start(ap, line); |
602 | 0 | WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, ap); |
603 | 0 | va_end(ap); |
604 | 0 | } |
605 | | |
606 | 0 | #define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__) |
607 | | static void log_timezone_(const DYNAMIC_TIME_ZONE_INFORMATION* tzif, DWORD result, const char* file, |
608 | | const char* fkt, size_t line) |
609 | 0 | { |
610 | 0 | WINPR_ASSERT(tzif); |
611 | | |
612 | 0 | char buffer[130] = { 0 }; |
613 | 0 | DWORD level = WLOG_TRACE; |
614 | 0 | wLog* log = WLog_Get(TAG); |
615 | 0 | log_print(log, level, file, fkt, line, "DYNAMIC_TIME_ZONE_INFORMATION {"); |
616 | |
|
617 | 0 | log_print(log, level, file, fkt, line, " Bias=%" PRIu32, tzif->Bias); |
618 | 0 | (void)ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer, |
619 | 0 | ARRAYSIZE(buffer)); |
620 | 0 | log_print(log, level, file, fkt, line, " StandardName=%s", buffer); |
621 | 0 | log_print(log, level, file, fkt, line, " StandardDate=%s", |
622 | 0 | systemtime2str(&tzif->StandardDate, buffer, sizeof(buffer))); |
623 | 0 | log_print(log, level, file, fkt, line, " StandardBias=%" PRIu32, tzif->StandardBias); |
624 | |
|
625 | 0 | (void)ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer, |
626 | 0 | ARRAYSIZE(buffer)); |
627 | 0 | log_print(log, level, file, fkt, line, " DaylightName=%s", buffer); |
628 | 0 | log_print(log, level, file, fkt, line, " DaylightDate=%s", |
629 | 0 | systemtime2str(&tzif->DaylightDate, buffer, sizeof(buffer))); |
630 | 0 | log_print(log, level, file, fkt, line, " DaylightBias=%" PRIu32, tzif->DaylightBias); |
631 | 0 | (void)ConvertWCharNToUtf8(tzif->TimeZoneKeyName, ARRAYSIZE(tzif->TimeZoneKeyName), buffer, |
632 | 0 | ARRAYSIZE(buffer)); |
633 | 0 | log_print(log, level, file, fkt, line, " TimeZoneKeyName=%s", buffer); |
634 | 0 | log_print(log, level, file, fkt, line, " DynamicDaylightTimeDisabled=DST-%s", |
635 | 0 | tzif->DynamicDaylightTimeDisabled ? "disabled" : "enabled"); |
636 | 0 | switch (result) |
637 | 0 | { |
638 | 0 | case TIME_ZONE_ID_DAYLIGHT: |
639 | 0 | log_print(log, level, file, fkt, line, " DaylightDate in use"); |
640 | 0 | break; |
641 | 0 | case TIME_ZONE_ID_STANDARD: |
642 | 0 | log_print(log, level, file, fkt, line, " StandardDate in use"); |
643 | 0 | break; |
644 | 0 | default: |
645 | 0 | log_print(log, level, file, fkt, line, " UnknownDate in use"); |
646 | 0 | break; |
647 | 0 | } |
648 | 0 | log_print(log, level, file, fkt, line, "}"); |
649 | 0 | } |
650 | | |
651 | | DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation) |
652 | 0 | { |
653 | 0 | DYNAMIC_TIME_ZONE_INFORMATION dyn = { 0 }; |
654 | 0 | DWORD rc = GetDynamicTimeZoneInformation(&dyn); |
655 | 0 | lpTimeZoneInformation->Bias = dyn.Bias; |
656 | 0 | lpTimeZoneInformation->DaylightBias = dyn.DaylightBias; |
657 | 0 | lpTimeZoneInformation->DaylightDate = dyn.DaylightDate; |
658 | 0 | lpTimeZoneInformation->StandardBias = dyn.StandardBias; |
659 | 0 | lpTimeZoneInformation->StandardDate = dyn.StandardDate; |
660 | 0 | memcpy(lpTimeZoneInformation->StandardName, dyn.StandardName, |
661 | 0 | sizeof(lpTimeZoneInformation->StandardName)); |
662 | 0 | memcpy(lpTimeZoneInformation->DaylightName, dyn.DaylightName, |
663 | 0 | sizeof(lpTimeZoneInformation->DaylightName)); |
664 | 0 | return rc; |
665 | 0 | } |
666 | | |
667 | | BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation) |
668 | 0 | { |
669 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
670 | 0 | return FALSE; |
671 | 0 | } |
672 | | |
673 | | BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime) |
674 | 0 | { |
675 | 0 | WINPR_UNUSED(lpSystemTime); |
676 | 0 | WINPR_UNUSED(lpFileTime); |
677 | 0 | return FALSE; |
678 | 0 | } |
679 | | |
680 | | BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime) |
681 | 0 | { |
682 | 0 | WINPR_UNUSED(lpFileTime); |
683 | 0 | WINPR_UNUSED(lpSystemTime); |
684 | 0 | return FALSE; |
685 | 0 | } |
686 | | |
687 | | BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone, |
688 | | LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime) |
689 | 0 | { |
690 | 0 | WINPR_UNUSED(lpTimeZone); |
691 | 0 | WINPR_UNUSED(lpUniversalTime); |
692 | 0 | WINPR_UNUSED(lpLocalTime); |
693 | 0 | return FALSE; |
694 | 0 | } |
695 | | |
696 | | BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation, |
697 | | LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime) |
698 | 0 | { |
699 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
700 | 0 | WINPR_UNUSED(lpLocalTime); |
701 | 0 | WINPR_UNUSED(lpUniversalTime); |
702 | 0 | return FALSE; |
703 | 0 | } |
704 | | |
705 | | #endif |
706 | | |
707 | | /* |
708 | | * GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A |
709 | | * and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs |
710 | | */ |
711 | | #if !defined(_WIN32) || \ |
712 | | (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \ |
713 | | !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */ |
714 | | |
715 | | typedef enum |
716 | | { |
717 | | HAVE_TRANSITION_DATES = 0, |
718 | | HAVE_NO_STANDARD_TRANSITION_DATE = 1, |
719 | | HAVE_NO_DAYLIGHT_TRANSITION_DATE = 2 |
720 | | } dyn_transition_result; |
721 | | |
722 | | static int dynamic_time_zone_from_localtime(const struct tm* local_time, |
723 | | PDYNAMIC_TIME_ZONE_INFORMATION tz) |
724 | 0 | { |
725 | 0 | WINPR_ASSERT(local_time); |
726 | 0 | WINPR_ASSERT(tz); |
727 | 0 | int rc = HAVE_TRANSITION_DATES; |
728 | |
|
729 | 0 | tz->Bias = get_bias(local_time, FALSE); |
730 | | |
731 | | /* If the current time has (or had) DST */ |
732 | 0 | if (local_time->tm_isdst >= 0) |
733 | 0 | { |
734 | | /* DST bias is the difference between standard time and DST in minutes */ |
735 | 0 | const LONG d = get_bias(local_time, TRUE); |
736 | 0 | tz->DaylightBias = -1 * (tz->Bias - d); |
737 | 0 | if (!get_transition_date(local_time, FALSE, &tz->StandardDate)) |
738 | 0 | { |
739 | 0 | rc |= HAVE_NO_STANDARD_TRANSITION_DATE; |
740 | 0 | tz->StandardBias = 0; |
741 | 0 | } |
742 | 0 | if (!get_transition_date(local_time, TRUE, &tz->DaylightDate)) |
743 | 0 | { |
744 | 0 | rc |= HAVE_NO_DAYLIGHT_TRANSITION_DATE; |
745 | 0 | tz->DaylightBias = 0; |
746 | 0 | } |
747 | 0 | } |
748 | 0 | return rc; |
749 | 0 | } |
750 | | |
751 | | DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation) |
752 | 0 | { |
753 | 0 | const char** list = NULL; |
754 | 0 | char* tzid = NULL; |
755 | 0 | const char* defaultName = "Client Local Time"; |
756 | 0 | DWORD res = TIME_ZONE_ID_UNKNOWN; |
757 | 0 | const DYNAMIC_TIME_ZONE_INFORMATION empty = { 0 }; |
758 | |
|
759 | 0 | WINPR_ASSERT(pTimeZoneInformation); |
760 | | |
761 | 0 | *pTimeZoneInformation = empty; |
762 | 0 | (void)ConvertUtf8ToWChar(defaultName, pTimeZoneInformation->StandardName, |
763 | 0 | ARRAYSIZE(pTimeZoneInformation->StandardName)); |
764 | |
|
765 | 0 | const time_t t = time(NULL); |
766 | 0 | struct tm tres = { 0 }; |
767 | 0 | struct tm* local_time = localtime_r(&t, &tres); |
768 | 0 | if (!local_time) |
769 | 0 | goto out_error; |
770 | | |
771 | 0 | pTimeZoneInformation->Bias = get_bias(local_time, FALSE); |
772 | 0 | if (local_time->tm_isdst >= 0) |
773 | 0 | dynamic_time_zone_from_localtime(local_time, pTimeZoneInformation); |
774 | |
|
775 | 0 | tzid = winpr_guess_time_zone(); |
776 | 0 | if (!map_iana_id(tzid, pTimeZoneInformation)) |
777 | 0 | { |
778 | 0 | const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone, NULL, 0); |
779 | 0 | list = (const char**)calloc(len, sizeof(const char*)); |
780 | 0 | if (!list) |
781 | 0 | goto out_error; |
782 | 0 | const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len); |
783 | 0 | for (size_t x = 0; x < size; x++) |
784 | 0 | { |
785 | 0 | const char* id = list[x]; |
786 | 0 | if (map_iana_id(id, pTimeZoneInformation)) |
787 | 0 | { |
788 | 0 | res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD; |
789 | 0 | break; |
790 | 0 | } |
791 | 0 | } |
792 | 0 | } |
793 | 0 | else |
794 | 0 | res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD; |
795 | | |
796 | 0 | out_error: |
797 | 0 | free(tzid); |
798 | 0 | free((void*)list); |
799 | |
|
800 | 0 | log_timezone(pTimeZoneInformation, res); |
801 | 0 | return res; |
802 | 0 | } |
803 | | |
804 | | BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation) |
805 | 0 | { |
806 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
807 | 0 | return FALSE; |
808 | 0 | } |
809 | | |
810 | | BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi, |
811 | | LPTIME_ZONE_INFORMATION ptzi) |
812 | 0 | { |
813 | 0 | WINPR_UNUSED(wYear); |
814 | 0 | WINPR_UNUSED(pdtzi); |
815 | 0 | WINPR_UNUSED(ptzi); |
816 | 0 | return FALSE; |
817 | 0 | } |
818 | | |
819 | | #endif |
820 | | |
821 | | #if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */ |
822 | | |
823 | | BOOL SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, |
824 | | const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime) |
825 | 0 | { |
826 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
827 | 0 | WINPR_UNUSED(lpUniversalTime); |
828 | 0 | WINPR_UNUSED(lpLocalTime); |
829 | 0 | return FALSE; |
830 | 0 | } |
831 | | |
832 | | BOOL TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, |
833 | | const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime) |
834 | 0 | { |
835 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
836 | 0 | WINPR_UNUSED(lpLocalTime); |
837 | 0 | WINPR_UNUSED(lpUniversalTime); |
838 | 0 | return FALSE; |
839 | 0 | } |
840 | | |
841 | | #endif |
842 | | |
843 | | #if !defined(_WIN32) |
844 | | |
845 | | DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex, |
846 | | PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation) |
847 | 0 | { |
848 | 0 | if (!lpTimeZoneInformation) |
849 | 0 | return ERROR_INVALID_PARAMETER; |
850 | 0 | const DYNAMIC_TIME_ZONE_INFORMATION empty = { 0 }; |
851 | 0 | *lpTimeZoneInformation = empty; |
852 | |
|
853 | 0 | const TimeZoneNameMapEntry* entry = TimeZoneGetAt(dwIndex); |
854 | 0 | if (!entry) |
855 | 0 | return ERROR_NO_MORE_ITEMS; |
856 | | |
857 | 0 | if (entry->DaylightName) |
858 | 0 | (void)ConvertUtf8ToWChar(entry->DaylightName, lpTimeZoneInformation->DaylightName, |
859 | 0 | ARRAYSIZE(lpTimeZoneInformation->DaylightName)); |
860 | 0 | if (entry->StandardName) |
861 | 0 | (void)ConvertUtf8ToWChar(entry->StandardName, lpTimeZoneInformation->StandardName, |
862 | 0 | ARRAYSIZE(lpTimeZoneInformation->StandardName)); |
863 | 0 | if (entry->Id) |
864 | 0 | (void)ConvertUtf8ToWChar(entry->Id, lpTimeZoneInformation->TimeZoneKeyName, |
865 | 0 | ARRAYSIZE(lpTimeZoneInformation->TimeZoneKeyName)); |
866 | |
|
867 | 0 | const time_t t = time(NULL); |
868 | 0 | struct tm tres = { 0 }; |
869 | |
|
870 | 0 | char* tzcopy = entry->Iana ? setNewAndSaveOldTZ(entry->Iana) : NULL; |
871 | |
|
872 | 0 | struct tm* local_time = localtime_r(&t, &tres); |
873 | |
|
874 | 0 | if (local_time) |
875 | 0 | dynamic_time_zone_from_localtime(local_time, lpTimeZoneInformation); |
876 | |
|
877 | 0 | if (entry->Iana) |
878 | 0 | restoreSavedTZ(tzcopy); |
879 | |
|
880 | 0 | return ERROR_SUCCESS; |
881 | 0 | } |
882 | | |
883 | | // NOLINTBEGIN(readability-non-const-parameter) |
884 | | DWORD GetDynamicTimeZoneInformationEffectiveYears( |
885 | | const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear) |
886 | | // NOLINTEND(readability-non-const-parameter) |
887 | 0 | { |
888 | 0 | WINPR_UNUSED(lpTimeZoneInformation); |
889 | 0 | WINPR_UNUSED(FirstYear); |
890 | 0 | WINPR_UNUSED(LastYear); |
891 | 0 | return ERROR_FILE_NOT_FOUND; |
892 | 0 | } |
893 | | |
894 | | #elif _WIN32_WINNT < 0x0602 /* Windows 8 */ |
895 | | DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex, |
896 | | PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation) |
897 | | { |
898 | | WINPR_UNUSED(dwIndex); |
899 | | WINPR_UNUSED(lpTimeZoneInformation); |
900 | | return ERROR_NO_MORE_ITEMS; |
901 | | } |
902 | | |
903 | | DWORD GetDynamicTimeZoneInformationEffectiveYears( |
904 | | const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear) |
905 | | { |
906 | | WINPR_UNUSED(lpTimeZoneInformation); |
907 | | WINPR_UNUSED(FirstYear); |
908 | | WINPR_UNUSED(LastYear); |
909 | | return ERROR_FILE_NOT_FOUND; |
910 | | } |
911 | | #endif |
912 | | |
913 | | #if !defined(_WIN32) |
914 | | char* setNewAndSaveOldTZ(const char* val) |
915 | 0 | { |
916 | | // NOLINTBEGIN(concurrency-mt-unsafe) |
917 | 0 | const char* otz = getenv("TZ"); |
918 | 0 | char* oldtz = NULL; |
919 | 0 | if (otz) |
920 | 0 | oldtz = _strdup(otz); |
921 | 0 | setenv("TZ", val, 1); |
922 | 0 | tzset(); |
923 | | // NOLINTEND(concurrency-mt-unsafe) |
924 | 0 | return oldtz; |
925 | 0 | } |
926 | | |
927 | | void restoreSavedTZ(char* saved) |
928 | 0 | { |
929 | | // NOLINTBEGIN(concurrency-mt-unsafe) |
930 | 0 | if (saved) |
931 | 0 | { |
932 | 0 | setenv("TZ", saved, 1); |
933 | 0 | free(saved); |
934 | 0 | } |
935 | 0 | else |
936 | 0 | unsetenv("TZ"); |
937 | 0 | tzset(); |
938 | | // NOLINTEND(concurrency-mt-unsafe) |
939 | 0 | } |
940 | | #endif |