intl/icu/source/common/wintz.c

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /*
     2 ********************************************************************************
     3 *   Copyright (C) 2005-2013, International Business Machines
     4 *   Corporation and others.  All Rights Reserved.
     5 ********************************************************************************
     6 *
     7 * File WINTZ.CPP
     8 *
     9 ********************************************************************************
    10 */
    12 #include "unicode/utypes.h"
    14 #if U_PLATFORM_HAS_WIN32_API
    16 #include "wintz.h"
    17 #include "cmemory.h"
    18 #include "cstring.h"
    20 #include "unicode/ustring.h"
    21 #include "unicode/ures.h"
    23 #   define WIN32_LEAN_AND_MEAN
    24 #   define VC_EXTRALEAN
    25 #   define NOUSER
    26 #   define NOSERVICE
    27 #   define NOIME
    28 #   define NOMCX
    29 #include <windows.h>
    31 #define MAX_LENGTH_ID 40
    33 /* The layout of the Tzi value in the registry */
    34 typedef struct
    35 {
    36     int32_t bias;
    37     int32_t standardBias;
    38     int32_t daylightBias;
    39     SYSTEMTIME standardDate;
    40     SYSTEMTIME daylightDate;
    41 } TZI;
    43 /**
    44  * Various registry keys and key fragments.
    45  */
    46 static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\";
    47 static const char STANDARD_NAME_REGKEY[] = "StandardName";
    48 static const char STANDARD_TIME_REGKEY[] = " Standard Time";
    49 static const char TZI_REGKEY[] = "TZI";
    50 static const char STD_REGKEY[] = "Std";
    52 /**
    53  * HKLM subkeys used to probe for the flavor of Windows.  Note that we
    54  * specifically check for the "GMT" zone subkey; this is present on
    55  * NT, but on XP has become "GMT Standard Time".  We need to
    56  * discriminate between these cases.
    57  */
    58 static const char* const WIN_TYPE_PROBE_REGKEY[] = {
    59     /* WIN_9X_ME_TYPE */
    60     "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones",
    62     /* WIN_NT_TYPE */
    63     "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT"
    65     /* otherwise: WIN_2K_XP_TYPE */
    66 };
    68 /**
    69  * The time zone root subkeys (under HKLM) for different flavors of
    70  * Windows.
    71  */
    72 static const char* const TZ_REGKEY[] = {
    73     /* WIN_9X_ME_TYPE */
    74     "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\",
    76     /* WIN_NT_TYPE | WIN_2K_XP_TYPE */
    77     "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"
    78 };
    80 /**
    81  * Flavor of Windows, from our perspective.  Not a real OS version,
    82  * but rather the flavor of the layout of the time zone information in
    83  * the registry.
    84  */
    85 enum {
    86     WIN_9X_ME_TYPE = 1,
    87     WIN_NT_TYPE = 2,
    88     WIN_2K_XP_TYPE = 3
    89 };
    91 static int32_t gWinType = 0;
    93 static int32_t detectWindowsType()
    94 {
    95     int32_t winType;
    96     LONG result;
    97     HKEY hkey;
    99     /* Detect the version of windows by trying to open a sequence of
   100         probe keys.  We don't use the OS version API because what we
   101         really want to know is how the registry is laid out.
   102         Specifically, is it 9x/Me or not, and is it "GMT" or "GMT
   103         Standard Time". */
   104     for (winType = 0; winType < 2; winType++) {
   105         result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
   106                               WIN_TYPE_PROBE_REGKEY[winType],
   107                               0,
   108                               KEY_QUERY_VALUE,
   109                               &hkey);
   110         RegCloseKey(hkey);
   112         if (result == ERROR_SUCCESS) {
   113             break;
   114         }
   115     }
   117     return winType+1; /* +1 to bring it inline with the enum */
   118 }
   120 static LONG openTZRegKey(HKEY *hkey, const char *winid)
   121 {
   122     char subKeyName[110]; /* TODO: why 96?? */
   123     char *name;
   124     LONG result;
   126     /* This isn't thread safe, but it's good enough because the result should be constant per system. */
   127     if (gWinType <= 0) {
   128         gWinType = detectWindowsType();
   129     }
   131     uprv_strcpy(subKeyName, TZ_REGKEY[(gWinType != WIN_9X_ME_TYPE)]);
   132     name = &subKeyName[strlen(subKeyName)];
   133     uprv_strcat(subKeyName, winid);
   135     if (gWinType == WIN_9X_ME_TYPE) {
   136         /* Remove " Standard Time" */
   137         char *pStd = uprv_strstr(subKeyName, STANDARD_TIME_REGKEY);
   138         if (pStd) {
   139             *pStd = 0;
   140         }
   141     }
   143     result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
   144                             subKeyName,
   145                             0,
   146                             KEY_QUERY_VALUE,
   147                             hkey);
   148     return result;
   149 }
   151 static LONG getTZI(const char *winid, TZI *tzi)
   152 {
   153     DWORD cbData = sizeof(TZI);
   154     LONG result;
   155     HKEY hkey;
   157     result = openTZRegKey(&hkey, winid);
   159     if (result == ERROR_SUCCESS) {
   160         result = RegQueryValueExA(hkey,
   161                                     TZI_REGKEY,
   162                                     NULL,
   163                                     NULL,
   164                                     (LPBYTE)tzi,
   165                                     &cbData);
   167     }
   169     RegCloseKey(hkey);
   171     return result;
   172 }
   174 static LONG getSTDName(const char *winid, char *regStdName, int32_t length) {
   175     DWORD cbData = length;
   176     LONG result;
   177     HKEY hkey;
   179     result = openTZRegKey(&hkey, winid);
   181     if (result == ERROR_SUCCESS) {
   182         result = RegQueryValueExA(hkey,
   183                                     STD_REGKEY,
   184                                     NULL,
   185                                     NULL,
   186                                     (LPBYTE)regStdName,
   187                                     &cbData);
   189     }
   191     RegCloseKey(hkey);
   193     return result;
   194 }
   196 /*
   197   This code attempts to detect the Windows time zone, as set in the
   198   Windows Date and Time control panel.  It attempts to work on
   199   multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized
   200   installs.  It works by directly interrogating the registry and
   201   comparing the data there with the data returned by the
   202   GetTimeZoneInformation API, along with some other strategies.  The
   203   registry contains time zone data under one of two keys (depending on
   204   the flavor of Windows):
   206     HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\
   207     HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
   209   Under this key are several subkeys, one for each time zone.  These
   210   subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time"
   211   on WinNT/2k/XP.  There are some other wrinkles; see the code for
   212   details.  The subkey name is NOT LOCALIZED, allowing us to support
   213   localized installs.
   215   Under the subkey are data values.  We care about:
   217     Std   Standard time display name, localized
   218     TZI   Binary block of data
   220   The TZI data is of particular interest.  It contains the offset, two
   221   more offsets for standard and daylight time, and the start and end
   222   rules.  This is the same data returned by the GetTimeZoneInformation
   223   API.  The API may modify the data on the way out, so we have to be
   224   careful, but essentially we do a binary comparison against the TZI
   225   blocks of various registry keys.  When we find a match, we know what
   226   time zone Windows is set to.  Since the registry key is not
   227   localized, we can then translate the key through a simple table
   228   lookup into the corresponding ICU time zone.
   230   This strategy doesn't always work because there are zones which
   231   share an offset and rules, so more than one TZI block will match.
   232   For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
   233   their TZI blocks are identical.  For these cases, we fall back to a
   234   name lookup.  We attempt to match the display name as stored in the
   235   registry for the current zone to the display name stored in the
   236   registry for various Windows zones.  By comparing the registry data
   237   directly we avoid conversion complications.
   239   Author: Alan Liu
   240   Since: ICU 2.6
   241   Based on original code by Carl Brown <cbrown@xnetinc.com>
   242 */
   244 /**
   245  * Main Windows time zone detection function.  Returns the Windows
   246  * time zone, translated to an ICU time zone, or NULL upon failure.
   247  */
   248 U_CFUNC const char* U_EXPORT2
   249 uprv_detectWindowsTimeZone() {
   250     UErrorCode status = U_ZERO_ERROR;
   251     UResourceBundle* bundle = NULL;
   252     char* icuid = NULL;
   253     UChar apiStd[MAX_LENGTH_ID];
   254     char apiStdName[MAX_LENGTH_ID];
   255     char regStdName[MAX_LENGTH_ID];
   256     char tmpid[MAX_LENGTH_ID];
   257     int32_t len;
   258     int id;
   259     int errorCode;
   260     char ISOcode[3]; /* 2 letter iso code */
   262     LONG result;
   263     TZI tziKey;
   264     TZI tziReg;
   265     TIME_ZONE_INFORMATION apiTZI;
   267     /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
   268        to TZI.  We could also interrogate the registry directly; we do
   269        this below if needed. */
   270     uprv_memset(&apiTZI, 0, sizeof(apiTZI));
   271     uprv_memset(&tziKey, 0, sizeof(tziKey));
   272     uprv_memset(&tziReg, 0, sizeof(tziReg));
   273     GetTimeZoneInformation(&apiTZI);
   274     tziKey.bias = apiTZI.Bias;
   275     uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate,
   276            sizeof(apiTZI.StandardDate));
   277     uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate,
   278            sizeof(apiTZI.DaylightDate));
   280     /* Convert the wchar_t* standard name to char* */
   281     uprv_memset(apiStdName, 0, sizeof(apiStdName));
   282     u_strFromWCS(apiStd, MAX_LENGTH_ID, NULL, apiTZI.StandardName, -1, &status);
   283     u_austrncpy(apiStdName, apiStd, sizeof(apiStdName) - 1);
   285     tmpid[0] = 0;
   287     id = GetUserGeoID(GEOCLASS_NATION);
   288     errorCode = GetGeoInfo(id,GEO_ISO2,ISOcode,3,0);
   290     bundle = ures_openDirect(NULL, "windowsZones", &status);
   291     ures_getByKey(bundle, "mapTimezones", bundle, &status);
   293     /* Note: We get the winid not from static tables but from resource bundle. */
   294     while (U_SUCCESS(status) && ures_hasNext(bundle)) {
   295         UBool idFound = FALSE;
   296         const char* winid;
   297         UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status);
   298         if (U_FAILURE(status)) {
   299             break;
   300         }
   301         winid = ures_getKey(winTZ);
   302         result = getTZI(winid, &tziReg);
   304         if (result == ERROR_SUCCESS) {
   305             /* Windows alters the DaylightBias in some situations.
   306                Using the bias and the rules suffices, so overwrite
   307                these unreliable fields. */
   308             tziKey.standardBias = tziReg.standardBias;
   309             tziKey.daylightBias = tziReg.daylightBias;
   311             if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) {
   312                 const UChar* icuTZ = NULL;
   313                 if (errorCode != 0) {
   314                     icuTZ = ures_getStringByKey(winTZ, ISOcode, &len, &status);
   315                 }
   316                 if (errorCode==0 || icuTZ==NULL) {
   317                     /* fallback to default "001" and reset status */
   318                     status = U_ZERO_ERROR;
   319                     icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
   320                 }
   322                 if (U_SUCCESS(status)) {
   323                     /* Get the standard name from the registry key to compare with
   324                        the one from Windows API call. */
   325                     uprv_memset(regStdName, 0, sizeof(regStdName));
   326                     result = getSTDName(winid, regStdName, sizeof(regStdName));
   327                     if (result == ERROR_SUCCESS) {
   328                         if (uprv_strcmp(apiStdName, regStdName) == 0) {
   329                             idFound = TRUE;
   330                         }
   331                     }
   333                     /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows.
   334                      * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching
   335                      * the current time zone information)
   336                      */
   337                     if (idFound || tmpid[0] == 0) {
   338                         /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
   339                         int index=0;
   340                         while (! (*icuTZ == '\0' || *icuTZ ==' ')) {
   341                             tmpid[index++]=(char)(*icuTZ++);  /* safe to assume 'char' is ASCII compatible on windows */
   342                         }
   343                         tmpid[index]='\0';
   344                     }
   345                 }
   346             }
   347         }
   348         ures_close(winTZ);
   349         if (idFound) {
   350             break;
   351         }
   352     }
   354     /*
   355      * Copy the timezone ID to icuid to be returned.
   356      */
   357     if (tmpid[0] != 0) {
   358         len = uprv_strlen(tmpid);
   359         icuid = (char*)uprv_calloc(len + 1, sizeof(char));
   360         if (icuid != NULL) {
   361             uprv_strcpy(icuid, tmpid);
   362         }
   363     }
   365     ures_close(bundle);
   367     return icuid;
   368 }
   370 #endif /* U_PLATFORM_HAS_WIN32_API */

mercurial