Home | History | Annotate | Download | only in common
      1 /*
      2 ********************************************************************************
      3 *   Copyright (C) 2005-2012, 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/ustring.h"
     21 #include "unicode/ures.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";
     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 /*
    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):
    205 
    206     HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\
    207     HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
    208 
    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.
    214 
    215   Under the subkey are data values.  We care about:
    216 
    217     Std   Standard time display name, localized
    218     TZI   Binary block of data
    219 
    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.
    229 
    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.
    238 
    239   Author: Alan Liu
    240   Since: ICU 2.6
    241   Based on original code by Carl Brown <cbrown (at) xnetinc.com>
    242 */
    243 
    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 apiStdLength = 0;
    258     int32_t len;
    259     int id;
    260     int errorCode;
    261     char ISOcode[3]; /* 2 letter iso code */
    262 
    263     LONG result;
    264     TZI tziKey;
    265     TZI tziReg;
    266     TIME_ZONE_INFORMATION apiTZI;
    267 
    268     /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
    269        to TZI.  We could also interrogate the registry directly; we do
    270        this below if needed. */
    271     uprv_memset(&apiTZI, 0, sizeof(apiTZI));
    272     uprv_memset(&tziKey, 0, sizeof(tziKey));
    273     uprv_memset(&tziReg, 0, sizeof(tziReg));
    274     GetTimeZoneInformation(&apiTZI);
    275     tziKey.bias = apiTZI.Bias;
    276     uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate,
    277            sizeof(apiTZI.StandardDate));
    278     uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate,
    279            sizeof(apiTZI.DaylightDate));
    280 
    281     /* Convert the wchar_t* standard name to char* */
    282     uprv_memset(apiStdName, 0, sizeof(apiStdName));
    283     u_strFromWCS(apiStd, MAX_LENGTH_ID, &apiStdLength, apiTZI.StandardName, -1, &status);
    284     u_austrncpy(apiStdName, apiStd, apiStdLength);
    285 
    286     tmpid[0] = 0;
    287 
    288     id = GetUserGeoID(GEOCLASS_NATION);
    289     errorCode = GetGeoInfo(id,GEO_ISO2,ISOcode,3,0);
    290 
    291     bundle = ures_openDirect(NULL, "windowsZones", &status);
    292     ures_getByKey(bundle, "mapTimezones", bundle, &status);
    293 
    294     /* Note: We get the winid not from static tables but from resource bundle. */
    295     while (U_SUCCESS(status) && ures_hasNext(bundle)) {
    296         UBool idFound = FALSE;
    297         const char* winid;
    298         UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status);
    299         if (U_FAILURE(status)) {
    300             break;
    301         }
    302         winid = ures_getKey(winTZ);
    303         result = getTZI(winid, &tziReg);
    304 
    305         if (result == ERROR_SUCCESS) {
    306             /* Windows alters the DaylightBias in some situations.
    307                Using the bias and the rules suffices, so overwrite
    308                these unreliable fields. */
    309             tziKey.standardBias = tziReg.standardBias;
    310             tziKey.daylightBias = tziReg.daylightBias;
    311 
    312             if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) {
    313                 const UChar* icuTZ = NULL;
    314                 if (errorCode != 0) {
    315                     icuTZ = ures_getStringByKey(winTZ, ISOcode, &len, &status);
    316                 }
    317                 if (errorCode==0 || icuTZ==NULL) {
    318                     /* fallback to default "001" and reset status */
    319                     status = U_ZERO_ERROR;
    320                     icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
    321                 }
    322 
    323                 if (U_SUCCESS(status)) {
    324                     /* Get the standard name from the registry key to compare with
    325                        the one from Windows API call. */
    326                     uprv_memset(regStdName, 0, sizeof(regStdName));
    327                     result = getSTDName(winid, regStdName, sizeof(regStdName));
    328                     if (result == ERROR_SUCCESS) {
    329                         if (uprv_strcmp(apiStdName, regStdName) == 0) {
    330                             idFound = TRUE;
    331                         }
    332                     }
    333 
    334                     /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows.
    335                      * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching
    336                      * the current time zone information)
    337                      */
    338                     if (idFound || tmpid[0] == 0) {
    339                         /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
    340                         int index=0;
    341                         while (! (*icuTZ == '\0' || *icuTZ ==' ')) {
    342                             tmpid[index++]=(char)(*icuTZ++);  /* safe to assume 'char' is ASCII compatible on windows */
    343                         }
    344                         tmpid[index]='\0';
    345                     }
    346                 }
    347             }
    348         }
    349         ures_close(winTZ);
    350         if (idFound) {
    351             break;
    352         }
    353     }
    354 
    355     /*
    356      * Copy the timezone ID to icuid to be returned.
    357      */
    358     if (tmpid[0] != 0) {
    359         len = uprv_strlen(tmpid);
    360         icuid = (char*)uprv_calloc(len + 1, sizeof(char));
    361         if (icuid != NULL) {
    362             uprv_strcpy(icuid, tmpid);
    363         }
    364     }
    365 
    366     ures_close(bundle);
    367 
    368     return icuid;
    369 }
    370 
    371 #endif /* U_PLATFORM_HAS_WIN32_API */
    372