Coverage Report

Created: 2025-07-01 06:46

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