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.

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

mercurial