22#include <winpr/config.h>
24#include <winpr/environment.h>
25#include <winpr/wtypes.h>
26#include <winpr/timezone.h>
28#include <winpr/assert.h>
29#include <winpr/file.h>
33#define TAG WINPR_TAG("timezone")
35#include "TimeZoneNameMap.h"
36#include "TimeZoneIanaAbbrevMap.h"
46static char* winpr_read_unix_timezone_identifier_from_file(FILE* fp)
48 const INT CHUNK_SIZE = 32;
51 size_t length = CHUNK_SIZE;
53 char* tzid = malloc(length);
59 rc = fread(tzid + read, 1, length - read - 1UL, fp);
63 if (read < (length - 1UL))
66 if (read > length - 1UL)
73 char* tmp = (
char*)realloc(tzid, length);
92 if (tzid[read - 1] ==
'\n')
93 tzid[read - 1] =
'\0';
99static char* winpr_get_timezone_from_link(
const char* links[],
size_t count)
101 const char* _links[] = {
"/etc/localtime",
"/etc/TZ" };
103 if (links ==
nullptr)
106 count = ARRAYSIZE(_links);
116 for (
size_t x = 0; x < count; x++)
118 char* tzid =
nullptr;
119 const char* link = links[x];
120 char* buf = realpath(link,
nullptr);
127 size_t len = pos = strlen(buf);
130 for (
size_t i = 1; i <= len; i++)
132 const size_t curpos = len - i;
133 const char cur = buf[curpos];
145 if ((len == 0) || (sep != 2))
148 tzid = (
char*)calloc(alloc + 1,
sizeof(
char));
153 strncpy(tzid, &buf[pos], alloc);
154 WLog_DBG(TAG,
"tzid: %s", tzid);
168#include "../utils/android.h"
170static char* winpr_get_android_timezone_identifier(
void)
172 char* tzid =
nullptr;
176 if (jniVm && ((*jniVm)->GetEnv(jniVm, (
void**)&jniEnv, JNI_VERSION_1_6) == JNI_OK))
181 jmethodID jDefaultTimezone;
182 jmethodID jTimezoneIdentifier;
184 jboolean attached = (*jniVm)->AttachCurrentThread(jniVm, &jniEnv,
nullptr);
185 jObjClass = (*jniEnv)->FindClass(jniEnv,
"java/util/TimeZone");
191 (*jniEnv)->GetStaticMethodID(jniEnv, jObjClass,
"getDefault",
"()Ljava/util/TimeZone;");
193 if (!jDefaultTimezone)
196 jObj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jObjClass, jDefaultTimezone);
201 jTimezoneIdentifier =
202 (*jniEnv)->GetMethodID(jniEnv, jObjClass,
"getID",
"()Ljava/lang/String;");
204 if (!jTimezoneIdentifier)
207 tzJId = (*jniEnv)->CallObjectMethod(jniEnv, jObj, jTimezoneIdentifier);
212 raw = (*jniEnv)->GetStringUTFChars(jniEnv, tzJId, 0);
217 (*jniEnv)->ReleaseStringUTFChars(jniEnv, tzJId, raw);
221 (*jniVm)->DetachCurrentThread(jniVm);
227 FILE* fp = popen(
"getprop persist.sys.timezone",
"r");
231 tzid = winpr_read_unix_timezone_identifier_from_file(fp);
240static char* winpr_get_unix_timezone_identifier_from_file(
void)
243 return winpr_get_android_timezone_identifier();
246 char* tzid =
nullptr;
247#if !defined(WINPR_TIMEZONE_FILE)
249 "Please define WINPR_TIMEZONE_FILE with the path to your timezone file (e.g. /etc/timezone or similar)"
251 fp = winpr_fopen(WINPR_TIMEZONE_FILE,
"r");
257 tzid = winpr_read_unix_timezone_identifier_from_file(fp);
260 WLog_DBG(TAG,
"tzid: %s", tzid);
265static char* winpr_time_zone_from_env(
void)
268 char* tzid =
nullptr;
270 DWORD nSize = GetEnvironmentVariableA(tz,
nullptr, 0);
273 tzid = (
char*)calloc(nSize,
sizeof(
char));
276 if (!GetEnvironmentVariableA(tz, tzid, nSize))
278 else if (tzid[0] ==
':')
281 memmove(tzid, tzid + 1, nSize -
sizeof(
char));
292static char* winpr_translate_time_zone(
const char* tzid)
294 const char* zipath =
"/usr/share/zoneinfo/";
296 const char* links[] = { buf };
309 winpr_asprintf(&buf, &bsize,
"%s%s", zipath, tzid);
313 char* ntzid = winpr_get_timezone_from_link(links, 1);
318static char* winpr_guess_time_zone(
void)
320 char* tzid = winpr_time_zone_from_env();
323 tzid = winpr_get_unix_timezone_identifier_from_file();
326 tzid = winpr_get_timezone_from_link(
nullptr, 0);
332 char* ntzid = winpr_translate_time_zone(tzid);
342static SYSTEMTIME tm2systemtime(
const struct tm* t)
348 st.wYear = (WORD)(1900 + t->tm_year);
349 st.wMonth = (WORD)t->tm_mon + 1;
350 st.wDay = (WORD)t->tm_mday;
351 st.wDayOfWeek = (WORD)t->tm_wday;
352 st.wHour = (WORD)t->tm_hour;
353 st.wMinute = (WORD)t->tm_min;
354 st.wSecond = (WORD)t->tm_sec;
355 st.wMilliseconds = 0;
360static struct tm systemtime2tm(const
SYSTEMTIME* st)
362 struct tm t = WINPR_C_ARRAY_INIT;
365 if (st->wYear >= 1900)
366 t.tm_year = st->wYear - 1900;
368 t.tm_mon = st->wMonth - 1;
369 t.tm_mday = st->wDay;
370 t.tm_wday = st->wDayOfWeek;
371 t.tm_hour = st->wHour;
372 t.tm_min = st->wMinute;
373 t.tm_sec = st->wSecond;
378static LONG get_gmtoff_min(
const struct tm* t)
381 return -(LONG)(t->tm_gmtoff / 60l);
384static struct tm update_tm(const struct tm* start)
387 struct tm cur = *start;
388 const time_t t = mktime(&cur);
389 struct tm next = WINPR_C_ARRAY_INIT;
390 (void)localtime_r(&t, &next);
394static struct tm next_day(const struct tm* start)
396 struct tm cur = *start;
402 return update_tm(&cur);
405static struct tm adjust_time(const struct tm* start,
int hour,
int minute)
407 struct tm cur = *start;
412 return update_tm(&cur);
416static WORD get_transition_weekday_occurrence(
const SYSTEMTIME* st)
419 struct tm start = systemtime2tm(st);
422 struct tm next = start;
428 const time_t t = mktime(&next);
431 struct tm cur = WINPR_C_ARRAY_INIT;
432 (void)localtime_r(&t, &cur);
434 if (cur.tm_mon + 1 != st->wMonth)
437 if (cur.tm_wday == st->wDayOfWeek)
439 if (cur.tm_mday <= st->wDay)
452static SYSTEMTIME tm2transitiontime(
const struct tm* cur)
457 t.wDay = get_transition_weekday_occurrence(&t);
464static SYSTEMTIME get_transition_time(
const struct tm* start, BOOL toDst)
466 BOOL toggled = FALSE;
467 struct tm first = adjust_time(start, 0, 0);
468 for (
int hour = 0; hour < 24; hour++)
470 struct tm cur = adjust_time(start, hour, 0);
471 if (cur.tm_isdst != first.tm_isdst)
476 if (toDst && (cur.tm_isdst > 0))
477 return tm2transitiontime(&cur);
478 else if (!toDst && (cur.tm_isdst == 0))
479 return tm2transitiontime(&cur);
482 return tm2transitiontime(start);
485static BOOL get_transition_date(
const struct tm* pstart, BOOL toDst,
SYSTEMTIME* pdate)
487 WINPR_ASSERT(pstart);
490 *pdate = tm2transitiontime(
nullptr);
492 struct tm start = update_tm(pstart);
493 if (start.tm_isdst < 0)
496 BOOL val = start.tm_isdst > 0;
497 BOOL toggled = FALSE;
498 struct tm cur = start;
499 struct tm last = cur;
500 for (
int day = 1; day <= 365; day++)
503 cur = next_day(&cur);
504 const BOOL curDst = (cur.tm_isdst > 0);
505 if ((val != curDst) && !toggled)
512 *pdate = get_transition_time(&last, toDst);
515 if (!toDst && !curDst)
517 *pdate = get_transition_time(&last, toDst);
525static LONG get_bias(
const struct tm* start, BOOL dstBias)
527 if ((start->tm_isdst > 0) && dstBias)
528 return get_gmtoff_min(start);
529 if ((start->tm_isdst == 0) && !dstBias)
530 return get_gmtoff_min(start);
531 if (start->tm_isdst < 0)
532 return get_gmtoff_min(start);
534 struct tm cur = *start;
535 for (
int day = 1; day <= 365; day++)
537 cur = next_day(&cur);
538 if ((cur.tm_isdst > 0) && dstBias)
539 return get_gmtoff_min(&cur);
540 else if ((cur.tm_isdst == 0) && !dstBias)
541 return get_gmtoff_min(&cur);
548 const char* winId = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_ID);
549 const char* winStd = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_STANDARD);
550 const char* winDst = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_DAYLIGHT);
553 (void)ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName));
555 (void)ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName));
557 (void)ConvertUtf8ToWChar(winId, tz->TimeZoneKeyName, ARRAYSIZE(tz->TimeZoneKeyName));
559 return winId !=
nullptr;
562static const char* weekday2str(WORD wDayOfWeek)
581 return "DAY-OF-MAGIC";
585static char* systemtime2str(
const SYSTEMTIME* t,
char* buffer,
size_t len)
589 if (memcmp(t, &empty,
sizeof(
SYSTEMTIME)) == 0)
590 (void)_snprintf(buffer, len,
"{ not set }");
593 (void)_snprintf(buffer, len,
594 "{ %" PRIu16
"-%" PRIu16
"-%" PRIu16
" [%s] %" PRIu16
":%" PRIu16
595 ":%" PRIu16
".%" PRIu16
"}",
596 t->wYear, t->wMonth, t->wDay, weekday2str(t->wDayOfWeek), t->wHour,
597 t->wMinute, t->wSecond, t->wMilliseconds);
602WINPR_ATTR_FORMAT_ARG(6, 7)
603static
void log_print(wLog* log, DWORD level, const
char* file, const
char* fkt,
size_t line,
604 WINPR_FORMAT_ARG const
char* fmt, ...)
606 if (!WLog_IsLevelActive(log, level))
609 va_list ap = WINPR_C_ARRAY_INIT;
611 WLog_PrintTextMessageVA(log, level, line, file, fkt, fmt, ap);
615#define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__)
617 const char* fkt,
size_t line)
621 char buffer[130] = WINPR_C_ARRAY_INIT;
622 DWORD level = WLOG_TRACE;
623 wLog* log = WLog_Get(TAG);
624 log_print(log, level, file, fkt, line,
"DYNAMIC_TIME_ZONE_INFORMATION {");
626 log_print(log, level, file, fkt, line,
" Bias=%" PRId32, tzif->Bias);
627 (void)ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer,
629 log_print(log, level, file, fkt, line,
" StandardName=%s", buffer);
630 log_print(log, level, file, fkt, line,
" StandardDate=%s",
631 systemtime2str(&tzif->StandardDate, buffer,
sizeof(buffer)));
632 log_print(log, level, file, fkt, line,
" StandardBias=%" PRId32, tzif->StandardBias);
634 (void)ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer,
636 log_print(log, level, file, fkt, line,
" DaylightName=%s", buffer);
637 log_print(log, level, file, fkt, line,
" DaylightDate=%s",
638 systemtime2str(&tzif->DaylightDate, buffer,
sizeof(buffer)));
639 log_print(log, level, file, fkt, line,
" DaylightBias=%" PRId32, tzif->DaylightBias);
640 (void)ConvertWCharNToUtf8(tzif->TimeZoneKeyName, ARRAYSIZE(tzif->TimeZoneKeyName), buffer,
642 log_print(log, level, file, fkt, line,
" TimeZoneKeyName=%s", buffer);
643 log_print(log, level, file, fkt, line,
" DynamicDaylightTimeDisabled=DST-%s",
644 tzif->DynamicDaylightTimeDisabled ?
"disabled" :
"enabled");
647 case TIME_ZONE_ID_DAYLIGHT:
648 log_print(log, level, file, fkt, line,
" DaylightDate in use");
650 case TIME_ZONE_ID_STANDARD:
651 log_print(log, level, file, fkt, line,
" StandardDate in use");
654 log_print(log, level, file, fkt, line,
" UnknownDate in use");
657 log_print(log, level, file, fkt, line,
"}");
663 DWORD rc = GetDynamicTimeZoneInformation(&dyn);
664 lpTimeZoneInformation->Bias = dyn.Bias;
665 lpTimeZoneInformation->DaylightBias = dyn.DaylightBias;
666 lpTimeZoneInformation->DaylightDate = dyn.DaylightDate;
667 lpTimeZoneInformation->StandardBias = dyn.StandardBias;
668 lpTimeZoneInformation->StandardDate = dyn.StandardDate;
669 memcpy(lpTimeZoneInformation->StandardName, dyn.StandardName,
670 sizeof(lpTimeZoneInformation->StandardName));
671 memcpy(lpTimeZoneInformation->DaylightName, dyn.DaylightName,
672 sizeof(lpTimeZoneInformation->DaylightName));
678 WINPR_UNUSED(lpTimeZoneInformation);
682BOOL SystemTimeToFileTime(
const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
684 WINPR_UNUSED(lpSystemTime);
685 WINPR_UNUSED(lpFileTime);
689BOOL FileTimeToSystemTime(
const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
691 WINPR_UNUSED(lpFileTime);
692 WINPR_UNUSED(lpSystemTime);
697 LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
699 WINPR_UNUSED(lpTimeZone);
700 WINPR_UNUSED(lpUniversalTime);
701 WINPR_UNUSED(lpLocalTime);
706 LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
708 WINPR_UNUSED(lpTimeZoneInformation);
709 WINPR_UNUSED(lpLocalTime);
710 WINPR_UNUSED(lpUniversalTime);
720#if !defined(_WIN32) || \
721 (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
722 !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501))
726 HAVE_TRANSITION_DATES = 0,
727 HAVE_NO_STANDARD_TRANSITION_DATE = 1,
728 HAVE_NO_DAYLIGHT_TRANSITION_DATE = 2
729} dyn_transition_result;
731static int dynamic_time_zone_from_localtime(
const struct tm* local_time,
734 WINPR_ASSERT(local_time);
736 int rc = HAVE_TRANSITION_DATES;
738 tz->Bias = get_bias(local_time, FALSE);
741 if (local_time->tm_isdst >= 0)
744 const LONG d = get_bias(local_time, TRUE);
745 tz->DaylightBias = -1 * (tz->Bias - d);
746 struct tm newyear = *local_time;
761 const struct tm* stdtransition = local_time;
762 const struct tm* dsttransition = &newyear;
763 if (local_time->tm_isdst == 0)
765 stdtransition = &newyear;
766 dsttransition = local_time;
768 if (!get_transition_date(stdtransition, FALSE, &tz->StandardDate))
770 rc |= HAVE_NO_STANDARD_TRANSITION_DATE;
771 tz->StandardBias = 0;
773 if (!get_transition_date(dsttransition, TRUE, &tz->DaylightDate))
775 rc |= HAVE_NO_DAYLIGHT_TRANSITION_DATE;
776 tz->DaylightBias = 0;
784 const char** list =
nullptr;
785 char* tzid =
nullptr;
786 const char* defaultName =
"Client Local Time";
787 DWORD res = TIME_ZONE_ID_UNKNOWN;
790 WINPR_ASSERT(pTimeZoneInformation);
792 *pTimeZoneInformation = empty;
793 (void)ConvertUtf8ToWChar(defaultName, pTimeZoneInformation->StandardName,
794 ARRAYSIZE(pTimeZoneInformation->StandardName));
796 const time_t t = time(
nullptr);
797 struct tm tres = WINPR_C_ARRAY_INIT;
798 struct tm* local_time = localtime_r(&t, &tres);
802 pTimeZoneInformation->Bias = get_bias(local_time, FALSE);
803 if (local_time->tm_isdst >= 0)
804 dynamic_time_zone_from_localtime(local_time, pTimeZoneInformation);
806 tzid = winpr_guess_time_zone();
807 if (!map_iana_id(tzid, pTimeZoneInformation))
809 const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone,
nullptr, 0);
810 list = (
const char**)calloc(len,
sizeof(
const char*));
813 const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len);
814 for (
size_t x = 0; x < size; x++)
816 const char*
id = list[x];
817 if (map_iana_id(
id, pTimeZoneInformation))
819 res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
825 res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
831 log_timezone(pTimeZoneInformation, res);
837 WINPR_UNUSED(lpTimeZoneInformation);
852#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601))
855 const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
857 WINPR_UNUSED(lpTimeZoneInformation);
858 WINPR_UNUSED(lpUniversalTime);
859 WINPR_UNUSED(lpLocalTime);
864 const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
866 WINPR_UNUSED(lpTimeZoneInformation);
867 WINPR_UNUSED(lpLocalTime);
868 WINPR_UNUSED(lpUniversalTime);
876DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex,
879 if (!lpTimeZoneInformation)
880 return ERROR_INVALID_PARAMETER;
882 *lpTimeZoneInformation = empty;
886 return ERROR_NO_MORE_ITEMS;
888 if (entry->DaylightName)
889 (void)ConvertUtf8ToWChar(entry->DaylightName, lpTimeZoneInformation->DaylightName,
890 ARRAYSIZE(lpTimeZoneInformation->DaylightName));
891 if (entry->StandardName)
892 (void)ConvertUtf8ToWChar(entry->StandardName, lpTimeZoneInformation->StandardName,
893 ARRAYSIZE(lpTimeZoneInformation->StandardName));
895 (void)ConvertUtf8ToWChar(entry->Id, lpTimeZoneInformation->TimeZoneKeyName,
896 ARRAYSIZE(lpTimeZoneInformation->TimeZoneKeyName));
898 const time_t t = time(
nullptr);
899 struct tm tres = WINPR_C_ARRAY_INIT;
901 char* tzcopy = entry->Iana ? setNewAndSaveOldTZ(entry->Iana) : nullptr;
903 struct tm* local_time = localtime_r(&t, &tres);
906 dynamic_time_zone_from_localtime(local_time, lpTimeZoneInformation);
909 restoreSavedTZ(tzcopy);
911 return ERROR_SUCCESS;
915DWORD GetDynamicTimeZoneInformationEffectiveYears(
919 WINPR_UNUSED(lpTimeZoneInformation);
920 WINPR_UNUSED(FirstYear);
921 WINPR_UNUSED(LastYear);
922 return ERROR_FILE_NOT_FOUND;
925#elif _WIN32_WINNT < 0x0602
926DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex,
929 WINPR_UNUSED(dwIndex);
930 WINPR_UNUSED(lpTimeZoneInformation);
931 return ERROR_NO_MORE_ITEMS;
934DWORD GetDynamicTimeZoneInformationEffectiveYears(
937 WINPR_UNUSED(lpTimeZoneInformation);
938 WINPR_UNUSED(FirstYear);
939 WINPR_UNUSED(LastYear);
940 return ERROR_FILE_NOT_FOUND;
945char* setNewAndSaveOldTZ(
const char* val)
948 const char* otz = getenv(
"TZ");
949 char* oldtz =
nullptr;
951 oldtz = _strdup(otz);
952 setenv(
"TZ", val, 1);
958void restoreSavedTZ(
char* saved)
963 setenv(
"TZ", saved, 1);