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"
46WINPR_ATTR_MALLOC(free, 1)
47static
char* winpr_read_unix_timezone_identifier_from_file(FILE* fp)
49 const INT CHUNK_SIZE = 32;
52 size_t length = CHUNK_SIZE;
54 char* tzid = malloc(length);
60 rc = fread(tzid + read, 1, length - read - 1UL, fp);
64 if (read < (length - 1UL))
67 if (read > length - 1UL)
74 char* tmp = (
char*)realloc(tzid, length);
93 if (tzid[read - 1] ==
'\n')
94 tzid[read - 1] =
'\0';
101static const char* winpr_get_zoneinfo_dir(
void)
104 const char* dir = getenv(
"TZDIR");
105 if (!dir || (dir[0] ==
'\0'))
106 return "/usr/share/zoneinfo";
111static const char* winpr_get_timezone_from_zoneinfo_path(
const char* path)
113 char* dir = realpath(winpr_get_zoneinfo_dir(),
nullptr);
117 const char* name =
nullptr;
118 const size_t len = strlen(dir);
119 if ((strncmp(path, dir, len) == 0) && (path[len] ==
'/'))
120 name = &path[len + 1];
126 const char* variants[] = {
"posix/",
"right/" };
127 for (
size_t x = 0; x < ARRAYSIZE(variants); x++)
129 const size_t vlen = strlen(variants[x]);
130 if (strncmp(name, variants[x], vlen) == 0)
142WINPR_ATTR_MALLOC(free, 1)
143static
char* winpr_get_timezone_from_link(const
char* links[],
size_t count)
145 const char* _links[] = {
"/etc/localtime",
"/etc/TZ" };
147 if (links ==
nullptr)
150 count = ARRAYSIZE(_links);
160 for (
size_t x = 0; x < count; x++)
162 char* tzid =
nullptr;
163 const char* link = links[x];
164 char* buf = realpath(link,
nullptr);
168 const char* name = winpr_get_timezone_from_zoneinfo_path(buf);
171 tzid = _strdup(name);
173 WLog_DBG(TAG,
"tzid: %s", tzid);
180 size_t len = pos = strlen(buf);
183 for (
size_t i = 1; i <= len; i++)
185 const size_t curpos = len - i;
186 const char cur = buf[curpos];
198 if ((len == 0) || (sep != 2))
201 tzid = (
char*)calloc(alloc + 1,
sizeof(
char));
206 strncpy(tzid, &buf[pos], alloc);
207 WLog_DBG(TAG,
"tzid: %s", tzid);
221#include "../utils/android.h"
223WINPR_ATTR_MALLOC(free, 1)
224static
char* winpr_get_android_timezone_identifier(
void)
226 char* tzid =
nullptr;
230 if (jniVm && ((*jniVm)->GetEnv(jniVm, (
void**)&jniEnv, JNI_VERSION_1_6) == JNI_OK))
235 jmethodID jDefaultTimezone;
236 jmethodID jTimezoneIdentifier;
238 jboolean attached = (*jniVm)->AttachCurrentThread(jniVm, &jniEnv,
nullptr);
239 jObjClass = (*jniEnv)->FindClass(jniEnv,
"java/util/TimeZone");
245 (*jniEnv)->GetStaticMethodID(jniEnv, jObjClass,
"getDefault",
"()Ljava/util/TimeZone;");
247 if (!jDefaultTimezone)
250 jObj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jObjClass, jDefaultTimezone);
255 jTimezoneIdentifier =
256 (*jniEnv)->GetMethodID(jniEnv, jObjClass,
"getID",
"()Ljava/lang/String;");
258 if (!jTimezoneIdentifier)
261 tzJId = (*jniEnv)->CallObjectMethod(jniEnv, jObj, jTimezoneIdentifier);
266 raw = (*jniEnv)->GetStringUTFChars(jniEnv, tzJId, 0);
271 (*jniEnv)->ReleaseStringUTFChars(jniEnv, tzJId, raw);
275 (*jniVm)->DetachCurrentThread(jniVm);
281 FILE* fp = popen(
"getprop persist.sys.timezone",
"r");
285 tzid = winpr_read_unix_timezone_identifier_from_file(fp);
294WINPR_ATTR_MALLOC(free, 1)
295static
char* winpr_get_unix_timezone_identifier_from_file(
void)
298 return winpr_get_android_timezone_identifier();
301 char* tzid =
nullptr;
302#if !defined(WINPR_TIMEZONE_FILE)
304 "Please define WINPR_TIMEZONE_FILE with the path to your timezone file (e.g. /etc/timezone or similar)"
306 fp = winpr_fopen(WINPR_TIMEZONE_FILE,
"r");
312 tzid = winpr_read_unix_timezone_identifier_from_file(fp);
315 WLog_DBG(TAG,
"tzid: %s", tzid);
320static char* winpr_time_zone_from_env(
void)
323 char* tzid =
nullptr;
325 DWORD nSize = GetEnvironmentVariableA(tz,
nullptr, 0);
328 tzid = (
char*)calloc(nSize,
sizeof(
char));
331 if (!GetEnvironmentVariableA(tz, tzid, nSize))
333 else if (tzid[0] ==
':')
336 memmove(tzid, tzid + 1, nSize -
sizeof(
char));
347static char* winpr_translate_time_zone(
const char* tzid)
350 const char* links[] = { buf };
363 winpr_asprintf(&buf, &bsize,
"%s/%s", winpr_get_zoneinfo_dir(), tzid);
367 char* ntzid = winpr_get_timezone_from_link(links, 1);
372#if defined(__EMSCRIPTEN__)
374extern void winpr_timezone_js_get(
char* buffer,
size_t size);
376static char* winpr_get_browser_timezone_identifier(
void)
378 char tzid[128] = WINPR_C_ARRAY_INIT;
380 winpr_timezone_js_get(tzid,
sizeof(tzid));
384 return _strdup(tzid);
388static char* winpr_guess_time_zone(
void)
390 char* tzid = winpr_time_zone_from_env();
393#if defined(__EMSCRIPTEN__)
394 tzid = winpr_get_browser_timezone_identifier();
398 tzid = winpr_get_unix_timezone_identifier_from_file();
401 tzid = winpr_get_timezone_from_link(
nullptr, 0);
407 char* ntzid = winpr_translate_time_zone(tzid);
417static SYSTEMTIME tm2systemtime(
const struct tm* t)
423 st.wYear = (WORD)(1900 + t->tm_year);
424 st.wMonth = (WORD)t->tm_mon + 1;
425 st.wDay = (WORD)t->tm_mday;
426 st.wDayOfWeek = (WORD)t->tm_wday;
427 st.wHour = (WORD)t->tm_hour;
428 st.wMinute = (WORD)t->tm_min;
429 st.wSecond = (WORD)t->tm_sec;
430 st.wMilliseconds = 0;
435static struct tm systemtime2tm(const
SYSTEMTIME* st)
437 struct tm t = WINPR_C_ARRAY_INIT;
440 if (st->wYear >= 1900)
441 t.tm_year = st->wYear - 1900;
443 t.tm_mon = st->wMonth - 1;
444 t.tm_mday = st->wDay;
445 t.tm_wday = st->wDayOfWeek;
446 t.tm_hour = st->wHour;
447 t.tm_min = st->wMinute;
448 t.tm_sec = st->wSecond;
453static LONG get_gmtoff_min(
const struct tm* t)
456 return -(LONG)(t->tm_gmtoff / 60l);
459static struct tm update_tm(const struct tm* start)
462 struct tm cur = *start;
463 const time_t t = mktime(&cur);
464 struct tm next = WINPR_C_ARRAY_INIT;
465 (void)localtime_r(&t, &next);
469static struct tm next_day(const struct tm* start)
471 struct tm cur = *start;
477 return update_tm(&cur);
480static struct tm adjust_time(const struct tm* start,
int hour,
int minute)
482 struct tm cur = *start;
487 return update_tm(&cur);
491static WORD get_transition_weekday_occurrence(
const SYSTEMTIME* st)
494 struct tm start = systemtime2tm(st);
497 struct tm next = start;
503 const time_t t = mktime(&next);
506 struct tm cur = WINPR_C_ARRAY_INIT;
507 (void)localtime_r(&t, &cur);
509 if (cur.tm_mon + 1 != st->wMonth)
512 if (cur.tm_wday == st->wDayOfWeek)
514 if (cur.tm_mday <= st->wDay)
527static SYSTEMTIME tm2transitiontime(
const struct tm* cur)
532 t.wDay = get_transition_weekday_occurrence(&t);
539static SYSTEMTIME get_transition_time(
const struct tm* start, BOOL toDst)
541 BOOL toggled = FALSE;
542 struct tm first = adjust_time(start, 0, 0);
543 for (
int hour = 0; hour < 24; hour++)
545 struct tm cur = adjust_time(start, hour, 0);
546 if (cur.tm_isdst != first.tm_isdst)
551 if (toDst && (cur.tm_isdst > 0))
552 return tm2transitiontime(&cur);
553 else if (!toDst && (cur.tm_isdst == 0))
554 return tm2transitiontime(&cur);
557 return tm2transitiontime(start);
560static BOOL get_transition_date(
const struct tm* pstart, BOOL toDst,
SYSTEMTIME* pdate)
562 WINPR_ASSERT(pstart);
565 *pdate = tm2transitiontime(
nullptr);
567 struct tm start = update_tm(pstart);
568 if (start.tm_isdst < 0)
571 BOOL val = start.tm_isdst > 0;
572 BOOL toggled = FALSE;
573 struct tm cur = start;
574 struct tm last = cur;
575 for (
int day = 1; day <= 365; day++)
578 cur = next_day(&cur);
579 const BOOL curDst = (cur.tm_isdst > 0);
580 if ((val != curDst) && !toggled)
587 *pdate = get_transition_time(&last, toDst);
590 if (!toDst && !curDst)
592 *pdate = get_transition_time(&last, toDst);
600static LONG get_bias(
const struct tm* start, BOOL dstBias)
602 if ((start->tm_isdst > 0) && dstBias)
603 return get_gmtoff_min(start);
604 if ((start->tm_isdst == 0) && !dstBias)
605 return get_gmtoff_min(start);
606 if (start->tm_isdst < 0)
607 return get_gmtoff_min(start);
609 struct tm cur = *start;
610 for (
int day = 1; day <= 365; day++)
612 cur = next_day(&cur);
613 if ((cur.tm_isdst > 0) && dstBias)
614 return get_gmtoff_min(&cur);
615 else if ((cur.tm_isdst == 0) && !dstBias)
616 return get_gmtoff_min(&cur);
623 const char* winId = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_ID);
624 const char* winStd = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_STANDARD);
625 const char* winDst = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_DAYLIGHT);
628 (void)ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName));
630 (void)ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName));
632 (void)ConvertUtf8ToWChar(winId, tz->TimeZoneKeyName, ARRAYSIZE(tz->TimeZoneKeyName));
634 return winId !=
nullptr;
637static const char* weekday2str(WORD wDayOfWeek)
656 return "DAY-OF-MAGIC";
660static char* systemtime2str(
const SYSTEMTIME* t,
char* buffer,
size_t len)
664 if (memcmp(t, &empty,
sizeof(
SYSTEMTIME)) == 0)
665 (void)_snprintf(buffer, len,
"{ not set }");
668 (void)_snprintf(buffer, len,
669 "{ %" PRIu16
"-%" PRIu16
"-%" PRIu16
" [%s] %" PRIu16
":%" PRIu16
670 ":%" PRIu16
".%" PRIu16
"}",
671 t->wYear, t->wMonth, t->wDay, weekday2str(t->wDayOfWeek), t->wHour,
672 t->wMinute, t->wSecond, t->wMilliseconds);
677WINPR_ATTR_FORMAT_ARG(6, 7)
678static
void log_print(wLog* log, DWORD level, const
char* file, const
char* fkt,
size_t line,
679 WINPR_FORMAT_ARG const
char* fmt, ...)
681 if (!WLog_IsLevelActive(log, level))
684 va_list ap = WINPR_C_ARRAY_INIT;
686 WLog_PrintTextMessageVA(log, level, line, file, fkt, fmt, ap);
690#define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__)
692 const char* fkt,
size_t line)
696 char buffer[130] = WINPR_C_ARRAY_INIT;
697 DWORD level = WLOG_TRACE;
698 wLog* log = WLog_Get(TAG);
699 log_print(log, level, file, fkt, line,
"DYNAMIC_TIME_ZONE_INFORMATION {");
701 log_print(log, level, file, fkt, line,
" Bias=%" PRId32, tzif->Bias);
702 (void)ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer,
704 log_print(log, level, file, fkt, line,
" StandardName=%s", buffer);
705 log_print(log, level, file, fkt, line,
" StandardDate=%s",
706 systemtime2str(&tzif->StandardDate, buffer,
sizeof(buffer)));
707 log_print(log, level, file, fkt, line,
" StandardBias=%" PRId32, tzif->StandardBias);
709 (void)ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer,
711 log_print(log, level, file, fkt, line,
" DaylightName=%s", buffer);
712 log_print(log, level, file, fkt, line,
" DaylightDate=%s",
713 systemtime2str(&tzif->DaylightDate, buffer,
sizeof(buffer)));
714 log_print(log, level, file, fkt, line,
" DaylightBias=%" PRId32, tzif->DaylightBias);
715 (void)ConvertWCharNToUtf8(tzif->TimeZoneKeyName, ARRAYSIZE(tzif->TimeZoneKeyName), buffer,
717 log_print(log, level, file, fkt, line,
" TimeZoneKeyName=%s", buffer);
718 log_print(log, level, file, fkt, line,
" DynamicDaylightTimeDisabled=DST-%s",
719 tzif->DynamicDaylightTimeDisabled ?
"disabled" :
"enabled");
722 case TIME_ZONE_ID_DAYLIGHT:
723 log_print(log, level, file, fkt, line,
" DaylightDate in use");
725 case TIME_ZONE_ID_STANDARD:
726 log_print(log, level, file, fkt, line,
" StandardDate in use");
729 log_print(log, level, file, fkt, line,
" UnknownDate in use");
732 log_print(log, level, file, fkt, line,
"}");
738 DWORD rc = GetDynamicTimeZoneInformation(&dyn);
739 lpTimeZoneInformation->Bias = dyn.Bias;
740 lpTimeZoneInformation->DaylightBias = dyn.DaylightBias;
741 lpTimeZoneInformation->DaylightDate = dyn.DaylightDate;
742 lpTimeZoneInformation->StandardBias = dyn.StandardBias;
743 lpTimeZoneInformation->StandardDate = dyn.StandardDate;
744 memcpy(lpTimeZoneInformation->StandardName, dyn.StandardName,
745 sizeof(lpTimeZoneInformation->StandardName));
746 memcpy(lpTimeZoneInformation->DaylightName, dyn.DaylightName,
747 sizeof(lpTimeZoneInformation->DaylightName));
753 WINPR_UNUSED(lpTimeZoneInformation);
757BOOL SystemTimeToFileTime(
const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
759 WINPR_UNUSED(lpSystemTime);
760 WINPR_UNUSED(lpFileTime);
764BOOL FileTimeToSystemTime(
const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
766 WINPR_UNUSED(lpFileTime);
767 WINPR_UNUSED(lpSystemTime);
772 LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
774 WINPR_UNUSED(lpTimeZone);
775 WINPR_UNUSED(lpUniversalTime);
776 WINPR_UNUSED(lpLocalTime);
781 LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
783 WINPR_UNUSED(lpTimeZoneInformation);
784 WINPR_UNUSED(lpLocalTime);
785 WINPR_UNUSED(lpUniversalTime);
795#if !defined(_WIN32) || \
796 (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
797 !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501))
801 HAVE_TRANSITION_DATES = 0,
802 HAVE_NO_STANDARD_TRANSITION_DATE = 1,
803 HAVE_NO_DAYLIGHT_TRANSITION_DATE = 2
804} dyn_transition_result;
806static int dynamic_time_zone_from_localtime(
const struct tm* local_time,
809 WINPR_ASSERT(local_time);
811 int rc = HAVE_TRANSITION_DATES;
813 tz->Bias = get_bias(local_time, FALSE);
816 if (local_time->tm_isdst >= 0)
819 const LONG d = get_bias(local_time, TRUE);
820 tz->DaylightBias = -1 * (tz->Bias - d);
821 struct tm newyear = *local_time;
836 const struct tm* stdtransition = local_time;
837 const struct tm* dsttransition = &newyear;
838 if (local_time->tm_isdst == 0)
840 stdtransition = &newyear;
841 dsttransition = local_time;
843 if (!get_transition_date(stdtransition, FALSE, &tz->StandardDate))
845 rc |= HAVE_NO_STANDARD_TRANSITION_DATE;
846 tz->StandardBias = 0;
848 if (!get_transition_date(dsttransition, TRUE, &tz->DaylightDate))
850 rc |= HAVE_NO_DAYLIGHT_TRANSITION_DATE;
851 tz->DaylightBias = 0;
859 const char** list =
nullptr;
860 char* tzid =
nullptr;
861 const char* defaultName =
"Client Local Time";
862 DWORD res = TIME_ZONE_ID_UNKNOWN;
865 WINPR_ASSERT(pTimeZoneInformation);
867 *pTimeZoneInformation = empty;
868 (void)ConvertUtf8ToWChar(defaultName, pTimeZoneInformation->StandardName,
869 ARRAYSIZE(pTimeZoneInformation->StandardName));
871 const time_t t = time(
nullptr);
872 struct tm tres = WINPR_C_ARRAY_INIT;
873 struct tm* local_time = localtime_r(&t, &tres);
877 pTimeZoneInformation->Bias = get_bias(local_time, FALSE);
878 if (local_time->tm_isdst >= 0)
879 dynamic_time_zone_from_localtime(local_time, pTimeZoneInformation);
881 tzid = winpr_guess_time_zone();
882 if (!map_iana_id(tzid, pTimeZoneInformation))
884 const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone,
nullptr, 0);
885 list = (
const char**)calloc(len,
sizeof(
const char*));
888 const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len);
889 for (
size_t x = 0; x < size; x++)
891 const char*
id = list[x];
892 if (map_iana_id(
id, pTimeZoneInformation))
894 res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
900 res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
906 log_timezone(pTimeZoneInformation, res);
912 WINPR_UNUSED(lpTimeZoneInformation);
927#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601))
930 const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
932 WINPR_UNUSED(lpTimeZoneInformation);
933 WINPR_UNUSED(lpUniversalTime);
934 WINPR_UNUSED(lpLocalTime);
939 const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
941 WINPR_UNUSED(lpTimeZoneInformation);
942 WINPR_UNUSED(lpLocalTime);
943 WINPR_UNUSED(lpUniversalTime);
951DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex,
954 if (!lpTimeZoneInformation)
955 return ERROR_INVALID_PARAMETER;
957 *lpTimeZoneInformation = empty;
961 return ERROR_NO_MORE_ITEMS;
963 if (entry->DaylightName)
964 (void)ConvertUtf8ToWChar(entry->DaylightName, lpTimeZoneInformation->DaylightName,
965 ARRAYSIZE(lpTimeZoneInformation->DaylightName));
966 if (entry->StandardName)
967 (void)ConvertUtf8ToWChar(entry->StandardName, lpTimeZoneInformation->StandardName,
968 ARRAYSIZE(lpTimeZoneInformation->StandardName));
970 (void)ConvertUtf8ToWChar(entry->Id, lpTimeZoneInformation->TimeZoneKeyName,
971 ARRAYSIZE(lpTimeZoneInformation->TimeZoneKeyName));
973 const time_t t = time(
nullptr);
974 struct tm tres = WINPR_C_ARRAY_INIT;
976 char* tzcopy = entry->Iana ? setNewAndSaveOldTZ(entry->Iana) : nullptr;
978 struct tm* local_time = localtime_r(&t, &tres);
981 dynamic_time_zone_from_localtime(local_time, lpTimeZoneInformation);
984 restoreSavedTZ(tzcopy);
986 return ERROR_SUCCESS;
990DWORD GetDynamicTimeZoneInformationEffectiveYears(
994 WINPR_UNUSED(lpTimeZoneInformation);
995 WINPR_UNUSED(FirstYear);
996 WINPR_UNUSED(LastYear);
997 return ERROR_FILE_NOT_FOUND;
1000#elif _WIN32_WINNT < 0x0602
1001DWORD EnumDynamicTimeZoneInformation(DWORD dwIndex,
1004 WINPR_UNUSED(dwIndex);
1005 WINPR_UNUSED(lpTimeZoneInformation);
1006 return ERROR_NO_MORE_ITEMS;
1009DWORD GetDynamicTimeZoneInformationEffectiveYears(
1012 WINPR_UNUSED(lpTimeZoneInformation);
1013 WINPR_UNUSED(FirstYear);
1014 WINPR_UNUSED(LastYear);
1015 return ERROR_FILE_NOT_FOUND;
1020char* setNewAndSaveOldTZ(
const char* val)
1023 const char* otz = getenv(
"TZ");
1024 char* oldtz =
nullptr;
1026 oldtz = _strdup(otz);
1027 setenv(
"TZ", val, 1);
1033void restoreSavedTZ(
char* saved)
1038 setenv(
"TZ", saved, 1);