Home | History | Annotate | Download | only in common
      1 //  2016 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html
      3 /*
      4 ********************************************************************************
      5 *   Copyright (C) 2005-2015, International Business Machines
      6 *   Corporation and others.  All Rights Reserved.
      7 ********************************************************************************
      8 *
      9 * File WINTZ.CPP
     10 *
     11 ********************************************************************************
     12 */
     13 
     14 #include "unicode/utypes.h"
     15 
     16 // This file contains only desktop Windows behavior
     17 // Windows UWP calls Windows::Globalization directly, so this isn't needed there.
     18 #if U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0)
     19 
     20 #include "wintz.h"
     21 #include "cmemory.h"
     22 #include "cstring.h"
     23 
     24 #include "unicode/ures.h"
     25 #include "unicode/ustring.h"
     26 
     27 #ifndef WIN32_LEAN_AND_MEAN
     28 #   define WIN32_LEAN_AND_MEAN
     29 #endif
     30 #   define VC_EXTRALEAN
     31 #   define NOUSER
     32 #   define NOSERVICE
     33 #   define NOIME
     34 #   define NOMCX
     35 #include <windows.h>
     36 
     37 #define MAX_LENGTH_ID 40
     38 
     39 /* The layout of the Tzi value in the registry */
     40 typedef struct
     41 {
     42     int32_t bias;
     43     int32_t standardBias;
     44     int32_t daylightBias;
     45     SYSTEMTIME standardDate;
     46     SYSTEMTIME daylightDate;
     47 } TZI;
     48 
     49 /**
     50  * Various registry keys and key fragments.
     51  */
     52 static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\";
     53 static const char STANDARD_TIME_REGKEY[] = " Standard Time";
     54 static const char TZI_REGKEY[] = "TZI";
     55 static const char STD_REGKEY[] = "Std";
     56 
     57 /**
     58  * The time zone root keys (under HKLM) for Win7+
     59  */
     60 static const char TZ_REGKEY[] = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\";
     61 
     62 static LONG openTZRegKey(HKEY *hkey, const char *winid)
     63 {
     64     char subKeyName[110]; /* TODO: why 110?? */
     65     char *name;
     66     LONG result;
     67 
     68     uprv_strcpy(subKeyName, TZ_REGKEY);
     69     name = &subKeyName[strlen(subKeyName)];
     70     uprv_strcat(subKeyName, winid);
     71 
     72     result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
     73                             subKeyName,
     74                             0,
     75                             KEY_QUERY_VALUE,
     76                             hkey);
     77     return result;
     78 }
     79 
     80 static LONG getTZI(const char *winid, TZI *tzi)
     81 {
     82     DWORD cbData = sizeof(TZI);
     83     LONG result;
     84     HKEY hkey;
     85 
     86     result = openTZRegKey(&hkey, winid);
     87 
     88     if (result == ERROR_SUCCESS)
     89     {
     90         result = RegQueryValueExA(hkey,
     91                                     TZI_REGKEY,
     92                                     NULL,
     93                                     NULL,
     94                                     (LPBYTE)tzi,
     95                                     &cbData);
     96         RegCloseKey(hkey);
     97     }
     98 
     99     return result;
    100 }
    101 
    102 static LONG getSTDName(const char *winid, char *regStdName, int32_t length)
    103 {
    104     DWORD cbData = length;
    105     LONG result;
    106     HKEY hkey;
    107 
    108     result = openTZRegKey(&hkey, winid);
    109 
    110     if (result == ERROR_SUCCESS)
    111     {
    112         result = RegQueryValueExA(hkey,
    113                                     STD_REGKEY,
    114                                     NULL,
    115                                     NULL,
    116                                     (LPBYTE)regStdName,
    117                                     &cbData);
    118         RegCloseKey(hkey);
    119     }
    120 
    121     return result;
    122 }
    123 
    124 static LONG getTZKeyName(char* tzKeyName, int32_t length)
    125 {
    126     HKEY hkey;
    127     LONG result = FALSE;
    128     DWORD cbData = length;
    129 
    130     if(ERROR_SUCCESS == RegOpenKeyExA(
    131         HKEY_LOCAL_MACHINE,
    132         CURRENT_ZONE_REGKEY,
    133         0,
    134         KEY_QUERY_VALUE,
    135         &hkey))
    136     {
    137          result = RegQueryValueExA(
    138              hkey,
    139              "TimeZoneKeyName",
    140              NULL,
    141              NULL,
    142              (LPBYTE)tzKeyName,
    143              &cbData);
    144 
    145         RegCloseKey(hkey);
    146     }
    147 
    148     return result;
    149 }
    150 
    151 /*
    152   This code attempts to detect the Windows time zone directly,
    153   as set in the Windows Date and Time control panel.  It attempts
    154   to work on versions greater than Windows Vista and on localized
    155   installs.  It works by directly interrogating the registry and
    156   comparing the data there with the data returned by the
    157   GetTimeZoneInformation API, along with some other strategies.  The
    158   registry contains time zone data under this key:
    159 
    160     HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
    161 
    162   Under this key are several subkeys, one for each time zone.  For
    163   example these subkeys are named "Pacific Standard Time" on Vista+.
    164   There are some other wrinkles; see the code for
    165   details.  The subkey name is NOT LOCALIZED, allowing us to support
    166   localized installs.
    167 
    168   Under the subkey are data values.  We care about:
    169 
    170     Std   Standard time display name, localized
    171     TZI   Binary block of data
    172 
    173   The TZI data is of particular interest.  It contains the offset, two
    174   more offsets for standard and daylight time, and the start and end
    175   rules.  This is the same data returned by the GetTimeZoneInformation
    176   API.  The API may modify the data on the way out, so we have to be
    177   careful, but essentially we do a binary comparison against the TZI
    178   blocks of various registry keys.  When we find a match, we know what
    179   time zone Windows is set to.  Since the registry key is not
    180   localized, we can then translate the key through a simple table
    181   lookup into the corresponding ICU time zone.
    182 
    183   This strategy doesn't always work because there are zones which
    184   share an offset and rules, so more than one TZI block will match.
    185   For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
    186   their TZI blocks are identical.  For these cases, we fall back to a
    187   name lookup.  We attempt to match the display name as stored in the
    188   registry for the current zone to the display name stored in the
    189   registry for various Windows zones.  By comparing the registry data
    190   directly we avoid conversion complications.
    191 
    192   Author: Alan Liu
    193   Since: ICU 2.6
    194   Based on original code by Carl Brown <cbrown (at) xnetinc.com>
    195 */
    196 
    197 /**
    198  * Main Windows time zone detection function.  Returns the Windows
    199  * time zone, translated to an ICU time zone, or NULL upon failure.
    200  */
    201 U_CFUNC const char* U_EXPORT2
    202 uprv_detectWindowsTimeZone()
    203 {
    204     UErrorCode status = U_ZERO_ERROR;
    205     UResourceBundle* bundle = NULL;
    206     char* icuid = NULL;
    207     char apiStdName[MAX_LENGTH_ID];
    208     char regStdName[MAX_LENGTH_ID];
    209     char tmpid[MAX_LENGTH_ID];
    210     int32_t len;
    211     int id;
    212     int errorCode;
    213     wchar_t ISOcodeW[3]; /* 2 letter iso code in UTF-16*/
    214     char  ISOcodeA[3]; /* 2 letter iso code in ansi */
    215 
    216     LONG result;
    217     TZI tziKey;
    218     TZI tziReg;
    219     TIME_ZONE_INFORMATION apiTZI;
    220 
    221     BOOL tryPreVistaFallback;
    222     OSVERSIONINFO osVerInfo;
    223 
    224     /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
    225        to TZI.  We could also interrogate the registry directly; we do
    226        this below if needed. */
    227     uprv_memset(&apiTZI, 0, sizeof(apiTZI));
    228     uprv_memset(&tziKey, 0, sizeof(tziKey));
    229     uprv_memset(&tziReg, 0, sizeof(tziReg));
    230     GetTimeZoneInformation(&apiTZI);
    231     tziKey.bias = apiTZI.Bias;
    232     uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate,
    233            sizeof(apiTZI.StandardDate));
    234     uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate,
    235            sizeof(apiTZI.DaylightDate));
    236 
    237     /* Convert the wchar_t* standard name to char* */
    238     uprv_memset(apiStdName, 0, sizeof(apiStdName));
    239     wcstombs(apiStdName, apiTZI.StandardName, MAX_LENGTH_ID);
    240 
    241     tmpid[0] = 0;
    242 
    243     id = GetUserGeoID(GEOCLASS_NATION);
    244     errorCode = GetGeoInfoW(id, GEO_ISO2, ISOcodeW, 3, 0);
    245     u_strToUTF8(ISOcodeA, 3, NULL, (const UChar *)ISOcodeW, 3, &status);
    246 
    247     bundle = ures_openDirect(NULL, "windowsZones", &status);
    248     ures_getByKey(bundle, "mapTimezones", bundle, &status);
    249 
    250     /*
    251         Windows Vista+ provides us with a "TimeZoneKeyName" that is not localized
    252         and can be used to directly map a name in our bundle. Try to use that first
    253         if we're on Vista or higher
    254     */
    255     uprv_memset(&osVerInfo, 0, sizeof(osVerInfo));
    256     osVerInfo.dwOSVersionInfoSize = sizeof(osVerInfo);
    257     tryPreVistaFallback = TRUE;
    258     result = getTZKeyName(regStdName, sizeof(regStdName));
    259     if(ERROR_SUCCESS == result)
    260     {
    261         UResourceBundle* winTZ = ures_getByKey(bundle, regStdName, NULL, &status);
    262         if(U_SUCCESS(status))
    263         {
    264             const UChar* icuTZ = NULL;
    265             if (errorCode != 0)
    266             {
    267                 icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status);
    268             }
    269             if (errorCode==0 || icuTZ==NULL)
    270             {
    271                 /* fallback to default "001" and reset status */
    272                 status = U_ZERO_ERROR;
    273                 icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
    274             }
    275 
    276             if(U_SUCCESS(status))
    277             {
    278                 int index=0;
    279                 while (! (*icuTZ == '\0' || *icuTZ ==' '))
    280                 {
    281                     tmpid[index++]=(char)(*icuTZ++);  /* safe to assume 'char' is ASCII compatible on windows */
    282                 }
    283                 tmpid[index]='\0';
    284                 tryPreVistaFallback = FALSE;
    285             }
    286         }
    287         ures_close(winTZ);
    288     }
    289 
    290     if(tryPreVistaFallback)
    291     {
    292         /* Note: We get the winid not from static tables but from resource bundle. */
    293         while (U_SUCCESS(status) && ures_hasNext(bundle))
    294         {
    295             UBool idFound = FALSE;
    296             const char* winid;
    297             UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status);
    298             if (U_FAILURE(status))
    299             {
    300                 break;
    301             }
    302             winid = ures_getKey(winTZ);
    303             result = getTZI(winid, &tziReg);
    304 
    305             if (result == ERROR_SUCCESS)
    306             {
    307                 /* Windows alters the DaylightBias in some situations.
    308                    Using the bias and the rules suffices, so overwrite
    309                    these unreliable fields. */
    310                 tziKey.standardBias = tziReg.standardBias;
    311                 tziKey.daylightBias = tziReg.daylightBias;
    312 
    313                 if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0)
    314                 {
    315                     const UChar* icuTZ = NULL;
    316                     if (errorCode != 0)
    317                     {
    318                         icuTZ = ures_getStringByKey(winTZ, ISOcodeA, &len, &status);
    319                     }
    320                     if (errorCode==0 || icuTZ==NULL)
    321                     {
    322                         /* fallback to default "001" and reset status */
    323                         status = U_ZERO_ERROR;
    324                         icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
    325                     }
    326 
    327                     if (U_SUCCESS(status))
    328                     {
    329                         /* Get the standard name from the registry key to compare with
    330                            the one from Windows API call. */
    331                         uprv_memset(regStdName, 0, sizeof(regStdName));
    332                         result = getSTDName(winid, regStdName, sizeof(regStdName));
    333                         if (result == ERROR_SUCCESS)
    334                         {
    335                             if (uprv_strcmp(apiStdName, regStdName) == 0)
    336                             {
    337                                 idFound = TRUE;
    338                             }
    339                         }
    340 
    341                         /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows.
    342                          * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching
    343                          * the current time zone information)
    344                          */
    345                         if (idFound || tmpid[0] == 0)
    346                         {
    347                             /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
    348                             int index=0;
    349                             while (! (*icuTZ == '\0' || *icuTZ ==' '))
    350                             {
    351                                 tmpid[index++]=(char)(*icuTZ++);  /* safe to assume 'char' is ASCII compatible on windows */
    352                             }
    353                             tmpid[index]='\0';
    354                         }
    355                     }
    356                 }
    357             }
    358             ures_close(winTZ);
    359             if (idFound)
    360             {
    361                 break;
    362             }
    363         }
    364     }
    365 
    366     /*
    367      * Copy the timezone ID to icuid to be returned.
    368      */
    369     if (tmpid[0] != 0)
    370     {
    371         len = uprv_strlen(tmpid);
    372         icuid = (char*)uprv_calloc(len + 1, sizeof(char));
    373         if (icuid != NULL)
    374         {
    375             uprv_strcpy(icuid, tmpid);
    376         }
    377     }
    378 
    379     ures_close(bundle);
    380 
    381     return icuid;
    382 }
    383 
    384 #endif /* U_PLATFORM_USES_ONLY_WIN32_API && (U_PLATFORM_HAS_WINUWP_API == 0) */
    385