intl/icu/source/common/wintz.c

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/intl/icu/source/common/wintz.c	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,370 @@
     1.4 +/*
     1.5 +********************************************************************************
     1.6 +*   Copyright (C) 2005-2013, International Business Machines
     1.7 +*   Corporation and others.  All Rights Reserved.
     1.8 +********************************************************************************
     1.9 +*
    1.10 +* File WINTZ.CPP
    1.11 +*
    1.12 +********************************************************************************
    1.13 +*/
    1.14 +
    1.15 +#include "unicode/utypes.h"
    1.16 +
    1.17 +#if U_PLATFORM_HAS_WIN32_API
    1.18 +
    1.19 +#include "wintz.h"
    1.20 +#include "cmemory.h"
    1.21 +#include "cstring.h"
    1.22 +
    1.23 +#include "unicode/ustring.h"
    1.24 +#include "unicode/ures.h"
    1.25 +
    1.26 +#   define WIN32_LEAN_AND_MEAN
    1.27 +#   define VC_EXTRALEAN
    1.28 +#   define NOUSER
    1.29 +#   define NOSERVICE
    1.30 +#   define NOIME
    1.31 +#   define NOMCX
    1.32 +#include <windows.h>
    1.33 +
    1.34 +#define MAX_LENGTH_ID 40
    1.35 +
    1.36 +/* The layout of the Tzi value in the registry */
    1.37 +typedef struct
    1.38 +{
    1.39 +    int32_t bias;
    1.40 +    int32_t standardBias;
    1.41 +    int32_t daylightBias;
    1.42 +    SYSTEMTIME standardDate;
    1.43 +    SYSTEMTIME daylightDate;
    1.44 +} TZI;
    1.45 +
    1.46 +/**
    1.47 + * Various registry keys and key fragments.
    1.48 + */
    1.49 +static const char CURRENT_ZONE_REGKEY[] = "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation\\";
    1.50 +static const char STANDARD_NAME_REGKEY[] = "StandardName";
    1.51 +static const char STANDARD_TIME_REGKEY[] = " Standard Time";
    1.52 +static const char TZI_REGKEY[] = "TZI";
    1.53 +static const char STD_REGKEY[] = "Std";
    1.54 +
    1.55 +/**
    1.56 + * HKLM subkeys used to probe for the flavor of Windows.  Note that we
    1.57 + * specifically check for the "GMT" zone subkey; this is present on
    1.58 + * NT, but on XP has become "GMT Standard Time".  We need to
    1.59 + * discriminate between these cases.
    1.60 + */
    1.61 +static const char* const WIN_TYPE_PROBE_REGKEY[] = {
    1.62 +    /* WIN_9X_ME_TYPE */
    1.63 +    "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones",
    1.64 +
    1.65 +    /* WIN_NT_TYPE */
    1.66 +    "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\GMT"
    1.67 +
    1.68 +    /* otherwise: WIN_2K_XP_TYPE */
    1.69 +};
    1.70 +
    1.71 +/**
    1.72 + * The time zone root subkeys (under HKLM) for different flavors of
    1.73 + * Windows.
    1.74 + */
    1.75 +static const char* const TZ_REGKEY[] = {
    1.76 +    /* WIN_9X_ME_TYPE */
    1.77 +    "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\",
    1.78 +
    1.79 +    /* WIN_NT_TYPE | WIN_2K_XP_TYPE */
    1.80 +    "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\"
    1.81 +};
    1.82 +
    1.83 +/**
    1.84 + * Flavor of Windows, from our perspective.  Not a real OS version,
    1.85 + * but rather the flavor of the layout of the time zone information in
    1.86 + * the registry.
    1.87 + */
    1.88 +enum {
    1.89 +    WIN_9X_ME_TYPE = 1,
    1.90 +    WIN_NT_TYPE = 2,
    1.91 +    WIN_2K_XP_TYPE = 3
    1.92 +};
    1.93 +
    1.94 +static int32_t gWinType = 0;
    1.95 +
    1.96 +static int32_t detectWindowsType()
    1.97 +{
    1.98 +    int32_t winType;
    1.99 +    LONG result;
   1.100 +    HKEY hkey;
   1.101 +
   1.102 +    /* Detect the version of windows by trying to open a sequence of
   1.103 +        probe keys.  We don't use the OS version API because what we
   1.104 +        really want to know is how the registry is laid out.
   1.105 +        Specifically, is it 9x/Me or not, and is it "GMT" or "GMT
   1.106 +        Standard Time". */
   1.107 +    for (winType = 0; winType < 2; winType++) {
   1.108 +        result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
   1.109 +                              WIN_TYPE_PROBE_REGKEY[winType],
   1.110 +                              0,
   1.111 +                              KEY_QUERY_VALUE,
   1.112 +                              &hkey);
   1.113 +        RegCloseKey(hkey);
   1.114 +
   1.115 +        if (result == ERROR_SUCCESS) {
   1.116 +            break;
   1.117 +        }
   1.118 +    }
   1.119 +
   1.120 +    return winType+1; /* +1 to bring it inline with the enum */
   1.121 +}
   1.122 +
   1.123 +static LONG openTZRegKey(HKEY *hkey, const char *winid)
   1.124 +{
   1.125 +    char subKeyName[110]; /* TODO: why 96?? */
   1.126 +    char *name;
   1.127 +    LONG result;
   1.128 +
   1.129 +    /* This isn't thread safe, but it's good enough because the result should be constant per system. */
   1.130 +    if (gWinType <= 0) {
   1.131 +        gWinType = detectWindowsType();
   1.132 +    }
   1.133 +
   1.134 +    uprv_strcpy(subKeyName, TZ_REGKEY[(gWinType != WIN_9X_ME_TYPE)]);
   1.135 +    name = &subKeyName[strlen(subKeyName)];
   1.136 +    uprv_strcat(subKeyName, winid);
   1.137 +
   1.138 +    if (gWinType == WIN_9X_ME_TYPE) {
   1.139 +        /* Remove " Standard Time" */
   1.140 +        char *pStd = uprv_strstr(subKeyName, STANDARD_TIME_REGKEY);
   1.141 +        if (pStd) {
   1.142 +            *pStd = 0;
   1.143 +        }
   1.144 +    }
   1.145 +
   1.146 +    result = RegOpenKeyExA(HKEY_LOCAL_MACHINE,
   1.147 +                            subKeyName,
   1.148 +                            0,
   1.149 +                            KEY_QUERY_VALUE,
   1.150 +                            hkey);
   1.151 +    return result;
   1.152 +}
   1.153 +
   1.154 +static LONG getTZI(const char *winid, TZI *tzi)
   1.155 +{
   1.156 +    DWORD cbData = sizeof(TZI);
   1.157 +    LONG result;
   1.158 +    HKEY hkey;
   1.159 +
   1.160 +    result = openTZRegKey(&hkey, winid);
   1.161 +
   1.162 +    if (result == ERROR_SUCCESS) {
   1.163 +        result = RegQueryValueExA(hkey,
   1.164 +                                    TZI_REGKEY,
   1.165 +                                    NULL,
   1.166 +                                    NULL,
   1.167 +                                    (LPBYTE)tzi,
   1.168 +                                    &cbData);
   1.169 +
   1.170 +    }
   1.171 +
   1.172 +    RegCloseKey(hkey);
   1.173 +
   1.174 +    return result;
   1.175 +}
   1.176 +
   1.177 +static LONG getSTDName(const char *winid, char *regStdName, int32_t length) {
   1.178 +    DWORD cbData = length;
   1.179 +    LONG result;
   1.180 +    HKEY hkey;
   1.181 +
   1.182 +    result = openTZRegKey(&hkey, winid);
   1.183 +
   1.184 +    if (result == ERROR_SUCCESS) {
   1.185 +        result = RegQueryValueExA(hkey,
   1.186 +                                    STD_REGKEY,
   1.187 +                                    NULL,
   1.188 +                                    NULL,
   1.189 +                                    (LPBYTE)regStdName,
   1.190 +                                    &cbData);
   1.191 +
   1.192 +    }
   1.193 +
   1.194 +    RegCloseKey(hkey);
   1.195 +
   1.196 +    return result;
   1.197 +}
   1.198 +
   1.199 +/*
   1.200 +  This code attempts to detect the Windows time zone, as set in the
   1.201 +  Windows Date and Time control panel.  It attempts to work on
   1.202 +  multiple flavors of Windows (9x, Me, NT, 2000, XP) and on localized
   1.203 +  installs.  It works by directly interrogating the registry and
   1.204 +  comparing the data there with the data returned by the
   1.205 +  GetTimeZoneInformation API, along with some other strategies.  The
   1.206 +  registry contains time zone data under one of two keys (depending on
   1.207 +  the flavor of Windows):
   1.208 +
   1.209 +    HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones\
   1.210 +    HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\
   1.211 +
   1.212 +  Under this key are several subkeys, one for each time zone.  These
   1.213 +  subkeys are named "Pacific" on Win9x/Me and "Pacific Standard Time"
   1.214 +  on WinNT/2k/XP.  There are some other wrinkles; see the code for
   1.215 +  details.  The subkey name is NOT LOCALIZED, allowing us to support
   1.216 +  localized installs.
   1.217 +
   1.218 +  Under the subkey are data values.  We care about:
   1.219 +
   1.220 +    Std   Standard time display name, localized
   1.221 +    TZI   Binary block of data
   1.222 +
   1.223 +  The TZI data is of particular interest.  It contains the offset, two
   1.224 +  more offsets for standard and daylight time, and the start and end
   1.225 +  rules.  This is the same data returned by the GetTimeZoneInformation
   1.226 +  API.  The API may modify the data on the way out, so we have to be
   1.227 +  careful, but essentially we do a binary comparison against the TZI
   1.228 +  blocks of various registry keys.  When we find a match, we know what
   1.229 +  time zone Windows is set to.  Since the registry key is not
   1.230 +  localized, we can then translate the key through a simple table
   1.231 +  lookup into the corresponding ICU time zone.
   1.232 +
   1.233 +  This strategy doesn't always work because there are zones which
   1.234 +  share an offset and rules, so more than one TZI block will match.
   1.235 +  For example, both Tokyo and Seoul are at GMT+9 with no DST rules;
   1.236 +  their TZI blocks are identical.  For these cases, we fall back to a
   1.237 +  name lookup.  We attempt to match the display name as stored in the
   1.238 +  registry for the current zone to the display name stored in the
   1.239 +  registry for various Windows zones.  By comparing the registry data
   1.240 +  directly we avoid conversion complications.
   1.241 +
   1.242 +  Author: Alan Liu
   1.243 +  Since: ICU 2.6
   1.244 +  Based on original code by Carl Brown <cbrown@xnetinc.com>
   1.245 +*/
   1.246 +
   1.247 +/**
   1.248 + * Main Windows time zone detection function.  Returns the Windows
   1.249 + * time zone, translated to an ICU time zone, or NULL upon failure.
   1.250 + */
   1.251 +U_CFUNC const char* U_EXPORT2
   1.252 +uprv_detectWindowsTimeZone() {
   1.253 +    UErrorCode status = U_ZERO_ERROR;
   1.254 +    UResourceBundle* bundle = NULL;
   1.255 +    char* icuid = NULL;
   1.256 +    UChar apiStd[MAX_LENGTH_ID];
   1.257 +    char apiStdName[MAX_LENGTH_ID];
   1.258 +    char regStdName[MAX_LENGTH_ID];
   1.259 +    char tmpid[MAX_LENGTH_ID];
   1.260 +    int32_t len;
   1.261 +    int id;
   1.262 +    int errorCode;
   1.263 +    char ISOcode[3]; /* 2 letter iso code */
   1.264 +
   1.265 +    LONG result;
   1.266 +    TZI tziKey;
   1.267 +    TZI tziReg;
   1.268 +    TIME_ZONE_INFORMATION apiTZI;
   1.269 +
   1.270 +    /* Obtain TIME_ZONE_INFORMATION from the API, and then convert it
   1.271 +       to TZI.  We could also interrogate the registry directly; we do
   1.272 +       this below if needed. */
   1.273 +    uprv_memset(&apiTZI, 0, sizeof(apiTZI));
   1.274 +    uprv_memset(&tziKey, 0, sizeof(tziKey));
   1.275 +    uprv_memset(&tziReg, 0, sizeof(tziReg));
   1.276 +    GetTimeZoneInformation(&apiTZI);
   1.277 +    tziKey.bias = apiTZI.Bias;
   1.278 +    uprv_memcpy((char *)&tziKey.standardDate, (char*)&apiTZI.StandardDate,
   1.279 +           sizeof(apiTZI.StandardDate));
   1.280 +    uprv_memcpy((char *)&tziKey.daylightDate, (char*)&apiTZI.DaylightDate,
   1.281 +           sizeof(apiTZI.DaylightDate));
   1.282 +
   1.283 +    /* Convert the wchar_t* standard name to char* */
   1.284 +    uprv_memset(apiStdName, 0, sizeof(apiStdName));
   1.285 +    u_strFromWCS(apiStd, MAX_LENGTH_ID, NULL, apiTZI.StandardName, -1, &status);
   1.286 +    u_austrncpy(apiStdName, apiStd, sizeof(apiStdName) - 1);
   1.287 +
   1.288 +    tmpid[0] = 0;
   1.289 +
   1.290 +    id = GetUserGeoID(GEOCLASS_NATION);
   1.291 +    errorCode = GetGeoInfo(id,GEO_ISO2,ISOcode,3,0);
   1.292 +
   1.293 +    bundle = ures_openDirect(NULL, "windowsZones", &status);
   1.294 +    ures_getByKey(bundle, "mapTimezones", bundle, &status);
   1.295 +
   1.296 +    /* Note: We get the winid not from static tables but from resource bundle. */
   1.297 +    while (U_SUCCESS(status) && ures_hasNext(bundle)) {
   1.298 +        UBool idFound = FALSE;
   1.299 +        const char* winid;
   1.300 +        UResourceBundle* winTZ = ures_getNextResource(bundle, NULL, &status);
   1.301 +        if (U_FAILURE(status)) {
   1.302 +            break;
   1.303 +        }
   1.304 +        winid = ures_getKey(winTZ);
   1.305 +        result = getTZI(winid, &tziReg);
   1.306 +
   1.307 +        if (result == ERROR_SUCCESS) {
   1.308 +            /* Windows alters the DaylightBias in some situations.
   1.309 +               Using the bias and the rules suffices, so overwrite
   1.310 +               these unreliable fields. */
   1.311 +            tziKey.standardBias = tziReg.standardBias;
   1.312 +            tziKey.daylightBias = tziReg.daylightBias;
   1.313 +
   1.314 +            if (uprv_memcmp((char *)&tziKey, (char*)&tziReg, sizeof(tziKey)) == 0) {
   1.315 +                const UChar* icuTZ = NULL;
   1.316 +                if (errorCode != 0) {
   1.317 +                    icuTZ = ures_getStringByKey(winTZ, ISOcode, &len, &status);
   1.318 +                }
   1.319 +                if (errorCode==0 || icuTZ==NULL) {
   1.320 +                    /* fallback to default "001" and reset status */
   1.321 +                    status = U_ZERO_ERROR;
   1.322 +                    icuTZ = ures_getStringByKey(winTZ, "001", &len, &status);
   1.323 +                }
   1.324 +
   1.325 +                if (U_SUCCESS(status)) {
   1.326 +                    /* Get the standard name from the registry key to compare with
   1.327 +                       the one from Windows API call. */
   1.328 +                    uprv_memset(regStdName, 0, sizeof(regStdName));
   1.329 +                    result = getSTDName(winid, regStdName, sizeof(regStdName));
   1.330 +                    if (result == ERROR_SUCCESS) {
   1.331 +                        if (uprv_strcmp(apiStdName, regStdName) == 0) {
   1.332 +                            idFound = TRUE;
   1.333 +                        }
   1.334 +                    }
   1.335 +
   1.336 +                    /* tmpid buffer holds the ICU timezone ID corresponding to the timezone ID from Windows.
   1.337 +                     * If none is found, tmpid buffer will contain a fallback ID (i.e. the time zone ID matching
   1.338 +                     * the current time zone information)
   1.339 +                     */
   1.340 +                    if (idFound || tmpid[0] == 0) {
   1.341 +                        /* if icuTZ has more than one city, take only the first (i.e. terminate icuTZ at first space) */
   1.342 +                        int index=0;
   1.343 +                        while (! (*icuTZ == '\0' || *icuTZ ==' ')) {
   1.344 +                            tmpid[index++]=(char)(*icuTZ++);  /* safe to assume 'char' is ASCII compatible on windows */
   1.345 +                        }
   1.346 +                        tmpid[index]='\0';
   1.347 +                    }
   1.348 +                }
   1.349 +            }
   1.350 +        }
   1.351 +        ures_close(winTZ);
   1.352 +        if (idFound) {
   1.353 +            break;
   1.354 +        }
   1.355 +    }
   1.356 +
   1.357 +    /*
   1.358 +     * Copy the timezone ID to icuid to be returned.
   1.359 +     */
   1.360 +    if (tmpid[0] != 0) {
   1.361 +        len = uprv_strlen(tmpid);
   1.362 +        icuid = (char*)uprv_calloc(len + 1, sizeof(char));
   1.363 +        if (icuid != NULL) {
   1.364 +            uprv_strcpy(icuid, tmpid);
   1.365 +        }
   1.366 +    }
   1.367 +
   1.368 +    ures_close(bundle);
   1.369 +    
   1.370 +    return icuid;
   1.371 +}
   1.372 +
   1.373 +#endif /* U_PLATFORM_HAS_WIN32_API */

mercurial