Coverage Report

Created: 2024-09-08 06:16

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