Home | History | Annotate | Download | only in common
      1 /*
      2 ********************************************************************************
      3 *   Copyright (C) 2005-2015, International Business Machines
      4 *   Corporation and others.  All Rights Reserved.
      5 ********************************************************************************
      6 *
      7 * File WINTZ.CPP
      8 *
      9 ********************************************************************************
     10 */
     11 
     12 #include "unicode/utypes.h"
     13 
     14 #if U_PLATFORM_HAS_WIN32_API
     15 
     16 #include "wintz.h"
     17 #include "cmemory.h"
     18 #include "cstring.h"
     19 
     20 #include "unicode/ures.h"
     21 #include "unicode/ustring.h"
     22 
     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>
     30 
     31 #define MAX_LENGTH_ID 40
     32 
     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;
     42 
     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"; Currently unused constant */
     48 static const char STANDARD_TIME_REGKEY[] = " Standard Time";
     49 static const char TZI_REGKEY[] = "TZI";
     50 static const char STD_REGKEY[] = "Std";
     51 
     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",
     61 
     62     /* WIN_NT_TYPE */
     63     "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT"
     64 
     65     /* otherwise: WIN_2K_XP_TYPE */
     66 };
     67 
     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\\",
     75 
     76     /* WIN_NT_TYPE | WIN_2K_XP_TYPE */
     77     "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"
     78 };
     79 
     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 };
     90 
     91 static int32_t gWinType = 0;
     92 
     93 static int32_t detectWindowsType()
     94 {
     95     int32_t winType;
     96     LONG result;
     97     HKEY hkey;
     98 
     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);
    111 
    112         if (result == ERROR_SUCCESS) {
    113             break;
    114         }
    115     }
    116 
    117     return winType+1; /* +1 to bring it inline with the enum */
    118 }
    119 
    120 static LONG openTZRegKey(HKEY *hkey, const char *winid)
    121 {
    122     char subKeyName[110]; /* TODO: why 96?? */
    123     char *name;
    124     LONG result;
    125 
    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     }
    130 
    131     uprv_strcpy(subKeyName, TZ_REGKEY[(gWinType != WIN_9X_ME_TYPE)]);
    132     name = &subKeyName[strlen(subKeyName)];
    133     uprv_strcat(subKeyName, winid);
    134 
    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     }
    142 
    143     result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
    144                             subKeyName,
    145                             0,
    146                             KEY_QUERY_VALUE,
    147                             hkey);
    148     return result;
    149 }
    150 
    151 static LONG getTZI(const char *winid, TZI *tzi)
    152 {
    153     DWORD cbData = sizeof(TZI);
    154     LONG result;
    155     HKEY hkey;
    156 
    157     result = openTZRegKey(&hkey, winid);
    158 
    159     if (result == ERROR_SUCCESS) {
    160         result = RegQueryValueExA(hkey,
    161                                     TZI_REGKEY,
    162                                     NULL,
    163                                     NULL,
    164                                     (LPBYTE)tzi,
    165                                     &cbData);
    166 
    167     }
    168 
    169     RegCloseKey(hkey);
    170 
    171     return result;
    172 }
    173 
    174 static LONG getSTDName(const char *winid, char *regStdName, int32_t length) {
    175     DWORD cbData = length;
    176     LONG result;
    177     HKEY hkey;
    178 
    179     result = openTZRegKey(&hkey, winid);
    180 
    181     if (result == ERROR_SUCCESS) {
    182         result = RegQueryValueExA(hkey,
    183                                     STD_REGKEY,
    184                                     NULL,
    185                                     NULL,
    186                                     (LPBYTE)regStdName,
    187                                     &cbData);
    188 
    189     }
    190 
    191     RegCloseKey(hkey);
    192 
    193     return result;
    194 }
    195 
    196 static LONG getTZKeyName(char* tzKeyName, int32_t length) {
    197     HKEY hkey;
    198     LONG result = FALSE;
    199     DWORD cbData = length;
    200 
    201     if(ERROR_SUCCESS == RegOpenKeyExA(
    202         HKEY_LOCAL_MACHINE,
    203         CURRENT_ZONE_REGKEY,
    204         0,
    205         KEY_QUERY_VALUE,
    206         &hkey))
    207     {
    208          result = RegQueryValueExA(
    209              hkey,
    210              "TimeZoneKeyName",
    211              NULL,
    212              NULL,
    213              (LPBYTE)tzKeyName,
    214              &cbData);
    215     }
    216 
    217     return result;
    218 }
    219 
    220 /*
    221   This code attempts to detect the Windows time zone, as set in the
    222   Windows Date and Time control panel.  It attempts to work on
    223   multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized
    224   installs.  It works by directly interrogating the registry and
    225   comparing the data there with the data returned by the
    226   GetTimeZoneInformation API, along with some other strategies.  The
    227   registry contains time zone data under one of two keys (depending on
    228   the flavor of Windows):
    229 
    230     HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\
    231     HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
    232 
    233   Under this key are several subkeys, one for each time zone.  These
    234   subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time"
    235   on WinNT/2k/XP.  There are some other wrinkles; see the code for
    236   details.  The subkey name is NOT LOCALIZED, allowing us to support
    237   localized installs.
    238 
    239   Under the subkey are data values.  We care about:
    240 
    241     Std   Standard time display name, localized
    242     TZI   Binary block of data
    243 
    244   The TZI data is of particular interest.  It contains the offset, two
    245   more offsets for standard and daylight time, and the start and end
    246   rules.  This is the same data returned by the GetTimeZoneInformation
    247   API.  The API may modify the data on the way out, so we have to be
    248   careful, but essentially we do a binary comparison against the TZI
    249   blocks of various registry keys.  When we find a match, we know what
    250   time zone Windows is set to.  Since the registry key is not
    251   localized, we can then translate the key through a simple table
    252   lookup into the corresponding ICU time zone.
    253 
    254   This strategy doesn't always work because there are zones which
    255   share an offset and rules, so more than one TZI block will match.
    256   For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
    257   their TZI blocks are identical.  For these cases, we fall back to a
    258   name lookup.  We attempt to match the display name as stored in the
    259   registry for the current zone to the display name stored in the
    260   registry for various Windows zones.  By comparing the registry data
    261   directly we avoid conversion complications.
    262 
    263   Author: Alan Liu
    264   Since: ICU 2.6
    265   Based on original code by Carl Brown <cbrown (at) xnetinc.com>
    266 */
    267 
    268 /**
    269  * Main Windows time zone detection function.  Returns the Windows
    270  * time zone, translated to an ICU time zone, or NULL upon failure.
    271  */
    272 U_CFUNC const char* U_EXPORT2
    273 uprv_detectWindowsTimeZone() {
    274     UErrorCode status = U_ZERO_ERROR;
    275     UResourceBundle* bundle = NULL;
    276     char* icuid = NULL;
    277     char apiStdName[MAX_LENGTH_ID];
    278     char regStdName[MAX_LENGTH_ID];
    279     char tmpid[MAX_LENGTH_ID];
    280     int32_t len;
    281     int id;
    282     int errorCode;
    283     UChar ISOcodeW[3]; /* 2 letter iso code in UTF-16*/
    284     char  ISOcodeA[3]; /* 2 letter iso code in ansi */
    285 
    286     LONG result;
    287     TZI tziKey;
    288     TZI tziReg;
    289     TIME_ZONE_INFORMATION apiTZI;
    290 
    291     BOOL isVistaOrHigher;
    292     BOOL tryPreVistaFallback;
    293     OSVERSIONINFO osVerInfo;
    294 
    295     /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
    296        to TZI.  We could also interrogate the registry directly; we do
    297        this below if needed. */
    298     uprv_memset(&apiTZI, 0, sizeof(apiTZI));
    299     uprv_memset(&tziKey, 0, sizeof(tziKey));
    300     uprv_memset(&tziReg, 0, sizeof(tziReg));
    301     GetTimeZoneInformation(&apiTZI);
    302     tziKey.bias = apiTZI.Bias;
    303     uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate,
    304            sizeof(apiTZI.StandardDate));
    305     uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate,
    306            sizeof(apiTZI.DaylightDate));
    307 
    308     /* Convert the wchar_t* standard name to char* */
    309     uprv_memset(apiStdName, 0, sizeof(apiStdName));
    310     wcstombs(apiStdName, apiTZI.StandardName, MAX_LENGTH_ID);
    311 
    312     tmpid[0] = 0;
    313 
    314     id = GetUserGeoID(GEOCLASS_NATION);
    315     errorCode = GetGeoInfoW(id,GEO_ISO2,ISOcodeW,3,0);
    316     u_strToUTF8(ISOcodeA, 3, NULL, ISOcodeW, 3, &status);
    317 
    318     bundle = ures_openDirect(NULL, "windowsZones", &status);
    319     ures_getByKey(bundle, "mapTimezones", bundle, &status);
    320 
    321     /*
    322         Windows Vista+ provides us with a "TimeZoneKeyName" that is not localized
    323         and can be used to directly map a name in our bundle. Try to use that first
    324         if we're on Vista or higher
    325     */
    326     uprv_memset(&osVerInfo, 0, sizeof(osVerInfo));
    327     osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo);
    328     GetVersionEx(&osVerInfo);
    329     isVistaOrHigher = osVerInfo.dwMajorVersion >= 6;	/* actually includes Windows Server 2008 as well, but don't worry about it */
    330     tryPreVistaFallback = TRUE;
    331     if(isVistaOrHigher) {
    332         result = getTZKeyName(regStdName, sizeof(regStdName));
    333         if(ERROR_SUCCESS == result) {
    334             UResourceBundle* winTZ = ures_getByKey(bundle, regStdName, NULL, &status);
    335             if(U_SUCCESS(status)) {
    336                 const UChar* icuTZ = NULL;
    337                 if (errorCode != 0) {
    338                     icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status);
    339                 }
    340                 if (errorCode==0 || icuTZ==NULL) {
    341                     /* fallback to default "001" and reset status */
    342                     status = U_ZERO_ERROR;
    343                     icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
    344                 }
    345 
    346                 if(U_SUCCESS(status)) {
    347                     int index=0;
    348                     while (! (*icuTZ == '\0' || *icuTZ ==' ')) {
    349                         tmpid[index++]=(char)(*icuTZ++);  /* safe to assume 'char' is ASCII compatible on windows */
    350                     }
    351                     tmpid[index]='\0';
    352                     tryPreVistaFallback = FALSE;
    353                 }
    354             }
    355         }
    356     }
    357 
    358     if(tryPreVistaFallback) {
    359 
    360         /* Note: We get the winid not from static tables but from resource bundle. */
    361         while (U_SUCCESS(status) && ures_hasNext(bundle)) {
    362             UBool idFound = FALSE;
    363             const char* winid;
    364             UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status);
    365             if (U_FAILURE(status)) {
    366                 break;
    367             }
    368             winid = ures_getKey(winTZ);
    369             result = getTZI(winid, &tziReg);
    370 
    371             if (result == ERROR_SUCCESS) {
    372                 /* Windows alters the DaylightBias in some situations.
    373                    Using the bias and the rules suffices, so overwrite
    374                    these unreliable fields. */
    375                 tziKey.standardBias = tziReg.standardBias;
    376                 tziKey.daylightBias = tziReg.daylightBias;
    377 
    378                 if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) {
    379                     const UChar* icuTZ = NULL;
    380                     if (errorCode != 0) {
    381                         icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status);
    382                     }
    383                     if (errorCode==0 || icuTZ==NULL) {
    384                         /* fallback to default "001" and reset status */
    385                         status = U_ZERO_ERROR;
    386                         icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
    387                     }
    388 
    389                     if (U_SUCCESS(status)) {
    390                         /* Get the standard name from the registry key to compare with
    391                            the one from Windows API call. */
    392                         uprv_memset(regStdName, 0, sizeof(regStdName));
    393                         result = getSTDName(winid, regStdName, sizeof(regStdName));
    394                         if (result == ERROR_SUCCESS) {
    395                             if (uprv_strcmp(apiStdName, regStdName) == 0) {
    396                                 idFound = TRUE;
    397                             }
    398                         }
    399 
    400                         /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows.
    401                          * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching
    402                          * the current time zone information)
    403                          */
    404                         if (idFound || tmpid[0] == 0) {
    405                             /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
    406                             int index=0;
    407                             while (! (*icuTZ == '\0' || *icuTZ ==' ')) {
    408                                 tmpid[index++]=(char)(*icuTZ++);  /* safe to assume 'char' is ASCII compatible on windows */
    409                             }
    410                             tmpid[index]='\0';
    411                         }
    412                     }
    413                 }
    414             }
    415             ures_close(winTZ);
    416             if (idFound) {
    417                 break;
    418             }
    419         }
    420     }
    421 
    422     /*
    423      * Copy the timezone ID to icuid to be returned.
    424      */
    425     if (tmpid[0] != 0) {
    426         len = uprv_strlen(tmpid);
    427         icuid = (char*)uprv_calloc(len + 1, sizeof(char));
    428         if (icuid != NULL) {
    429             uprv_strcpy(icuid, tmpid);
    430         }
    431     }
    432 
    433     ures_close(bundle);
    434 
    435     return icuid;
    436 }
    437 
    438 #endif /* U_PLATFORM_HAS_WIN32_API */
    439