Coverage Report

Created: 2026-04-12 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/timezone/timezone.c
Line
Count
Source
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
106k
#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
106k
{
48
106k
  const INT CHUNK_SIZE = 32;
49
106k
  size_t rc = 0;
50
106k
  size_t read = 0;
51
106k
  size_t length = CHUNK_SIZE;
52
53
106k
  char* tzid = malloc(length);
54
106k
  if (!tzid)
55
0
    return nullptr;
56
57
106k
  do
58
106k
  {
59
106k
    rc = fread(tzid + read, 1, length - read - 1UL, fp);
60
106k
    if (rc > 0)
61
106k
      read += rc;
62
63
106k
    if (read < (length - 1UL))
64
106k
      break;
65
66
0
    if (read > length - 1UL)
67
0
    {
68
0
      free(tzid);
69
0
      return nullptr;
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 nullptr;
78
0
    }
79
80
0
    tzid = tmp;
81
0
  } while (rc > 0);
82
83
106k
  if (ferror(fp))
84
0
  {
85
0
    free(tzid);
86
0
    return nullptr;
87
0
  }
88
89
106k
  tzid[read] = '\0';
90
106k
  if (read > 0)
91
106k
  {
92
106k
    if (tzid[read - 1] == '\n')
93
106k
      tzid[read - 1] = '\0';
94
106k
  }
95
96
106k
  return tzid;
97
106k
}
98
99
static char* winpr_get_timezone_from_link(const char* links[], size_t count)
100
106k
{
101
106k
  const char* _links[] = { "/etc/localtime", "/etc/TZ" };
102
103
106k
  if (links == nullptr)
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
106k
  for (size_t x = 0; x < count; x++)
117
106k
  {
118
106k
    char* tzid = nullptr;
119
106k
    const char* link = links[x];
120
106k
    char* buf = realpath(link, nullptr);
121
122
106k
    if (buf)
123
106k
    {
124
106k
      size_t sep = 0;
125
106k
      size_t alloc = 0;
126
106k
      size_t pos = 0;
127
106k
      size_t len = pos = strlen(buf);
128
129
      /* find the position of the 2nd to last "/" */
130
852k
      for (size_t i = 1; i <= len; i++)
131
852k
      {
132
852k
        const size_t curpos = len - i;
133
852k
        const char cur = buf[curpos];
134
135
852k
        if (cur == '/')
136
213k
          sep++;
137
852k
        if (sep >= 2)
138
106k
        {
139
106k
          alloc = i;
140
106k
          pos = len - i + 1;
141
106k
          break;
142
106k
        }
143
852k
      }
144
145
106k
      if ((len == 0) || (sep != 2))
146
0
        goto end;
147
148
106k
      tzid = (char*)calloc(alloc + 1, sizeof(char));
149
150
106k
      if (!tzid)
151
0
        goto end;
152
153
106k
      strncpy(tzid, &buf[pos], alloc);
154
106k
      WLog_DBG(TAG, "tzid: %s", tzid);
155
106k
      goto end;
156
106k
    }
157
158
106k
  end:
159
106k
    free(buf);
160
106k
    if (tzid)
161
106k
      return tzid;
162
106k
  }
163
164
0
  return nullptr;
165
106k
}
166
167
#if defined(ANDROID)
168
#include "../utils/android.h"
169
170
static char* winpr_get_android_timezone_identifier(void)
171
{
172
  char* tzid = nullptr;
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, nullptr);
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
106k
{
242
#if defined(ANDROID)
243
  return winpr_get_android_timezone_identifier();
244
#else
245
106k
  FILE* fp = nullptr;
246
106k
  char* tzid = nullptr;
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
106k
  fp = winpr_fopen(WINPR_TIMEZONE_FILE, "r");
252
106k
#endif
253
254
106k
  if (nullptr == fp)
255
0
    return nullptr;
256
257
106k
  tzid = winpr_read_unix_timezone_identifier_from_file(fp);
258
106k
  (void)fclose(fp);
259
106k
  if (tzid != nullptr)
260
106k
    WLog_DBG(TAG, "tzid: %s", tzid);
261
106k
  return tzid;
262
106k
#endif
263
106k
}
264
265
static char* winpr_time_zone_from_env(void)
266
106k
{
267
106k
  LPCSTR tz = "TZ";
268
106k
  char* tzid = nullptr;
269
270
106k
  DWORD nSize = GetEnvironmentVariableA(tz, nullptr, 0);
271
106k
  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
106k
  return tzid;
286
287
0
fail:
288
0
  free(tzid);
289
0
  return nullptr;
290
106k
}
291
292
static char* winpr_translate_time_zone(const char* tzid)
293
106k
{
294
106k
  const char* zipath = "/usr/share/zoneinfo/";
295
106k
  char* buf = nullptr;
296
106k
  const char* links[] = { buf };
297
298
106k
  if (!tzid)
299
0
    return nullptr;
300
301
106k
  if (tzid[0] == '/')
302
0
  {
303
    /* Full path given in TZ */
304
0
    links[0] = tzid;
305
0
  }
306
106k
  else
307
106k
  {
308
106k
    size_t bsize = 0;
309
106k
    winpr_asprintf(&buf, &bsize, "%s%s", zipath, tzid);
310
106k
    links[0] = buf;
311
106k
  }
312
313
106k
  char* ntzid = winpr_get_timezone_from_link(links, 1);
314
106k
  free(buf);
315
106k
  return ntzid;
316
106k
}
317
318
static char* winpr_guess_time_zone(void)
319
106k
{
320
106k
  char* tzid = winpr_time_zone_from_env();
321
106k
  if (tzid)
322
0
    goto end;
323
106k
  tzid = winpr_get_unix_timezone_identifier_from_file();
324
106k
  if (tzid)
325
106k
    goto end;
326
0
  tzid = winpr_get_timezone_from_link(nullptr, 0);
327
0
  if (tzid)
328
0
    goto end;
329
330
106k
end:
331
106k
{
332
106k
  char* ntzid = winpr_translate_time_zone(tzid);
333
106k
  if (ntzid)
334
106k
  {
335
106k
    free(tzid);
336
106k
    return ntzid;
337
106k
  }
338
0
  return tzid;
339
106k
}
340
106k
}
341
342
static SYSTEMTIME tm2systemtime(const struct tm* t)
343
213k
{
344
213k
  SYSTEMTIME st = WINPR_C_ARRAY_INIT;
345
346
213k
  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
213k
  return st;
358
213k
}
359
360
static struct tm systemtime2tm(const SYSTEMTIME* st)
361
0
{
362
0
  struct tm t = WINPR_C_ARRAY_INIT;
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
213k
{
380
213k
  WINPR_ASSERT(t);
381
213k
  return -(LONG)(t->tm_gmtoff / 60l);
382
213k
}
383
384
static struct tm update_tm(const struct tm* start)
385
116M
{
386
116M
  WINPR_ASSERT(start);
387
116M
  struct tm cur = *start;
388
116M
  const time_t t = mktime(&cur);
389
116M
  struct tm next = WINPR_C_ARRAY_INIT;
390
116M
  (void)localtime_r(&t, &next);
391
116M
  return next;
392
116M
}
393
394
static struct tm next_day(const struct tm* start)
395
116M
{
396
116M
  struct tm cur = *start;
397
116M
  cur.tm_hour = 0;
398
116M
  cur.tm_min = 0;
399
116M
  cur.tm_sec = 0;
400
116M
  cur.tm_isdst = -1;
401
116M
  cur.tm_mday++;
402
116M
  return update_tm(&cur);
403
116M
}
404
405
static struct tm adjust_time(const struct tm* start, int hour, int minute)
406
0
{
407
0
  struct tm cur = *start;
408
0
  cur.tm_hour = hour;
409
0
  cur.tm_min = minute;
410
0
  cur.tm_sec = 0;
411
0
  cur.tm_isdst = -1;
412
0
  return update_tm(&cur);
413
0
}
414
415
/* [MS-RDPBCGR] 2.2.1.11.1.1.1.1.1  System Time (TS_SYSTEMTIME) */
416
static WORD get_transition_weekday_occurrence(const SYSTEMTIME* st)
417
0
{
418
0
  WORD count = 0;
419
0
  struct tm start = systemtime2tm(st);
420
421
0
  WORD last = 0;
422
0
  struct tm next = start;
423
0
  next.tm_mday = 1;
424
0
  next.tm_isdst = -1;
425
0
  do
426
0
  {
427
428
0
    const time_t t = mktime(&next);
429
0
    next.tm_mday++;
430
431
0
    struct tm cur = WINPR_C_ARRAY_INIT;
432
0
    (void)localtime_r(&t, &cur);
433
434
0
    if (cur.tm_mon + 1 != st->wMonth)
435
0
      break;
436
437
0
    if (cur.tm_wday == st->wDayOfWeek)
438
0
    {
439
0
      if (cur.tm_mday <= st->wDay)
440
0
        count++;
441
0
      last++;
442
0
    }
443
444
0
  } while (TRUE);
445
446
0
  if (count == last)
447
0
    count = 5;
448
449
0
  return count;
450
0
}
451
452
static SYSTEMTIME tm2transitiontime(const struct tm* cur)
453
213k
{
454
213k
  SYSTEMTIME t = tm2systemtime(cur);
455
213k
  if (cur)
456
0
  {
457
0
    t.wDay = get_transition_weekday_occurrence(&t);
458
0
    t.wYear = 0;
459
0
  }
460
461
213k
  return t;
462
213k
}
463
464
static SYSTEMTIME get_transition_time(const struct tm* start, BOOL toDst)
465
0
{
466
0
  BOOL toggled = FALSE;
467
0
  struct tm first = adjust_time(start, 0, 0);
468
0
  for (int hour = 0; hour < 24; hour++)
469
0
  {
470
0
    struct tm cur = adjust_time(start, hour, 0);
471
0
    if (cur.tm_isdst != first.tm_isdst)
472
0
      toggled = TRUE;
473
474
0
    if (toggled)
475
0
    {
476
0
      if (toDst && (cur.tm_isdst > 0))
477
0
        return tm2transitiontime(&cur);
478
0
      else if (!toDst && (cur.tm_isdst == 0))
479
0
        return tm2transitiontime(&cur);
480
0
    }
481
0
  }
482
0
  return tm2transitiontime(start);
483
0
}
484
485
static BOOL get_transition_date(const struct tm* pstart, BOOL toDst, SYSTEMTIME* pdate)
486
213k
{
487
213k
  WINPR_ASSERT(pstart);
488
213k
  WINPR_ASSERT(pdate);
489
490
213k
  *pdate = tm2transitiontime(nullptr);
491
492
213k
  struct tm start = update_tm(pstart);
493
213k
  if (start.tm_isdst < 0)
494
0
    return FALSE;
495
496
213k
  BOOL val = start.tm_isdst > 0; // the year starts with DST or not
497
213k
  BOOL toggled = FALSE;
498
213k
  struct tm cur = start;
499
213k
  struct tm last = cur;
500
77.9M
  for (int day = 1; day <= 365; day++)
501
77.7M
  {
502
77.7M
    last = cur;
503
77.7M
    cur = next_day(&cur);
504
77.7M
    const BOOL curDst = (cur.tm_isdst > 0);
505
77.7M
    if ((val != curDst) && !toggled)
506
0
      toggled = TRUE;
507
508
77.7M
    if (toggled)
509
0
    {
510
0
      if (toDst && curDst)
511
0
      {
512
0
        *pdate = get_transition_time(&last, toDst);
513
0
        return TRUE;
514
0
      }
515
0
      if (!toDst && !curDst)
516
0
      {
517
0
        *pdate = get_transition_time(&last, toDst);
518
0
        return TRUE;
519
0
      }
520
0
    }
521
77.7M
  }
522
213k
  return FALSE;
523
213k
}
524
525
static LONG get_bias(const struct tm* start, BOOL dstBias)
526
319k
{
527
319k
  if ((start->tm_isdst > 0) && dstBias)
528
0
    return get_gmtoff_min(start);
529
319k
  if ((start->tm_isdst == 0) && !dstBias)
530
213k
    return get_gmtoff_min(start);
531
106k
  if (start->tm_isdst < 0)
532
0
    return get_gmtoff_min(start);
533
534
106k
  struct tm cur = *start;
535
38.9M
  for (int day = 1; day <= 365; day++)
536
38.8M
  {
537
38.8M
    cur = next_day(&cur);
538
38.8M
    if ((cur.tm_isdst > 0) && dstBias)
539
0
      return get_gmtoff_min(&cur);
540
38.8M
    else if ((cur.tm_isdst == 0) && !dstBias)
541
0
      return get_gmtoff_min(&cur);
542
38.8M
  }
543
106k
  return 0;
544
106k
}
545
546
static BOOL map_iana_id(const char* iana, LPDYNAMIC_TIME_ZONE_INFORMATION tz)
547
106k
{
548
106k
  const char* winId = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_ID);
549
106k
  const char* winStd = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_STANDARD);
550
106k
  const char* winDst = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_DAYLIGHT);
551
552
106k
  if (winStd)
553
106k
    (void)ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName));
554
106k
  if (winDst)
555
106k
    (void)ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName));
556
106k
  if (winId)
557
106k
    (void)ConvertUtf8ToWChar(winId, tz->TimeZoneKeyName, ARRAYSIZE(tz->TimeZoneKeyName));
558
559
106k
  return winId != nullptr;
560
106k
}
561
562
static const char* weekday2str(WORD wDayOfWeek)
563
0
{
564
0
  switch (wDayOfWeek)
565
0
  {
566
0
    case 0:
567
0
      return "SUNDAY";
568
0
    case 1:
569
0
      return "MONDAY";
570
0
    case 2:
571
0
      return "TUESDAY";
572
0
    case 3:
573
0
      return "WEDNESDAY";
574
0
    case 4:
575
0
      return "THURSDAY";
576
0
    case 5:
577
0
      return "FRIDAY";
578
0
    case 6:
579
0
      return "SATURDAY";
580
0
    default:
581
0
      return "DAY-OF-MAGIC";
582
0
  }
583
0
}
584
585
static char* systemtime2str(const SYSTEMTIME* t, char* buffer, size_t len)
586
213k
{
587
213k
  const SYSTEMTIME empty = WINPR_C_ARRAY_INIT;
588
589
213k
  if (memcmp(t, &empty, sizeof(SYSTEMTIME)) == 0)
590
213k
    (void)_snprintf(buffer, len, "{ not set }");
591
0
  else
592
0
  {
593
0
    (void)_snprintf(buffer, len,
594
0
                    "{ %" PRIu16 "-%" PRIu16 "-%" PRIu16 " [%s] %" PRIu16 ":%" PRIu16
595
0
                    ":%" PRIu16 ".%" PRIu16 "}",
596
0
                    t->wYear, t->wMonth, t->wDay, weekday2str(t->wDayOfWeek), t->wHour,
597
0
                    t->wMinute, t->wSecond, t->wMilliseconds);
598
0
  }
599
213k
  return buffer;
600
213k
}
601
602
WINPR_ATTR_FORMAT_ARG(6, 7)
603
static void log_print(wLog* log, DWORD level, const char* file, const char* fkt, size_t line,
604
                      WINPR_FORMAT_ARG const char* fmt, ...)
605
1.27M
{
606
1.27M
  if (!WLog_IsLevelActive(log, level))
607
1.27M
    return;
608
609
0
  va_list ap = WINPR_C_ARRAY_INIT;
610
0
  va_start(ap, fmt);
611
0
  WLog_PrintTextMessageVA(log, level, line, file, fkt, fmt, ap);
612
0
  va_end(ap);
613
0
}
614
615
106k
#define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__)
616
static void log_timezone_(const DYNAMIC_TIME_ZONE_INFORMATION* tzif, DWORD result, const char* file,
617
                          const char* fkt, size_t line)
618
106k
{
619
106k
  WINPR_ASSERT(tzif);
620
621
106k
  char buffer[130] = WINPR_C_ARRAY_INIT;
622
106k
  DWORD level = WLOG_TRACE;
623
106k
  wLog* log = WLog_Get(TAG);
624
106k
  log_print(log, level, file, fkt, line, "DYNAMIC_TIME_ZONE_INFORMATION {");
625
626
106k
  log_print(log, level, file, fkt, line, "  Bias=%" PRId32, tzif->Bias);
627
106k
  (void)ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer,
628
106k
                            ARRAYSIZE(buffer));
629
106k
  log_print(log, level, file, fkt, line, "  StandardName=%s", buffer);
630
106k
  log_print(log, level, file, fkt, line, "  StandardDate=%s",
631
106k
            systemtime2str(&tzif->StandardDate, buffer, sizeof(buffer)));
632
106k
  log_print(log, level, file, fkt, line, "  StandardBias=%" PRId32, tzif->StandardBias);
633
634
106k
  (void)ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer,
635
106k
                            ARRAYSIZE(buffer));
636
106k
  log_print(log, level, file, fkt, line, "  DaylightName=%s", buffer);
637
106k
  log_print(log, level, file, fkt, line, "  DaylightDate=%s",
638
106k
            systemtime2str(&tzif->DaylightDate, buffer, sizeof(buffer)));
639
106k
  log_print(log, level, file, fkt, line, "  DaylightBias=%" PRId32, tzif->DaylightBias);
640
106k
  (void)ConvertWCharNToUtf8(tzif->TimeZoneKeyName, ARRAYSIZE(tzif->TimeZoneKeyName), buffer,
641
106k
                            ARRAYSIZE(buffer));
642
106k
  log_print(log, level, file, fkt, line, "  TimeZoneKeyName=%s", buffer);
643
106k
  log_print(log, level, file, fkt, line, "  DynamicDaylightTimeDisabled=DST-%s",
644
106k
            tzif->DynamicDaylightTimeDisabled ? "disabled" : "enabled");
645
106k
  switch (result)
646
106k
  {
647
0
    case TIME_ZONE_ID_DAYLIGHT:
648
0
      log_print(log, level, file, fkt, line, "  DaylightDate in use");
649
0
      break;
650
106k
    case TIME_ZONE_ID_STANDARD:
651
106k
      log_print(log, level, file, fkt, line, "  StandardDate in use");
652
106k
      break;
653
0
    default:
654
0
      log_print(log, level, file, fkt, line, "  UnknownDate in use");
655
0
      break;
656
106k
  }
657
106k
  log_print(log, level, file, fkt, line, "}");
658
106k
}
659
660
DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation)
661
53.2k
{
662
53.2k
  DYNAMIC_TIME_ZONE_INFORMATION dyn = WINPR_C_ARRAY_INIT;
663
53.2k
  DWORD rc = GetDynamicTimeZoneInformation(&dyn);
664
53.2k
  lpTimeZoneInformation->Bias = dyn.Bias;
665
53.2k
  lpTimeZoneInformation->DaylightBias = dyn.DaylightBias;
666
53.2k
  lpTimeZoneInformation->DaylightDate = dyn.DaylightDate;
667
53.2k
  lpTimeZoneInformation->StandardBias = dyn.StandardBias;
668
53.2k
  lpTimeZoneInformation->StandardDate = dyn.StandardDate;
669
53.2k
  memcpy(lpTimeZoneInformation->StandardName, dyn.StandardName,
670
53.2k
         sizeof(lpTimeZoneInformation->StandardName));
671
53.2k
  memcpy(lpTimeZoneInformation->DaylightName, dyn.DaylightName,
672
53.2k
         sizeof(lpTimeZoneInformation->DaylightName));
673
53.2k
  return rc;
674
53.2k
}
675
676
BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation)
677
0
{
678
0
  WINPR_UNUSED(lpTimeZoneInformation);
679
0
  return FALSE;
680
0
}
681
682
BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
683
0
{
684
0
  WINPR_UNUSED(lpSystemTime);
685
0
  WINPR_UNUSED(lpFileTime);
686
0
  return FALSE;
687
0
}
688
689
BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
690
0
{
691
0
  WINPR_UNUSED(lpFileTime);
692
0
  WINPR_UNUSED(lpSystemTime);
693
0
  return FALSE;
694
0
}
695
696
BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone,
697
                                     LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
698
0
{
699
0
  WINPR_UNUSED(lpTimeZone);
700
0
  WINPR_UNUSED(lpUniversalTime);
701
0
  WINPR_UNUSED(lpLocalTime);
702
0
  return FALSE;
703
0
}
704
705
BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
706
                                     LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
707
0
{
708
0
  WINPR_UNUSED(lpTimeZoneInformation);
709
0
  WINPR_UNUSED(lpLocalTime);
710
0
  WINPR_UNUSED(lpUniversalTime);
711
0
  return FALSE;
712
0
}
713
714
#endif
715
716
/*
717
 * GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A
718
 * and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs
719
 */
720
#if !defined(_WIN32) ||                                                  \
721
    (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
722
                         !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */
723
724
typedef enum
725
{
726
  HAVE_TRANSITION_DATES = 0,
727
  HAVE_NO_STANDARD_TRANSITION_DATE = 1,
728
  HAVE_NO_DAYLIGHT_TRANSITION_DATE = 2
729
} dyn_transition_result;
730
731
static int dynamic_time_zone_from_localtime(const struct tm* local_time,
732
                                            PDYNAMIC_TIME_ZONE_INFORMATION tz)
733
106k
{
734
106k
  WINPR_ASSERT(local_time);
735
106k
  WINPR_ASSERT(tz);
736
106k
  int rc = HAVE_TRANSITION_DATES;
737
738
106k
  tz->Bias = get_bias(local_time, FALSE);
739
740
  /* If the current time has (or had) DST */
741
106k
  if (local_time->tm_isdst >= 0)
742
106k
  {
743
    /* DST bias is the difference between standard time and DST in minutes */
744
106k
    const LONG d = get_bias(local_time, TRUE);
745
106k
    tz->DaylightBias = -1 * (tz->Bias - d);
746
106k
    struct tm newyear = *local_time;
747
106k
    newyear.tm_mday = 0;
748
106k
    newyear.tm_yday = 0;
749
106k
    newyear.tm_wday = 0;
750
106k
    newyear.tm_min = 0;
751
106k
    newyear.tm_sec = 0;
752
106k
    newyear.tm_mon = 0;
753
754
    /* Searching for transition dates.
755
     *
756
     * For the current DST setting, search from the local_time, for the other one
757
     * search from beginning of the year.
758
     * We want the dates in the same year, so searching both from local_time might lead to
759
     * issues.
760
     */
761
106k
    const struct tm* stdtransition = local_time;
762
106k
    const struct tm* dsttransition = &newyear;
763
106k
    if (local_time->tm_isdst == 0)
764
106k
    {
765
106k
      stdtransition = &newyear;
766
106k
      dsttransition = local_time;
767
106k
    }
768
106k
    if (!get_transition_date(stdtransition, FALSE, &tz->StandardDate))
769
106k
    {
770
106k
      rc |= HAVE_NO_STANDARD_TRANSITION_DATE;
771
106k
      tz->StandardBias = 0;
772
106k
    }
773
106k
    if (!get_transition_date(dsttransition, TRUE, &tz->DaylightDate))
774
106k
    {
775
106k
      rc |= HAVE_NO_DAYLIGHT_TRANSITION_DATE;
776
106k
      tz->DaylightBias = 0;
777
106k
    }
778
106k
  }
779
106k
  return rc;
780
106k
}
781
782
DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation)
783
106k
{
784
106k
  const char** list = nullptr;
785
106k
  char* tzid = nullptr;
786
106k
  const char* defaultName = "Client Local Time";
787
106k
  DWORD res = TIME_ZONE_ID_UNKNOWN;
788
106k
  const DYNAMIC_TIME_ZONE_INFORMATION empty = WINPR_C_ARRAY_INIT;
789
790
106k
  WINPR_ASSERT(pTimeZoneInformation);
791
792
106k
  *pTimeZoneInformation = empty;
793
106k
  (void)ConvertUtf8ToWChar(defaultName, pTimeZoneInformation->StandardName,
794
106k
                           ARRAYSIZE(pTimeZoneInformation->StandardName));
795
796
106k
  const time_t t = time(nullptr);
797
106k
  struct tm tres = WINPR_C_ARRAY_INIT;
798
106k
  struct tm* local_time = localtime_r(&t, &tres);
799
106k
  if (!local_time)
800
0
    goto out_error;
801
802
106k
  pTimeZoneInformation->Bias = get_bias(local_time, FALSE);
803
106k
  if (local_time->tm_isdst >= 0)
804
106k
    dynamic_time_zone_from_localtime(local_time, pTimeZoneInformation);
805
806
106k
  tzid = winpr_guess_time_zone();
807
106k
  if (!map_iana_id(tzid, pTimeZoneInformation))
808
0
  {
809
0
    const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone, nullptr, 0);
810
0
    list = (const char**)calloc(len, sizeof(const char*));
811
0
    if (!list)
812
0
      goto out_error;
813
0
    const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len);
814
0
    for (size_t x = 0; x < size; x++)
815
0
    {
816
0
      const char* id = list[x];
817
0
      if (map_iana_id(id, pTimeZoneInformation))
818
0
      {
819
0
        res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
820
0
        break;
821
0
      }
822
0
    }
823
0
  }
824
106k
  else
825
106k
    res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
826
827
106k
out_error:
828
106k
  free(tzid);
829
106k
  free((void*)list);
830
831
106k
  log_timezone(pTimeZoneInformation, res);
832
106k
  return res;
833
106k
}
834
835
BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation)
836
0
{
837
0
  WINPR_UNUSED(lpTimeZoneInformation);
838
0
  return FALSE;
839
0
}
840
841
BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi,
842
                                   LPTIME_ZONE_INFORMATION ptzi)
843
0
{
844
0
  WINPR_UNUSED(wYear);
845
0
  WINPR_UNUSED(pdtzi);
846
0
  WINPR_UNUSED(ptzi);
847
0
  return FALSE;
848
0
}
849
850
#endif
851
852
#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */
853
854
BOOL SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
855
                                       const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
856
0
{
857
0
  WINPR_UNUSED(lpTimeZoneInformation);
858
0
  WINPR_UNUSED(lpUniversalTime);
859
0
  WINPR_UNUSED(lpLocalTime);
860
0
  return FALSE;
861
0
}
862
863
BOOL TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
864
                                       const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
865
0
{
866
0
  WINPR_UNUSED(lpTimeZoneInformation);
867
0
  WINPR_UNUSED(lpLocalTime);
868
0
  WINPR_UNUSED(lpUniversalTime);
869
0
  return FALSE;
870
0
}
871
872
#endif
873
874
#if !defined(_WIN32)
875
876
DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex,
877
                                     PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
878
0
{
879
0
  if (!lpTimeZoneInformation)
880
0
    return ERROR_INVALID_PARAMETER;
881
0
  const DYNAMIC_TIME_ZONE_INFORMATION empty = WINPR_C_ARRAY_INIT;
882
0
  *lpTimeZoneInformation = empty;
883
884
0
  const TimeZoneNameMapEntry* entry = TimeZoneGetAt(dwIndex);
885
0
  if (!entry)
886
0
    return ERROR_NO_MORE_ITEMS;
887
888
0
  if (entry->DaylightName)
889
0
    (void)ConvertUtf8ToWChar(entry->DaylightName, lpTimeZoneInformation->DaylightName,
890
0
                             ARRAYSIZE(lpTimeZoneInformation->DaylightName));
891
0
  if (entry->StandardName)
892
0
    (void)ConvertUtf8ToWChar(entry->StandardName, lpTimeZoneInformation->StandardName,
893
0
                             ARRAYSIZE(lpTimeZoneInformation->StandardName));
894
0
  if (entry->Id)
895
0
    (void)ConvertUtf8ToWChar(entry->Id, lpTimeZoneInformation->TimeZoneKeyName,
896
0
                             ARRAYSIZE(lpTimeZoneInformation->TimeZoneKeyName));
897
898
0
  const time_t t = time(nullptr);
899
0
  struct tm tres = WINPR_C_ARRAY_INIT;
900
901
0
  char* tzcopy = entry->Iana ? setNewAndSaveOldTZ(entry->Iana) : nullptr;
902
903
0
  struct tm* local_time = localtime_r(&t, &tres);
904
905
0
  if (local_time)
906
0
    dynamic_time_zone_from_localtime(local_time, lpTimeZoneInformation);
907
908
0
  if (entry->Iana)
909
0
    restoreSavedTZ(tzcopy);
910
911
0
  return ERROR_SUCCESS;
912
0
}
913
914
// NOLINTBEGIN(readability-non-const-parameter)
915
DWORD GetDynamicTimeZoneInformationEffectiveYears(
916
    const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
917
// NOLINTEND(readability-non-const-parameter)
918
0
{
919
0
  WINPR_UNUSED(lpTimeZoneInformation);
920
0
  WINPR_UNUSED(FirstYear);
921
0
  WINPR_UNUSED(LastYear);
922
0
  return ERROR_FILE_NOT_FOUND;
923
0
}
924
925
#elif _WIN32_WINNT < 0x0602 /* Windows 8 */
926
DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex,
927
                                     PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
928
{
929
  WINPR_UNUSED(dwIndex);
930
  WINPR_UNUSED(lpTimeZoneInformation);
931
  return ERROR_NO_MORE_ITEMS;
932
}
933
934
DWORD GetDynamicTimeZoneInformationEffectiveYears(
935
    const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
936
{
937
  WINPR_UNUSED(lpTimeZoneInformation);
938
  WINPR_UNUSED(FirstYear);
939
  WINPR_UNUSED(LastYear);
940
  return ERROR_FILE_NOT_FOUND;
941
}
942
#endif
943
944
#if !defined(_WIN32)
945
char* setNewAndSaveOldTZ(const char* val)
946
0
{
947
  // NOLINTBEGIN(concurrency-mt-unsafe)
948
0
  const char* otz = getenv("TZ");
949
0
  char* oldtz = nullptr;
950
0
  if (otz)
951
0
    oldtz = _strdup(otz);
952
0
  setenv("TZ", val, 1);
953
0
  tzset();
954
  // NOLINTEND(concurrency-mt-unsafe)
955
0
  return oldtz;
956
0
}
957
958
void restoreSavedTZ(char* saved)
959
0
{
960
  // NOLINTBEGIN(concurrency-mt-unsafe)
961
0
  if (saved)
962
0
  {
963
0
    setenv("TZ", saved, 1);
964
0
    free(saved);
965
0
  }
966
0
  else
967
0
    unsetenv("TZ");
968
0
  tzset();
969
  // NOLINTEND(concurrency-mt-unsafe)
970
0
}
971
#endif