summaryrefslogtreecommitdiffstats
path: root/winpr/libwinpr/timezone/timezone.c
diff options
context:
space:
mode:
Diffstat (limited to 'winpr/libwinpr/timezone/timezone.c')
-rw-r--r--winpr/libwinpr/timezone/timezone.c581
1 files changed, 581 insertions, 0 deletions
diff --git a/winpr/libwinpr/timezone/timezone.c b/winpr/libwinpr/timezone/timezone.c
new file mode 100644
index 0000000..f6d9874
--- /dev/null
+++ b/winpr/libwinpr/timezone/timezone.c
@@ -0,0 +1,581 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Time Zone
+ *
+ * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <winpr/config.h>
+
+#include <winpr/environment.h>
+#include <winpr/wtypes.h>
+#include <winpr/timezone.h>
+#include <winpr/crt.h>
+#include <winpr/file.h>
+#include "../log.h"
+
+#define TAG WINPR_TAG("timezone")
+
+#ifndef _WIN32
+
+#include <time.h>
+#include <unistd.h>
+
+#include "TimeZones.h"
+#include "WindowsZones.h"
+
+static UINT64 winpr_windows_gmtime(void)
+{
+ time_t unix_time = 0;
+ UINT64 windows_time = 0;
+ time(&unix_time);
+
+ if (unix_time < 0)
+ return 0;
+
+ windows_time = (UINT64)unix_time;
+ windows_time *= 10000000;
+ windows_time += 621355968000000000ULL;
+ return windows_time;
+}
+
+static char* winpr_read_unix_timezone_identifier_from_file(FILE* fp)
+{
+ const INT CHUNK_SIZE = 32;
+ INT64 rc = 0;
+ INT64 read = 0;
+ INT64 length = CHUNK_SIZE;
+ char* tzid = NULL;
+
+ tzid = (char*)malloc(length);
+ if (!tzid)
+ return NULL;
+
+ do
+ {
+ rc = fread(tzid + read, 1, length - read - 1, fp);
+ if (rc > 0)
+ read += rc;
+
+ if (read < (length - 1))
+ break;
+
+ length += CHUNK_SIZE;
+ char* tmp = (char*)realloc(tzid, length);
+ if (!tmp)
+ {
+ free(tzid);
+ return NULL;
+ }
+
+ tzid = tmp;
+ } while (rc > 0);
+
+ if (ferror(fp))
+ {
+ free(tzid);
+ return NULL;
+ }
+
+ tzid[read] = '\0';
+ if (read > 0)
+ {
+ if (tzid[read - 1] == '\n')
+ tzid[read - 1] = '\0';
+ }
+
+ return tzid;
+}
+
+static char* winpr_get_timezone_from_link(const char* links[], size_t count)
+{
+ const char* _links[] = { "/etc/localtime", "/etc/TZ" };
+
+ if (links == NULL)
+ {
+ links = _links;
+ count = ARRAYSIZE(_links);
+ }
+
+ /*
+ * On linux distros such as Redhat or Archlinux, a symlink at /etc/localtime
+ * will point to /usr/share/zoneinfo/region/place where region/place could be
+ * America/Montreal for example.
+ * Some distributions do have to symlink at /etc/TZ.
+ */
+
+ for (size_t x = 0; x < count; x++)
+ {
+ char* tzid = NULL;
+ const char* link = links[x];
+ char* buf = realpath(link, NULL);
+
+ if (buf)
+ {
+ size_t sep = 0;
+ size_t alloc = 0;
+ size_t pos = 0;
+ size_t len = pos = strlen(buf);
+
+ /* find the position of the 2nd to last "/" */
+ for (size_t i = 1; i <= len; i++)
+ {
+ const size_t curpos = len - i;
+ const char cur = buf[curpos];
+
+ if (cur == '/')
+ sep++;
+ if (sep >= 2)
+ {
+ alloc = i;
+ pos = len - i + 1;
+ break;
+ }
+ }
+
+ if ((len == 0) || (sep != 2))
+ goto end;
+
+ tzid = (char*)calloc(alloc + 1, sizeof(char));
+
+ if (!tzid)
+ goto end;
+
+ strncpy(tzid, &buf[pos], alloc);
+ WLog_DBG(TAG, "tzid: %s", tzid);
+ goto end;
+ }
+
+ end:
+ free(buf);
+ if (tzid)
+ return tzid;
+ }
+
+ return NULL;
+}
+
+#if defined(ANDROID)
+#include "../utils/android.h"
+
+static char* winpr_get_android_timezone_identifier(void)
+{
+ char* tzid = NULL;
+ JNIEnv* jniEnv;
+
+ /* Preferred: Try to get identifier from java TimeZone class */
+ if (jniVm && ((*jniVm)->GetEnv(jniVm, (void**)&jniEnv, JNI_VERSION_1_6) == JNI_OK))
+ {
+ const char* raw;
+ jclass jObjClass;
+ jobject jObj;
+ jmethodID jDefaultTimezone;
+ jmethodID jTimezoneIdentifier;
+ jstring tzJId;
+ jboolean attached = (*jniVm)->AttachCurrentThread(jniVm, &jniEnv, NULL);
+ jObjClass = (*jniEnv)->FindClass(jniEnv, "java/util/TimeZone");
+
+ if (!jObjClass)
+ goto fail;
+
+ jDefaultTimezone =
+ (*jniEnv)->GetStaticMethodID(jniEnv, jObjClass, "getDefault", "()Ljava/util/TimeZone;");
+
+ if (!jDefaultTimezone)
+ goto fail;
+
+ jObj = (*jniEnv)->CallStaticObjectMethod(jniEnv, jObjClass, jDefaultTimezone);
+
+ if (!jObj)
+ goto fail;
+
+ jTimezoneIdentifier =
+ (*jniEnv)->GetMethodID(jniEnv, jObjClass, "getID", "()Ljava/lang/String;");
+
+ if (!jTimezoneIdentifier)
+ goto fail;
+
+ tzJId = (*jniEnv)->CallObjectMethod(jniEnv, jObj, jTimezoneIdentifier);
+
+ if (!tzJId)
+ goto fail;
+
+ raw = (*jniEnv)->GetStringUTFChars(jniEnv, tzJId, 0);
+
+ if (raw)
+ tzid = _strdup(raw);
+
+ (*jniEnv)->ReleaseStringUTFChars(jniEnv, tzJId, raw);
+ fail:
+
+ if (attached)
+ (*jniVm)->DetachCurrentThread(jniVm);
+ }
+
+ /* Fall back to property, might not be available. */
+ if (!tzid)
+ {
+ FILE* fp = popen("getprop persist.sys.timezone", "r");
+
+ if (fp)
+ {
+ tzid = winpr_read_unix_timezone_identifier_from_file(fp);
+ pclose(fp);
+ }
+ }
+
+ return tzid;
+}
+#endif
+
+static char* winpr_get_unix_timezone_identifier_from_file(void)
+{
+#if defined(ANDROID)
+ return winpr_get_android_timezone_identifier();
+#else
+ FILE* fp = NULL;
+ char* tzid = NULL;
+#if defined(__FreeBSD__) || defined(__OpenBSD__)
+ fp = winpr_fopen("/var/db/zoneinfo", "r");
+#else
+ fp = winpr_fopen("/etc/timezone", "r");
+#endif
+
+ if (NULL == fp)
+ return NULL;
+
+ tzid = winpr_read_unix_timezone_identifier_from_file(fp);
+ fclose(fp);
+ if (tzid != NULL)
+ WLog_DBG(TAG, "tzid: %s", tzid);
+ return tzid;
+#endif
+}
+
+static BOOL winpr_match_unix_timezone_identifier_with_list(const char* tzid, const char* list)
+{
+ char* p = NULL;
+ char* list_copy = NULL;
+ char* context = NULL;
+
+ list_copy = _strdup(list);
+
+ if (!list_copy)
+ return FALSE;
+
+ p = strtok_s(list_copy, " ", &context);
+
+ while (p != NULL)
+ {
+ if (strcmp(p, tzid) == 0)
+ {
+ free(list_copy);
+ return TRUE;
+ }
+
+ p = strtok_s(NULL, " ", &context);
+ }
+
+ free(list_copy);
+ return FALSE;
+}
+
+static TIME_ZONE_ENTRY* winpr_detect_windows_time_zone(void)
+{
+ char* tzid = NULL;
+ char* ntzid = NULL;
+ LPCSTR tz = "TZ";
+
+ DWORD nSize = GetEnvironmentVariableA(tz, NULL, 0);
+ if (nSize)
+ {
+ tzid = (char*)malloc(nSize);
+ if (!GetEnvironmentVariableA(tz, tzid, nSize))
+ {
+ free(tzid);
+ tzid = NULL;
+ }
+ }
+
+ if (tzid == NULL)
+ tzid = winpr_get_unix_timezone_identifier_from_file();
+
+ if (tzid == NULL)
+ {
+ tzid = winpr_get_timezone_from_link(NULL, 0);
+ }
+ else
+ {
+ const char* zipath = "/usr/share/zoneinfo/";
+ char buf[1024] = { 0 };
+ const char* links[] = { buf };
+
+ snprintf(buf, ARRAYSIZE(buf), "%s%s", zipath, tzid);
+ ntzid = winpr_get_timezone_from_link(links, 1);
+ if (ntzid != NULL)
+ {
+ free(tzid);
+ tzid = ntzid;
+ }
+ }
+
+ if (tzid == NULL)
+ return NULL;
+
+ WLog_INFO(TAG, "tzid: %s", tzid);
+
+ for (size_t i = 0; i < TimeZoneTableNrElements; i++)
+ {
+ const TIME_ZONE_ENTRY* tze = &TimeZoneTable[i];
+
+ for (size_t j = 0; j < WindowsTimeZoneIdTableNrElements; j++)
+ {
+ const WINDOWS_TZID_ENTRY* wzid = &WindowsTimeZoneIdTable[j];
+
+ if (strcmp(tze->Id, wzid->windows) != 0)
+ continue;
+
+ if (winpr_match_unix_timezone_identifier_with_list(tzid, wzid->tzid))
+ {
+ TIME_ZONE_ENTRY* ctimezone = (TIME_ZONE_ENTRY*)malloc(sizeof(TIME_ZONE_ENTRY));
+ free(tzid);
+
+ if (!ctimezone)
+ return NULL;
+
+ *ctimezone = TimeZoneTable[i];
+ return ctimezone;
+ }
+ }
+ }
+
+ WLog_ERR(TAG, "Unable to find a match for unix timezone: %s", tzid);
+ free(tzid);
+ return NULL;
+}
+
+static const TIME_ZONE_RULE_ENTRY*
+winpr_get_current_time_zone_rule(const TIME_ZONE_RULE_ENTRY* rules, UINT32 count)
+{
+ UINT64 windows_time = 0;
+ windows_time = winpr_windows_gmtime();
+
+ for (UINT32 i = 0; i < count; i++)
+ {
+ if ((rules[i].TicksStart >= windows_time) && (windows_time >= rules[i].TicksEnd))
+ {
+ /*WLog_ERR(TAG, "Got rule %" PRIu32 " from table at %p with count %"PRIu32"", i,
+ * (void*) rules, count);*/
+ return &rules[i];
+ }
+ }
+
+ WLog_ERR(TAG, "Unable to get current timezone rule");
+ return NULL;
+}
+
+DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation)
+{
+ time_t t = 0;
+ struct tm tres;
+ struct tm* local_time = NULL;
+ TIME_ZONE_ENTRY* dtz = NULL;
+ LPTIME_ZONE_INFORMATION tz = lpTimeZoneInformation;
+ lpTimeZoneInformation->StandardBias = 0;
+ time(&t);
+ local_time = localtime_r(&t, &tres);
+ if (!local_time)
+ goto out_error;
+
+ memset(tz, 0, sizeof(TIME_ZONE_INFORMATION));
+#ifdef WINPR_HAVE_TM_GMTOFF
+ {
+ long bias = -(local_time->tm_gmtoff / 60L);
+
+ if (bias > INT32_MAX)
+ bias = INT32_MAX;
+
+ tz->Bias = (LONG)bias;
+ }
+#else
+ tz->Bias = 0;
+#endif
+ dtz = winpr_detect_windows_time_zone();
+
+ if (dtz != NULL)
+ {
+ const TIME_ZONE_INFORMATION empty = { 0 };
+
+ WLog_DBG(TAG, "tz: Bias=%" PRId32 " sn='%s' dln='%s'", dtz->Bias, dtz->StandardName,
+ dtz->DaylightName);
+
+ *tz = empty;
+ tz->Bias = dtz->Bias;
+
+ if (ConvertUtf8ToWChar(dtz->StandardName, tz->StandardName, ARRAYSIZE(tz->StandardName)) <
+ 0)
+ {
+ WLog_ERR(TAG, "StandardName conversion failed - using default");
+ goto out_error;
+ }
+
+ if (ConvertUtf8ToWChar(dtz->DaylightName, tz->DaylightName, ARRAYSIZE(tz->DaylightName)) <
+ 0)
+ {
+ WLog_ERR(TAG, "DaylightName conversion failed - using default");
+ goto out_error;
+ }
+
+ if ((dtz->SupportsDST) && (dtz->RuleTableCount > 0))
+ {
+ const TIME_ZONE_RULE_ENTRY* rule =
+ winpr_get_current_time_zone_rule(dtz->RuleTable, dtz->RuleTableCount);
+
+ if (rule != NULL)
+ {
+ tz->DaylightBias = -rule->DaylightDelta;
+ tz->StandardDate = rule->StandardDate;
+ tz->DaylightDate = rule->DaylightDate;
+ }
+ }
+
+ free(dtz);
+ /* 1 ... TIME_ZONE_ID_STANDARD
+ * 2 ... TIME_ZONE_ID_DAYLIGHT */
+ return local_time->tm_isdst ? 2 : 1;
+ }
+
+ /* could not detect timezone, use computed bias from tm_gmtoff */
+ WLog_DBG(TAG, "tz not found, using computed bias %" PRId32 ".", tz->Bias);
+out_error:
+ free(dtz);
+ memcpy(tz->StandardName, L"Client Local Time", sizeof(tz->StandardName));
+ memcpy(tz->DaylightName, L"Client Local Time", sizeof(tz->DaylightName));
+ return 0; /* TIME_ZONE_ID_UNKNOWN */
+}
+
+BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ return FALSE;
+}
+
+BOOL SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, LPFILETIME lpFileTime)
+{
+ WINPR_UNUSED(lpSystemTime);
+ WINPR_UNUSED(lpFileTime);
+ return FALSE;
+}
+
+BOOL FileTimeToSystemTime(const FILETIME* lpFileTime, LPSYSTEMTIME lpSystemTime)
+{
+ WINPR_UNUSED(lpFileTime);
+ WINPR_UNUSED(lpSystemTime);
+ return FALSE;
+}
+
+BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTimeZone,
+ LPSYSTEMTIME lpUniversalTime, LPSYSTEMTIME lpLocalTime)
+{
+ WINPR_UNUSED(lpTimeZone);
+ WINPR_UNUSED(lpUniversalTime);
+ WINPR_UNUSED(lpLocalTime);
+ return FALSE;
+}
+
+BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
+ LPSYSTEMTIME lpLocalTime, LPSYSTEMTIME lpUniversalTime)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ WINPR_UNUSED(lpLocalTime);
+ WINPR_UNUSED(lpUniversalTime);
+ return FALSE;
+}
+
+#endif
+
+/*
+ * GetDynamicTimeZoneInformation is provided by the SDK if _WIN32_WINNT >= 0x0600 in SDKs above 7.1A
+ * and incorrectly if _WIN32_WINNT >= 0x0501 in older SDKs
+ */
+#if !defined(_WIN32) || \
+ (defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
+ !defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */
+
+DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation)
+{
+ WINPR_UNUSED(pTimeZoneInformation);
+ return 0;
+}
+
+BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ return FALSE;
+}
+
+BOOL GetTimeZoneInformationForYear(USHORT wYear, PDYNAMIC_TIME_ZONE_INFORMATION pdtzi,
+ LPTIME_ZONE_INFORMATION ptzi)
+{
+ WINPR_UNUSED(wYear);
+ WINPR_UNUSED(pdtzi);
+ WINPR_UNUSED(ptzi);
+ return FALSE;
+}
+
+#endif
+
+#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0601)) /* Windows 7 */
+
+BOOL SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpUniversalTime, LPSYSTEMTIME lpLocalTime)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ WINPR_UNUSED(lpUniversalTime);
+ WINPR_UNUSED(lpLocalTime);
+ return FALSE;
+}
+
+BOOL TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpLocalTime, LPSYSTEMTIME lpUniversalTime)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ WINPR_UNUSED(lpLocalTime);
+ WINPR_UNUSED(lpUniversalTime);
+ return FALSE;
+}
+
+#endif
+
+#if !defined(_WIN32) || (defined(_WIN32) && (_WIN32_WINNT < 0x0602)) /* Windows 8 */
+
+DWORD EnumDynamicTimeZoneInformation(const DWORD dwIndex,
+ PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation)
+{
+ WINPR_UNUSED(dwIndex);
+ WINPR_UNUSED(lpTimeZoneInformation);
+ return 0;
+}
+
+DWORD GetDynamicTimeZoneInformationEffectiveYears(
+ const PDYNAMIC_TIME_ZONE_INFORMATION lpTimeZoneInformation, LPDWORD FirstYear, LPDWORD LastYear)
+{
+ WINPR_UNUSED(lpTimeZoneInformation);
+ WINPR_UNUSED(FirstYear);
+ WINPR_UNUSED(LastYear);
+ return 0;
+}
+
+#endif