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