22 #include <winpr/config.h>
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>
32 #define TAG WINPR_TAG("timezone")
35 #define MIN(x, y) (((x) < (y)) ? (x) : (y))
38 #include "TimeZoneNameMap.h"
39 #include "TimeZoneIanaAbbrevMap.h"
49 static char* winpr_read_unix_timezone_identifier_from_file(FILE* fp)
51 const INT CHUNK_SIZE = 32;
54 size_t length = CHUNK_SIZE;
56 char* tzid = malloc(length);
62 rc = fread(tzid + read, 1, length - read - 1UL, fp);
66 if (read < (length - 1UL))
69 if (read > length - 1UL)
76 char* tmp = (
char*)realloc(tzid, length);
95 if (tzid[read - 1] ==
'\n')
96 tzid[read - 1] =
'\0';
102 static char* winpr_get_timezone_from_link(
const char* links[],
size_t count)
104 const char* _links[] = {
"/etc/localtime",
"/etc/TZ" };
109 count = ARRAYSIZE(_links);
119 for (
size_t x = 0; x < count; x++)
122 const char* link = links[x];
123 char* buf = realpath(link, NULL);
130 size_t len = pos = strlen(buf);
133 for (
size_t i = 1; i <= len; i++)
135 const size_t curpos = len - i;
136 const char cur = buf[curpos];
148 if ((len == 0) || (sep != 2))
151 tzid = (
char*)calloc(alloc + 1,
sizeof(
char));
156 strncpy(tzid, &buf[pos], alloc);
157 WLog_DBG(TAG,
"tzid: %s", tzid);
171 #include "../utils/android.h"
173 static char* winpr_get_android_timezone_identifier(
void)
179 if (jniVm && ((*jniVm)->GetEnv(jniVm, (
void**)&jniEnv, JNI_VERSION_1_6) == JNI_OK))
184 jmethodID jDefaultTimezone;
185 jmethodID jTimezoneIdentifier;
187 jboolean attached = (*jniVm)->AttachCurrentThread(jniVm, &jniEnv, NULL);
188 jObjClass = (*jniEnv)->FindClass(jniEnv,
"java/util/TimeZone");
194 (*jniEnv)->GetStaticMethodID(jniEnv, jObjClass,
"getDefault",
"()Ljava/util/TimeZone;");
196 if (!jDefaultTimezone)
199 jObj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jObjClass, jDefaultTimezone);
204 jTimezoneIdentifier =
205 (*jniEnv)->GetMethodID(jniEnv, jObjClass,
"getID",
"()Ljava/lang/String;");
207 if (!jTimezoneIdentifier)
210 tzJId = (*jniEnv)->CallObjectMethod(jniEnv, jObj, jTimezoneIdentifier);
215 raw = (*jniEnv)->GetStringUTFChars(jniEnv, tzJId, 0);
220 (*jniEnv)->ReleaseStringUTFChars(jniEnv, tzJId, raw);
224 (*jniVm)->DetachCurrentThread(jniVm);
230 FILE* fp = popen(
"getprop persist.sys.timezone",
"r");
234 tzid = winpr_read_unix_timezone_identifier_from_file(fp);
243 static char* winpr_get_unix_timezone_identifier_from_file(
void)
246 return winpr_get_android_timezone_identifier();
250 #if !defined(WINPR_TIMEZONE_FILE)
252 "Please define WINPR_TIMEZONE_FILE with the path to your timezone file (e.g. /etc/timezone or similar)"
254 fp = winpr_fopen(WINPR_TIMEZONE_FILE,
"r");
260 tzid = winpr_read_unix_timezone_identifier_from_file(fp);
263 WLog_DBG(TAG,
"tzid: %s", tzid);
268 static char* winpr_time_zone_from_env(
void)
273 DWORD nSize = GetEnvironmentVariableA(tz, NULL, 0);
276 tzid = (
char*)calloc(nSize,
sizeof(
char));
279 if (!GetEnvironmentVariableA(tz, tzid, nSize))
281 else if (tzid[0] ==
':')
284 memmove(tzid, tzid + 1, nSize -
sizeof(
char));
295 static char* winpr_translate_time_zone(
const char* tzid)
297 const char* zipath =
"/usr/share/zoneinfo/";
299 const char* links[] = { buf };
312 winpr_asprintf(&buf, &bsize,
"%s%s", zipath, tzid);
316 char* ntzid = winpr_get_timezone_from_link(links, 1);
321 static char* winpr_guess_time_zone(
void)
323 char* tzid = winpr_time_zone_from_env();
326 tzid = winpr_get_unix_timezone_identifier_from_file();
329 tzid = winpr_get_timezone_from_link(NULL, 0);
335 char* ntzid = winpr_translate_time_zone(tzid);
345 static SYSTEMTIME tm2systemtime(
const struct tm* t)
347 SYSTEMTIME st = { 0 };
351 st.wYear = (WORD)(1900 + t->tm_year);
352 st.wMonth = (WORD)t->tm_mon + 1;
353 st.wDay = (WORD)t->tm_mday;
354 st.wDayOfWeek = (WORD)t->tm_wday;
355 st.wHour = (WORD)t->tm_hour;
356 st.wMinute = (WORD)t->tm_min;
357 st.wSecond = (WORD)t->tm_sec;
358 st.wMilliseconds = 0;
363 static struct tm systemtime2tm(const SYSTEMTIME* st)
368 if (st->wYear >= 1900)
369 t.tm_year = st->wYear - 1900;
371 t.tm_mon = st->wMonth - 1;
372 t.tm_mday = st->wDay;
373 t.tm_wday = st->wDayOfWeek;
374 t.tm_hour = st->wHour;
375 t.tm_min = st->wMinute;
376 t.tm_sec = st->wSecond;
381 static LONG get_gmtoff_min(
const struct tm* t)
384 return -(LONG)(t->tm_gmtoff / 60l);
387 static struct tm next_day(const struct tm* start)
389 struct tm cur = *start;
395 const time_t t = mktime(&cur);
396 (void)localtime_r(&t, &cur);
400 static struct tm adjust_time(const struct tm* start,
int hour,
int minute)
402 struct tm cur = *start;
407 const time_t t = mktime(&cur);
408 (void)localtime_r(&t, &cur);
413 static WORD get_transition_weekday_occurrence(
const SYSTEMTIME* st)
416 struct tm start = systemtime2tm(st);
419 struct tm next = start;
425 const time_t t = mktime(&next);
428 struct tm cur = { 0 };
429 (void)localtime_r(&t, &cur);
431 if (cur.tm_mon + 1 != st->wMonth)
434 if (cur.tm_wday == st->wDayOfWeek)
436 if (cur.tm_mday <= st->wDay)
449 static SYSTEMTIME tm2transitiontime(
const struct tm* cur)
451 SYSTEMTIME t = tm2systemtime(cur);
454 t.wDay = get_transition_weekday_occurrence(&t);
461 static SYSTEMTIME get_transition_time(
const struct tm* start, BOOL toDst)
463 BOOL toggled = FALSE;
464 struct tm first = adjust_time(start, 0, 0);
465 for (
int hour = 0; hour < 24; hour++)
467 struct tm cur = adjust_time(start, hour, 0);
468 if (cur.tm_isdst != first.tm_isdst)
473 if (toDst && (cur.tm_isdst > 0))
474 return tm2transitiontime(&cur);
475 else if (!toDst && (cur.tm_isdst == 0))
476 return tm2transitiontime(&cur);
479 return tm2transitiontime(start);
482 static BOOL get_transition_date(
const struct tm* start, BOOL toDst, SYSTEMTIME* pdate)
487 *pdate = tm2transitiontime(NULL);
489 if (start->tm_isdst < 0)
492 BOOL val = start->tm_isdst > 0;
493 BOOL toggled = FALSE;
494 struct tm cur = *start;
495 struct tm last = cur;
496 for (
int day = 1; day <= 365; day++)
499 cur = next_day(&cur);
500 const BOOL curDst = (cur.tm_isdst > 0);
501 if ((val != curDst) && !toggled)
508 *pdate = get_transition_time(&last, toDst);
511 if (!toDst && !curDst)
513 *pdate = get_transition_time(&last, toDst);
521 static LONG get_bias(
const struct tm* start, BOOL dstBias)
523 if ((start->tm_isdst > 0) && dstBias)
524 return get_gmtoff_min(start);
525 if ((start->tm_isdst == 0) && !dstBias)
526 return get_gmtoff_min(start);
527 if (start->tm_isdst < 0)
528 return get_gmtoff_min(start);
530 struct tm cur = *start;
531 for (
int day = 1; day <= 365; day++)
533 cur = next_day(&cur);
534 if ((cur.tm_isdst > 0) && dstBias)
535 return get_gmtoff_min(&cur);
536 else if ((cur.tm_isdst == 0) && !dstBias)
537 return get_gmtoff_min(&cur);
544 const char* winId = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_ID);
545 const char* winStd = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_STANDARD);
546 const char* winDst = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_DAYLIGHT);
549 (void)ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName));
551 (void)ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName));
553 (void)ConvertUtf8ToWChar(winId, tz->TimeZoneKeyName, ARRAYSIZE(tz->TimeZoneKeyName));
555 return winId != NULL;
558 static const char* weekday2str(WORD wDayOfWeek)
577 return "DAY-OF-MAGIC";
581 static char* systemtime2str(
const SYSTEMTIME* t,
char* buffer,
size_t len)
583 const SYSTEMTIME empty = { 0 };
585 if (memcmp(t, &empty,
sizeof(SYSTEMTIME)) == 0)
586 (void)_snprintf(buffer, len,
"{ not set }");
589 (void)_snprintf(buffer, len,
590 "{ %" PRIu16
"-%" PRIu16
"-%" PRIu16
" [%s] %" PRIu16
":%" PRIu16
591 ":%" PRIu16
".%" PRIu16
"}",
592 t->wYear, t->wMonth, t->wDay, weekday2str(t->wDayOfWeek), t->wHour,
593 t->wMinute, t->wSecond, t->wMilliseconds);
598 static void log_print(wLog* log, DWORD level,
const char* file,
const char* fkt,
size_t line, ...)
600 if (!WLog_IsLevelActive(log, level))
605 WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, ap);
609 #define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__)
611 const char* fkt,
size_t line)
615 char buffer[130] = { 0 };
616 DWORD level = WLOG_TRACE;
617 wLog* log = WLog_Get(TAG);
618 log_print(log, level, file, fkt, line,
"DYNAMIC_TIME_ZONE_INFORMATION {");
620 log_print(log, level, file, fkt, line,
" Bias=%" PRIu32, tzif->Bias);
621 (void)ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer,
623 log_print(log, level, file, fkt, line,
" StandardName=%s", buffer);
624 log_print(log, level, file, fkt, line,
" StandardDate=%s",
625 systemtime2str(&tzif->StandardDate, buffer,
sizeof(buffer)));
626 log_print(log, level, file, fkt, line,
" StandardBias=%" PRIu32, tzif->StandardBias);
628 (void)ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer,
630 log_print(log, level, file, fkt, line,
" DaylightName=%s", buffer);
631 log_print(log, level, file, fkt, line,
" DaylightDate=%s",
632 systemtime2str(&tzif->DaylightDate, buffer,
sizeof(buffer)));
633 log_print(log, level, file, fkt, line,
" DaylightBias=%" PRIu32, tzif->DaylightBias);
634 (void)ConvertWCharNToUtf8(tzif->TimeZoneKeyName, ARRAYSIZE(tzif->TimeZoneKeyName), buffer,
636 log_print(log, level, file, fkt, line,
" TimeZoneKeyName=%s", buffer);
637 log_print(log, level, file, fkt, line,
" DynamicDaylightTimeDisabled=DST-%s",
638 tzif->DynamicDaylightTimeDisabled ?
"disabled" :
"enabled");
641 case TIME_ZONE_ID_DAYLIGHT:
642 log_print(log, level, file, fkt, line,
" DaylightDate in use");
644 case TIME_ZONE_ID_STANDARD:
645 log_print(log, level, file, fkt, line,
" StandardDate in use");
648 log_print(log, level, file, fkt, line,
" UnknownDate in use");
651 log_print(log, level, file, fkt, line,
"}");
657 DWORD rc = GetDynamicTimeZoneInformation(&dyn);
658 lpTimeZoneInformation->Bias = dyn.Bias;
659 lpTimeZoneInformation->DaylightBias = dyn.DaylightBias;
660 lpTimeZoneInformation->DaylightDate = dyn.DaylightDate;
661 lpTimeZoneInformation->StandardBias = dyn.StandardBias;
662 lpTimeZoneInformation->StandardDate = dyn.StandardDate;
663 memcpy(lpTimeZoneInformation->StandardName, dyn.StandardName,
664 sizeof(lpTimeZoneInformation->StandardName));
665 memcpy(lpTimeZoneInformation->DaylightName, dyn.DaylightName,
666 sizeof(lpTimeZoneInformation->DaylightName));
672 WINPR_UNUSED(lpTimeZoneInformation);
676 BOOL SystemTimeToFileTime(
const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
678 WINPR_UNUSED(lpSystemTime);
679 WINPR_UNUSED(lpFileTime);
683 BOOL FileTimeToSystemTime(
const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
685 WINPR_UNUSED(lpFileTime);
686 WINPR_UNUSED(lpSystemTime);
691 LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
693 WINPR_UNUSED(lpTimeZone);
694 WINPR_UNUSED(lpUniversalTime);
695 WINPR_UNUSED(lpLocalTime);
700 LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
702 WINPR_UNUSED(lpTimeZoneInformation);
703 WINPR_UNUSED(lpLocalTime);
704 WINPR_UNUSED(lpUniversalTime);
714 #if !defined(_WIN32) || \
715 (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
716 !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501))
720 HAVE_TRANSITION_DATES = 0,
721 HAVE_NO_STANDARD_TRANSITION_DATE = 1,
722 HAVE_NO_DAYLIGHT_TRANSITION_DATE = 2
723 } dyn_transition_result;
725 static int dynamic_time_zone_from_localtime(
const struct tm* local_time,
728 WINPR_ASSERT(local_time);
730 int rc = HAVE_TRANSITION_DATES;
732 tz->Bias = get_bias(local_time, FALSE);
733 if (local_time->tm_isdst >= 0)
736 const LONG d = get_bias(local_time, TRUE);
737 tz->DaylightBias = -1 * (LONG)labs(tz->Bias - d);
738 if (!get_transition_date(local_time, FALSE, &tz->StandardDate))
739 rc |= HAVE_NO_STANDARD_TRANSITION_DATE;
740 if (!get_transition_date(local_time, TRUE, &tz->DaylightDate))
741 rc |= HAVE_NO_DAYLIGHT_TRANSITION_DATE;
748 BOOL doesNotHaveStandardDate = FALSE;
749 BOOL doesNotHaveDaylightDate = FALSE;
750 const char** list = NULL;
752 const char* defaultName =
"Client Local Time";
753 DWORD res = TIME_ZONE_ID_UNKNOWN;
759 (void)ConvertUtf8ToWChar(defaultName, tz->StandardName, ARRAYSIZE(tz->StandardName));
761 const time_t t = time(NULL);
762 struct tm tres = { 0 };
763 struct tm* local_time = localtime_r(&t, &tres);
767 tz->Bias = get_bias(local_time, FALSE);
768 if (local_time->tm_isdst >= 0)
770 const int rc = dynamic_time_zone_from_localtime(local_time, tz);
771 if (rc & HAVE_NO_STANDARD_TRANSITION_DATE)
772 doesNotHaveStandardDate = TRUE;
773 if (rc & HAVE_NO_DAYLIGHT_TRANSITION_DATE)
774 doesNotHaveDaylightDate = TRUE;
777 tzid = winpr_guess_time_zone();
778 if (!map_iana_id(tzid, tz))
780 const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone, NULL, 0);
781 list = calloc(len,
sizeof(
char*));
784 const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len);
785 for (
size_t x = 0; x < size; x++)
787 const char*
id = list[x];
788 if (map_iana_id(
id, tz))
790 res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
796 res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
798 if (doesNotHaveDaylightDate)
799 tz->DaylightBias = 0;
801 if (doesNotHaveStandardDate)
802 tz->StandardBias = 0;
808 log_timezone(tz, res);
814 WINPR_UNUSED(lpTimeZoneInformation);
829 #if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601))
832 const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
834 WINPR_UNUSED(lpTimeZoneInformation);
835 WINPR_UNUSED(lpUniversalTime);
836 WINPR_UNUSED(lpLocalTime);
841 const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
843 WINPR_UNUSED(lpTimeZoneInformation);
844 WINPR_UNUSED(lpLocalTime);
845 WINPR_UNUSED(lpUniversalTime);
853 DWORD EnumDynamicTimeZoneInformation(
const DWORD dwIndex,
856 if (!lpTimeZoneInformation)
857 return ERROR_INVALID_PARAMETER;
859 *lpTimeZoneInformation = empty;
863 return ERROR_NO_MORE_ITEMS;
865 if (entry->DaylightName)
866 (void)ConvertUtf8ToWChar(entry->DaylightName, lpTimeZoneInformation->DaylightName,
867 ARRAYSIZE(lpTimeZoneInformation->DaylightName));
868 if (entry->StandardName)
869 (void)ConvertUtf8ToWChar(entry->StandardName, lpTimeZoneInformation->StandardName,
870 ARRAYSIZE(lpTimeZoneInformation->StandardName));
872 (void)ConvertUtf8ToWChar(entry->Id, lpTimeZoneInformation->TimeZoneKeyName,
873 ARRAYSIZE(lpTimeZoneInformation->TimeZoneKeyName));
875 const time_t t = time(NULL);
876 struct tm tres = { 0 };
878 const char* tz = getenv(
"TZ");
882 size_t tzianalen = 0;
883 winpr_asprintf(&tzcopy, &tzianalen,
"TZ=%s", tz);
888 size_t tzianalen = 0;
889 winpr_asprintf(&tziana, &tzianalen,
"TZ=%s", entry->Iana);
895 struct tm* local_time = localtime_r(&t, &tres);
904 dynamic_time_zone_from_localtime(local_time, lpTimeZoneInformation);
906 return ERROR_SUCCESS;
910 DWORD GetDynamicTimeZoneInformationEffectiveYears(
914 WINPR_UNUSED(lpTimeZoneInformation);
915 WINPR_UNUSED(FirstYear);
916 WINPR_UNUSED(LastYear);
917 return ERROR_FILE_NOT_FOUND;
920 #elif _WIN32_WINNT < 0x0602
921 DWORD EnumDynamicTimeZoneInformation(
const DWORD dwIndex,
924 WINPR_UNUSED(dwIndex);
925 WINPR_UNUSED(lpTimeZoneInformation);
926 return ERROR_NO_MORE_ITEMS;
929 DWORD GetDynamicTimeZoneInformationEffectiveYears(
932 WINPR_UNUSED(lpTimeZoneInformation);
933 WINPR_UNUSED(FirstYear);
934 WINPR_UNUSED(LastYear);
935 return ERROR_FILE_NOT_FOUND;