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