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