Coverage Report

Created: 2025-08-26 06:31

/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
WINPR_ATTR_FORMAT_ARG(6, 7)
596
static void log_print(wLog* log, DWORD level, const char* file, const char* fkt, size_t line,
597
                      WINPR_FORMAT_ARG const char* fmt, ...)
598
0
{
599
0
  if (!WLog_IsLevelActive(log, level))
600
0
    return;
601
602
0
  va_list ap = { 0 };
603
0
  va_start(ap, fmt);
604
0
  WLog_PrintTextMessageVA(log, level, line, file, fkt, fmt, ap);
605
0
  va_end(ap);
606
0
}
607
608
0
#define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__)
609
static void log_timezone_(const DYNAMIC_TIME_ZONE_INFORMATION* tzif, DWORD result, const char* file,
610
                          const char* fkt, size_t line)
611
0
{
612
0
  WINPR_ASSERT(tzif);
613
614
0
  char buffer[130] = { 0 };
615
0
  DWORD level = WLOG_TRACE;
616
0
  wLog* log = WLog_Get(TAG);
617
0
  log_print(log, level, file, fkt, line, "DYNAMIC_TIME_ZONE_INFORMATION {");
618
619
0
  log_print(log, level, file, fkt, line, "  Bias=%" PRId32, tzif->Bias);
620
0
  (void)ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer,
621
0
                            ARRAYSIZE(buffer));
622
0
  log_print(log, level, file, fkt, line, "  StandardName=%s", buffer);
623
0
  log_print(log, level, file, fkt, line, "  StandardDate=%s",
624
0
            systemtime2str(&tzif->StandardDate, buffer, sizeof(buffer)));
625
0
  log_print(log, level, file, fkt, line, "  StandardBias=%" PRId32, tzif->StandardBias);
626
627
0
  (void)ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer,
628
0
                            ARRAYSIZE(buffer));
629
0
  log_print(log, level, file, fkt, line, "  DaylightName=%s", buffer);
630
0
  log_print(log, level, file, fkt, line, "  DaylightDate=%s",
631
0
            systemtime2str(&tzif->DaylightDate, buffer, sizeof(buffer)));
632
0
  log_print(log, level, file, fkt, line, "  DaylightBias=%" PRId32, tzif->DaylightBias);
633
0
  (void)ConvertWCharNToUtf8(tzif->TimeZoneKeyName, ARRAYSIZE(tzif->TimeZoneKeyName), buffer,
634
0
                            ARRAYSIZE(buffer));
635
0
  log_print(log, level, file, fkt, line, "  TimeZoneKeyName=%s", buffer);
636
0
  log_print(log, level, file, fkt, line, "  DynamicDaylightTimeDisabled=DST-%s",
637
0
            tzif->DynamicDaylightTimeDisabled ? "disabled" : "enabled");
638
0
  switch (result)
639
0
  {
640
0
    case TIME_ZONE_ID_DAYLIGHT:
641
0
      log_print(log, level, file, fkt, line, "  DaylightDate in use");
642
0
      break;
643
0
    case TIME_ZONE_ID_STANDARD:
644
0
      log_print(log, level, file, fkt, line, "  StandardDate in use");
645
0
      break;
646
0
    default:
647
0
      log_print(log, level, file, fkt, line, "  UnknownDate in use");
648
0
      break;
649
0
  }
650
0
  log_print(log, level, file, fkt, line, "}");
651
0
}
652
653
DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation)
654
0
{
655
0
  DYNAMIC_TIME_ZONE_INFORMATION dyn = { 0 };
656
0
  DWORD rc = GetDynamicTimeZoneInformation(&dyn);
657
0
  lpTimeZoneInformation->Bias = dyn.Bias;
658
0
  lpTimeZoneInformation->DaylightBias = dyn.DaylightBias;
659
0
  lpTimeZoneInformation->DaylightDate = dyn.DaylightDate;
660
0
  lpTimeZoneInformation->StandardBias = dyn.StandardBias;
661
0
  lpTimeZoneInformation->StandardDate = dyn.StandardDate;
662
0
  memcpy(lpTimeZoneInformation->StandardName, dyn.StandardName,
663
0
         sizeof(lpTimeZoneInformation->StandardName));
664
0
  memcpy(lpTimeZoneInformation->DaylightName, dyn.DaylightName,
665
0
         sizeof(lpTimeZoneInformation->DaylightName));
666
0
  return rc;
667
0
}
668
669
BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation)
670
0
{
671
0
  WINPR_UNUSED(lpTimeZoneInformation);
672
0
  return FALSE;
673
0
}
674
675
BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
676
0
{
677
0
  WINPR_UNUSED(lpSystemTime);
678
0
  WINPR_UNUSED(lpFileTime);
679
0
  return FALSE;
680
0
}
681
682
BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
683
0
{
684
0
  WINPR_UNUSED(lpFileTime);
685
0
  WINPR_UNUSED(lpSystemTime);
686
0
  return FALSE;
687
0
}
688
689
BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone,
690
                                     LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
691
0
{
692
0
  WINPR_UNUSED(lpTimeZone);
693
0
  WINPR_UNUSED(lpUniversalTime);
694
0
  WINPR_UNUSED(lpLocalTime);
695
0
  return FALSE;
696
0
}
697
698
BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
699
                                     LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
700
0
{
701
0
  WINPR_UNUSED(lpTimeZoneInformation);
702
0
  WINPR_UNUSED(lpLocalTime);
703
0
  WINPR_UNUSED(lpUniversalTime);
704
0
  return FALSE;
705
0
}
706
707
#endif
708
709
/*
710
 * GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A
711
 * and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs
712
 */
713
#if !defined(_WIN32) ||                                                  \
714
    (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
715
                         !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */
716
717
typedef enum
718
{
719
  HAVE_TRANSITION_DATES = 0,
720
  HAVE_NO_STANDARD_TRANSITION_DATE = 1,
721
  HAVE_NO_DAYLIGHT_TRANSITION_DATE = 2
722
} dyn_transition_result;
723
724
static int dynamic_time_zone_from_localtime(const struct tm* local_time,
725
                                            PDYNAMIC_TIME_ZONE_INFORMATION tz)
726
0
{
727
0
  WINPR_ASSERT(local_time);
728
0
  WINPR_ASSERT(tz);
729
0
  int rc = HAVE_TRANSITION_DATES;
730
731
0
  tz->Bias = get_bias(local_time, FALSE);
732
733
  /* If the current time has (or had) DST */
734
0
  if (local_time->tm_isdst >= 0)
735
0
  {
736
    /* DST bias is the difference between standard time and DST in minutes */
737
0
    const LONG d = get_bias(local_time, TRUE);
738
0
    tz->DaylightBias = -1 * (tz->Bias - d);
739
0
    if (!get_transition_date(local_time, FALSE, &tz->StandardDate))
740
0
    {
741
0
      rc |= HAVE_NO_STANDARD_TRANSITION_DATE;
742
0
      tz->StandardBias = 0;
743
0
    }
744
0
    if (!get_transition_date(local_time, TRUE, &tz->DaylightDate))
745
0
    {
746
0
      rc |= HAVE_NO_DAYLIGHT_TRANSITION_DATE;
747
0
      tz->DaylightBias = 0;
748
0
    }
749
0
  }
750
0
  return rc;
751
0
}
752
753
DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation)
754
0
{
755
0
  const char** list = NULL;
756
0
  char* tzid = NULL;
757
0
  const char* defaultName = "Client Local Time";
758
0
  DWORD res = TIME_ZONE_ID_UNKNOWN;
759
0
  const DYNAMIC_TIME_ZONE_INFORMATION empty = { 0 };
760
761
0
  WINPR_ASSERT(pTimeZoneInformation);
762
763
0
  *pTimeZoneInformation = empty;
764
0
  (void)ConvertUtf8ToWChar(defaultName, pTimeZoneInformation->StandardName,
765
0
                           ARRAYSIZE(pTimeZoneInformation->StandardName));
766
767
0
  const time_t t = time(NULL);
768
0
  struct tm tres = { 0 };
769
0
  struct tm* local_time = localtime_r(&t, &tres);
770
0
  if (!local_time)
771
0
    goto out_error;
772
773
0
  pTimeZoneInformation->Bias = get_bias(local_time, FALSE);
774
0
  if (local_time->tm_isdst >= 0)
775
0
    dynamic_time_zone_from_localtime(local_time, pTimeZoneInformation);
776
777
0
  tzid = winpr_guess_time_zone();
778
0
  if (!map_iana_id(tzid, pTimeZoneInformation))
779
0
  {
780
0
    const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone, NULL, 0);
781
0
    list = (const char**)calloc(len, sizeof(const char*));
782
0
    if (!list)
783
0
      goto out_error;
784
0
    const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len);
785
0
    for (size_t x = 0; x < size; x++)
786
0
    {
787
0
      const char* id = list[x];
788
0
      if (map_iana_id(id, pTimeZoneInformation))
789
0
      {
790
0
        res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
791
0
        break;
792
0
      }
793
0
    }
794
0
  }
795
0
  else
796
0
    res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
797
798
0
out_error:
799
0
  free(tzid);
800
0
  free((void*)list);
801
802
0
  log_timezone(pTimeZoneInformation, res);
803
0
  return res;
804
0
}
805
806
BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation)
807
0
{
808
0
  WINPR_UNUSED(lpTimeZoneInformation);
809
0
  return FALSE;
810
0
}
811
812
BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi,
813
                                   LPTIME_ZONE_INFORMATION ptzi)
814
0
{
815
0
  WINPR_UNUSED(wYear);
816
0
  WINPR_UNUSED(pdtzi);
817
0
  WINPR_UNUSED(ptzi);
818
0
  return FALSE;
819
0
}
820
821
#endif
822
823
#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */
824
825
BOOL SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
826
                                       const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
827
0
{
828
0
  WINPR_UNUSED(lpTimeZoneInformation);
829
0
  WINPR_UNUSED(lpUniversalTime);
830
0
  WINPR_UNUSED(lpLocalTime);
831
0
  return FALSE;
832
0
}
833
834
BOOL TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
835
                                       const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
836
0
{
837
0
  WINPR_UNUSED(lpTimeZoneInformation);
838
0
  WINPR_UNUSED(lpLocalTime);
839
0
  WINPR_UNUSED(lpUniversalTime);
840
0
  return FALSE;
841
0
}
842
843
#endif
844
845
#if !defined(_WIN32)
846
847
DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex,
848
                                     PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
849
0
{
850
0
  if (!lpTimeZoneInformation)
851
0
    return ERROR_INVALID_PARAMETER;
852
0
  const DYNAMIC_TIME_ZONE_INFORMATION empty = { 0 };
853
0
  *lpTimeZoneInformation = empty;
854
855
0
  const TimeZoneNameMapEntry* entry = TimeZoneGetAt(dwIndex);
856
0
  if (!entry)
857
0
    return ERROR_NO_MORE_ITEMS;
858
859
0
  if (entry->DaylightName)
860
0
    (void)ConvertUtf8ToWChar(entry->DaylightName, lpTimeZoneInformation->DaylightName,
861
0
                             ARRAYSIZE(lpTimeZoneInformation->DaylightName));
862
0
  if (entry->StandardName)
863
0
    (void)ConvertUtf8ToWChar(entry->StandardName, lpTimeZoneInformation->StandardName,
864
0
                             ARRAYSIZE(lpTimeZoneInformation->StandardName));
865
0
  if (entry->Id)
866
0
    (void)ConvertUtf8ToWChar(entry->Id, lpTimeZoneInformation->TimeZoneKeyName,
867
0
                             ARRAYSIZE(lpTimeZoneInformation->TimeZoneKeyName));
868
869
0
  const time_t t = time(NULL);
870
0
  struct tm tres = { 0 };
871
872
0
  char* tzcopy = entry->Iana ? setNewAndSaveOldTZ(entry->Iana) : NULL;
873
874
0
  struct tm* local_time = localtime_r(&t, &tres);
875
876
0
  if (local_time)
877
0
    dynamic_time_zone_from_localtime(local_time, lpTimeZoneInformation);
878
879
0
  if (entry->Iana)
880
0
    restoreSavedTZ(tzcopy);
881
882
0
  return ERROR_SUCCESS;
883
0
}
884
885
// NOLINTBEGIN(readability-non-const-parameter)
886
DWORD GetDynamicTimeZoneInformationEffectiveYears(
887
    const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
888
// NOLINTEND(readability-non-const-parameter)
889
0
{
890
0
  WINPR_UNUSED(lpTimeZoneInformation);
891
0
  WINPR_UNUSED(FirstYear);
892
0
  WINPR_UNUSED(LastYear);
893
0
  return ERROR_FILE_NOT_FOUND;
894
0
}
895
896
#elif _WIN32_WINNT < 0x0602 /* Windows 8 */
897
DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex,
898
                                     PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
899
{
900
  WINPR_UNUSED(dwIndex);
901
  WINPR_UNUSED(lpTimeZoneInformation);
902
  return ERROR_NO_MORE_ITEMS;
903
}
904
905
DWORD GetDynamicTimeZoneInformationEffectiveYears(
906
    const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
907
{
908
  WINPR_UNUSED(lpTimeZoneInformation);
909
  WINPR_UNUSED(FirstYear);
910
  WINPR_UNUSED(LastYear);
911
  return ERROR_FILE_NOT_FOUND;
912
}
913
#endif
914
915
#if !defined(_WIN32)
916
char* setNewAndSaveOldTZ(const char* val)
917
0
{
918
  // NOLINTBEGIN(concurrency-mt-unsafe)
919
0
  const char* otz = getenv("TZ");
920
0
  char* oldtz = NULL;
921
0
  if (otz)
922
0
    oldtz = _strdup(otz);
923
0
  setenv("TZ", val, 1);
924
0
  tzset();
925
  // NOLINTEND(concurrency-mt-unsafe)
926
0
  return oldtz;
927
0
}
928
929
void restoreSavedTZ(char* saved)
930
0
{
931
  // NOLINTBEGIN(concurrency-mt-unsafe)
932
0
  if (saved)
933
0
  {
934
0
    setenv("TZ", saved, 1);
935
0
    free(saved);
936
0
  }
937
0
  else
938
0
    unsetenv("TZ");
939
0
  tzset();
940
  // NOLINTEND(concurrency-mt-unsafe)
941
0
}
942
#endif