1 /*
   2  * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #include <windows.h>
  27 #include <stdio.h>
  28 #include <stdlib.h>
  29 #include "jvm.h"
  30 #include "TimeZone_md.h"
  31 #include "jdk_util.h"
  32 
  33 #define VALUE_UNKNOWN           0
  34 #define VALUE_KEY               1
  35 #define VALUE_MAPID             2
  36 #define VALUE_GMTOFFSET         3
  37 
  38 #define MAX_ZONE_CHAR           256
  39 #define MAX_MAPID_LENGTH        32
  40 
  41 #define NT_TZ_KEY               "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"
  42 #define WIN_TZ_KEY              "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones"
  43 #define WIN_CURRENT_TZ_KEY      "System\\CurrentControlSet\\Control\\TimeZoneInformation"
  44 
  45 typedef struct _TziValue {
  46     LONG        bias;
  47     LONG        stdBias;
  48     LONG        dstBias;
  49     SYSTEMTIME  stdDate;
  50     SYSTEMTIME  dstDate;
  51 } TziValue;
  52 
  53 #if _WIN32_WINNT < 0x0600 /* < _WIN32_WINNT_VISTA */
  54 typedef struct _TIME_DYNAMIC_ZONE_INFORMATION {
  55     LONG        Bias;
  56     WCHAR       StandardName[32];
  57     SYSTEMTIME  StandardDate;
  58     LONG        StandardBias;
  59     WCHAR       DaylightName[32];
  60     SYSTEMTIME  DaylightDate;
  61     LONG        DaylightBias;
  62     WCHAR       TimeZoneKeyName[128];
  63     BOOLEAN     DynamicDaylightTimeDisabled;
  64 } DYNAMIC_TIME_ZONE_INFORMATION, *PDYNAMIC_TIME_ZONE_INFORMATION;
  65 #endif
  66 
  67 /*
  68  * Registry key names
  69  */
  70 static void *keyNames[] = {
  71     (void *) L"StandardName",
  72     (void *) "StandardName",
  73     (void *) L"Std",
  74     (void *) "Std"
  75 };
  76 
  77 /*
  78  * Indices to keyNames[]
  79  */
  80 #define STANDARD_NAME           0
  81 #define STD_NAME                2
  82 
  83 /*
  84  * Calls RegQueryValueEx() to get the value for the specified key. If
  85  * the platform is NT, 2000 or XP, it calls the Unicode
  86  * version. Otherwise, it calls the ANSI version and converts the
  87  * value to Unicode. In this case, it assumes that the current ANSI
  88  * Code Page is the same as the native platform code page (e.g., Code
  89  * Page 932 for the Japanese Windows systems.
  90  *
  91  * `keyIndex' is an index value to the keyNames in Unicode
  92  * (WCHAR). `keyIndex' + 1 points to its ANSI value.
  93  *
  94  * Returns the status value. ERROR_SUCCESS if succeeded, a
  95  * non-ERROR_SUCCESS value otherwise.
  96  */
  97 static LONG
  98 getValueInRegistry(HKEY hKey,
  99                    int keyIndex,
 100                    LPDWORD typePtr,
 101                    LPBYTE buf,
 102                    LPDWORD bufLengthPtr)
 103 {
 104     LONG ret;
 105     DWORD bufLength = *bufLengthPtr;
 106     char val[MAX_ZONE_CHAR];
 107     DWORD valSize;
 108     int len;
 109 
 110     *typePtr = 0;
 111     ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL,
 112                            typePtr, buf, bufLengthPtr);
 113     if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) {
 114         return ret;
 115     }
 116 
 117     valSize = sizeof(val);
 118     ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL,
 119                            typePtr, val, &valSize);
 120     if (ret != ERROR_SUCCESS) {
 121         return ret;
 122     }
 123     if (*typePtr != REG_SZ) {
 124         return ERROR_BADKEY;
 125     }
 126 
 127     len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
 128                               (LPCSTR) val, -1,
 129                               (LPWSTR) buf, bufLength/sizeof(WCHAR));
 130     if (len <= 0) {
 131         return ERROR_BADKEY;
 132     }
 133     return ERROR_SUCCESS;
 134 }
 135 
 136 /*
 137  * Produces custom name "GMT+hh:mm" from the given bias in buffer.
 138  */
 139 static void customZoneName(LONG bias, char *buffer) {
 140     LONG gmtOffset;
 141     int sign;
 142 
 143     if (bias > 0) {
 144         gmtOffset = bias;
 145         sign = -1;
 146     } else {
 147         gmtOffset = -bias;
 148         sign = 1;
 149     }
 150     if (gmtOffset != 0) {
 151         sprintf(buffer, "GMT%c%02d:%02d",
 152                 ((sign >= 0) ? '+' : '-'),
 153                 gmtOffset / 60,
 154                 gmtOffset % 60);
 155     } else {
 156         strcpy(buffer, "GMT");
 157     }
 158 }
 159 
 160 /*
 161  * Use NO_DYNAMIC_TIME_ZONE_INFO as the return value indicating that no
 162  * dynamic time zone information is available.
 163  */
 164 #define NO_DYNAMIC_TIME_ZONE_INFO     (-128)
 165 
 166 static int getDynamicTimeZoneInfo(PDYNAMIC_TIME_ZONE_INFORMATION pdtzi) {
 167     DWORD timeType = NO_DYNAMIC_TIME_ZONE_INFO;
 168     HMODULE dllHandle;
 169 
 170     /*
 171      * Dynamically load the dll to call GetDynamicTimeZoneInformation.
 172      */
 173     dllHandle = JDK_LoadSystemLibrary("Kernel32.dll");
 174     if (dllHandle != NULL) {
 175         typedef DWORD (WINAPI *GetDynamicTimezoneInfoType)(PDYNAMIC_TIME_ZONE_INFORMATION);
 176         GetDynamicTimezoneInfoType getDynamicTimeZoneInfo =
 177             (GetDynamicTimezoneInfoType) GetProcAddress(dllHandle,
 178                                                         "GetDynamicTimeZoneInformation");
 179 
 180         if (getDynamicTimeZoneInfo != NULL) {
 181             timeType = getDynamicTimeZoneInfo(pdtzi);
 182         }
 183     }
 184     return timeType;
 185 }
 186 
 187 /*
 188  * Gets the current time zone entry in the "Time Zones" registry.
 189  */
 190 static int getWinTimeZone(char *winZoneName, char *winMapID)
 191 {
 192     TIME_ZONE_INFORMATION tzi;
 193     OSVERSIONINFO ver;
 194     int onlyMapID;
 195     HANDLE hKey = NULL, hSubKey = NULL;
 196     LONG ret;
 197     DWORD nSubKeys, i;
 198     ULONG valueType;
 199     TCHAR subKeyName[MAX_ZONE_CHAR];
 200     TCHAR szValue[MAX_ZONE_CHAR];
 201     WCHAR stdNameInReg[MAX_ZONE_CHAR];
 202     TziValue tempTzi;
 203     WCHAR *stdNamePtr = tzi.StandardName;
 204     DWORD valueSize;
 205     DWORD timeType;
 206     int isVistaOrLater;
 207 
 208     /*
 209      * Determine if this is a Vista or later.
 210      */
 211     ver.dwOSVersionInfoSize = sizeof(ver);
 212     GetVersionEx(&ver);
 213     isVistaOrLater = (ver.dwMajorVersion >= 6);
 214 
 215     if (isVistaOrLater) {
 216         DYNAMIC_TIME_ZONE_INFORMATION dtzi;
 217         DWORD bufSize;
 218         DWORD val;
 219 
 220         /*
 221          * Get the dynamic time zone information, if available, so that time
 222          * zone redirection can be supported. (see JDK-7044727)
 223          */
 224         timeType = getDynamicTimeZoneInfo(&dtzi);
 225         if (timeType == TIME_ZONE_ID_INVALID) {
 226             goto err;
 227         }
 228 
 229         if (timeType != NO_DYNAMIC_TIME_ZONE_INFO) {
 230             /*
 231              * Make sure TimeZoneKeyName is available from the API call. If
 232              * DynamicDaylightTime is disabled, return a custom time zone name
 233              * based on the GMT offset. Otherwise, return the TimeZoneKeyName
 234              * value.
 235              */
 236             if (dtzi.TimeZoneKeyName[0] != 0) {
 237                 if (dtzi.DynamicDaylightTimeDisabled) {
 238                     customZoneName(dtzi.Bias, winZoneName);
 239                     return VALUE_GMTOFFSET;
 240                 }
 241                 wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR);
 242                 return VALUE_KEY;
 243             }
 244 
 245             /*
 246              * If TimeZoneKeyName is not available, check whether StandardName
 247              * is available to fall back to the older API GetTimeZoneInformation.
 248              * If not, directly read the value from registry keys.
 249              */
 250             if (dtzi.StandardName[0] == 0) {
 251                 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
 252                                    KEY_READ, (PHKEY)&hKey);
 253                 if (ret != ERROR_SUCCESS) {
 254                     goto err;
 255                 }
 256 
 257                 /*
 258                  * Determine if auto-daylight time adjustment is turned off.
 259                  */
 260                 bufSize = sizeof(val);
 261                 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL,
 262                                        &valueType, (LPBYTE) &val, &bufSize);
 263                 if (ret != ERROR_SUCCESS) {
 264                     goto err;
 265                 }
 266                 /*
 267                  * Return a custom time zone name if auto-daylight time
 268                  * adjustment is disabled.
 269                  */
 270                 if (val == 1) {
 271                     customZoneName(dtzi.Bias, winZoneName);
 272                     (void) RegCloseKey(hKey);
 273                     return VALUE_GMTOFFSET;
 274                 }
 275 
 276                 bufSize = MAX_ZONE_CHAR;
 277                 ret = RegQueryValueExA(hKey, "TimeZoneKeyName",NULL,
 278                                        &valueType, (LPBYTE)winZoneName, &bufSize);
 279                 if (ret != ERROR_SUCCESS) {
 280                     goto err;
 281                 }
 282                 (void) RegCloseKey(hKey);
 283                 return VALUE_KEY;
 284             }
 285         }
 286     }
 287 
 288     /*
 289      * Fall back to GetTimeZoneInformation
 290      */
 291     timeType = GetTimeZoneInformation(&tzi);
 292     if (timeType == TIME_ZONE_ID_INVALID) {
 293         goto err;
 294     }
 295 
 296     ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
 297                        KEY_READ, (PHKEY)&hKey);
 298     if (ret == ERROR_SUCCESS) {
 299         DWORD val;
 300         DWORD bufSize;
 301 
 302         /*
 303          * Determine if auto-daylight time adjustment is turned off.
 304          */
 305         bufSize = sizeof(val);
 306         ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL,
 307                                &valueType, (LPBYTE) &val, &bufSize);
 308         if (ret != ERROR_SUCCESS) {
 309             /*
 310              * Try the old key name.
 311              */
 312             bufSize = sizeof(val);
 313             ret = RegQueryValueExA(hKey, "DisableAutoDaylightTimeSet", NULL,
 314                                    &valueType, (LPBYTE) &val, &bufSize);
 315         }
 316 
 317         if (ret == ERROR_SUCCESS) {
 318             int daylightSavingsUpdateDisabledOther = (val == 1 && tzi.DaylightDate.wMonth != 0);
 319             int daylightSavingsUpdateDisabledVista = (val == 1);
 320             int daylightSavingsUpdateDisabled
 321                 = (isVistaOrLater ? daylightSavingsUpdateDisabledVista : daylightSavingsUpdateDisabledOther);
 322 
 323             if (daylightSavingsUpdateDisabled) {
 324                 (void) RegCloseKey(hKey);
 325                 customZoneName(tzi.Bias, winZoneName);
 326                 return VALUE_GMTOFFSET;
 327             }
 328         }
 329 
 330         /*
 331          * Win32 problem: If the length of the standard time name is equal
 332          * to (or probably longer than) 32 in the registry,
 333          * GetTimeZoneInformation() on NT returns a null string as its
 334          * standard time name. We need to work around this problem by
 335          * getting the same information from the TimeZoneInformation
 336          * registry.
 337          */
 338         if (tzi.StandardName[0] == 0) {
 339             bufSize = sizeof(stdNameInReg);
 340             ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType,
 341                                      (LPBYTE) stdNameInReg, &bufSize);
 342             if (ret != ERROR_SUCCESS) {
 343                 goto err;
 344             }
 345             stdNamePtr = stdNameInReg;
 346         }
 347         (void) RegCloseKey(hKey);
 348     }
 349 
 350     /*
 351      * Open the "Time Zones" registry.
 352      */
 353     ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey);
 354     if (ret != ERROR_SUCCESS) {
 355         ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey);
 356         /*
 357          * If both failed, then give up.
 358          */
 359         if (ret != ERROR_SUCCESS) {
 360             return VALUE_UNKNOWN;
 361         }
 362     }
 363 
 364     /*
 365      * Get the number of subkeys of the "Time Zones" registry for
 366      * enumeration.
 367      */
 368     ret = RegQueryInfoKey(hKey, NULL, NULL, NULL, &nSubKeys,
 369                           NULL, NULL, NULL, NULL, NULL, NULL, NULL);
 370     if (ret != ERROR_SUCCESS) {
 371         goto err;
 372     }
 373 
 374     /*
 375      * Compare to the "Std" value of each subkey and find the entry that
 376      * matches the current control panel setting.
 377      */
 378     onlyMapID = 0;
 379     for (i = 0; i < nSubKeys; ++i) {
 380         DWORD size = sizeof(subKeyName);
 381         ret = RegEnumKeyEx(hKey, i, subKeyName, &size, NULL, NULL, NULL, NULL);
 382         if (ret != ERROR_SUCCESS) {
 383             goto err;
 384         }
 385         ret = RegOpenKeyEx(hKey, subKeyName, 0, KEY_READ, (PHKEY)&hSubKey);
 386         if (ret != ERROR_SUCCESS) {
 387             goto err;
 388         }
 389 
 390         size = sizeof(szValue);
 391         ret = getValueInRegistry(hSubKey, STD_NAME, &valueType,
 392                                  szValue, &size);
 393         if (ret != ERROR_SUCCESS) {
 394             /*
 395              * NT 4.0 SP3 fails here since it doesn't have the "Std"
 396              * entry in the Time Zones registry.
 397              */
 398             RegCloseKey(hSubKey);
 399             onlyMapID = 1;
 400             ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, (PHKEY)&hSubKey);
 401             if (ret != ERROR_SUCCESS) {
 402                 goto err;
 403             }
 404             break;
 405         }
 406 
 407         if (wcscmp((WCHAR *)szValue, stdNamePtr) == 0) {
 408             /*
 409              * Some localized Win32 platforms use a same name to
 410              * different time zones. So, we can't rely only on the name
 411              * here. We need to check GMT offsets and transition dates
 412              * to make sure it's the registry of the current time
 413              * zone.
 414              */
 415             DWORD tziValueSize = sizeof(tempTzi);
 416             ret = RegQueryValueEx(hSubKey, "TZI", NULL, &valueType,
 417                                   (unsigned char *) &tempTzi, &tziValueSize);
 418             if (ret == ERROR_SUCCESS) {
 419                 if ((tzi.Bias != tempTzi.bias) ||
 420                     (memcmp((const void *) &tzi.StandardDate,
 421                             (const void *) &tempTzi.stdDate,
 422                             sizeof(SYSTEMTIME)) != 0)) {
 423                         goto out;
 424                 }
 425 
 426                 if (tzi.DaylightBias != 0) {
 427                     if ((tzi.DaylightBias != tempTzi.dstBias) ||
 428                         (memcmp((const void *) &tzi.DaylightDate,
 429                                 (const void *) &tempTzi.dstDate,
 430                                 sizeof(SYSTEMTIME)) != 0)) {
 431                         goto out;
 432                     }
 433                 }
 434             }
 435 
 436             /*
 437              * found matched record, terminate search
 438              */
 439             strcpy(winZoneName, subKeyName);
 440             break;
 441         }
 442     out:
 443         (void) RegCloseKey(hSubKey);
 444     }
 445 
 446     /*
 447      * Get the "MapID" value of the registry to be able to eliminate
 448      * duplicated key names later.
 449      */
 450     valueSize = MAX_MAPID_LENGTH;
 451     ret = RegQueryValueExA(hSubKey, "MapID", NULL, &valueType, winMapID, &valueSize);
 452     (void) RegCloseKey(hSubKey);
 453     (void) RegCloseKey(hKey);
 454 
 455     if (ret != ERROR_SUCCESS) {
 456         /*
 457          * Vista doesn't have mapID. VALUE_UNKNOWN should be returned
 458          * only for Windows NT.
 459          */
 460         if (onlyMapID == 1) {
 461             return VALUE_UNKNOWN;
 462         }
 463     }
 464 
 465     return VALUE_KEY;
 466 
 467  err:
 468     if (hKey != NULL) {
 469         (void) RegCloseKey(hKey);
 470     }
 471     return VALUE_UNKNOWN;
 472 }
 473 
 474 /*
 475  * The mapping table file name.
 476  */
 477 #define MAPPINGS_FILE "\\lib\\tzmappings"
 478 
 479 /*
 480  * Index values for the mapping table.
 481  */
 482 #define TZ_WIN_NAME     0
 483 #define TZ_MAPID        1
 484 #define TZ_REGION       2
 485 #define TZ_JAVA_NAME    3
 486 
 487 #define TZ_NITEMS       4       /* number of items (fields) */
 488 
 489 /*
 490  * Looks up the mapping table (tzmappings) and returns a Java time
 491  * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is
 492  * returned.
 493  *
 494  * value_type is one of the following values:
 495  *      VALUE_KEY for exact key matching
 496  *      VALUE_MAPID for MapID (this is
 497  *      required for the old Windows, such as NT 4.0 SP3).
 498  */
 499 static char *matchJavaTZ(const char *java_home_dir, int value_type, char *tzName,
 500                          char *mapID)
 501 {
 502     int line;
 503     int IDmatched = 0;
 504     FILE *fp;
 505     char *javaTZName = NULL;
 506     char *items[TZ_NITEMS];
 507     char *mapFileName;
 508     char lineBuffer[MAX_ZONE_CHAR * 4];
 509     int noMapID = *mapID == '\0';       /* no mapID on Vista and later */
 510 
 511     mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1);
 512     if (mapFileName == NULL) {
 513         return NULL;
 514     }
 515     strcpy(mapFileName, java_home_dir);
 516     strcat(mapFileName, MAPPINGS_FILE);
 517 
 518     if ((fp = fopen(mapFileName, "r")) == NULL) {
 519         jio_fprintf(stderr, "can't open %s.\n", mapFileName);
 520         free((void *) mapFileName);
 521         return NULL;
 522     }
 523     free((void *) mapFileName);
 524 
 525     line = 0;
 526     while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) {
 527         char *start, *idx, *endp;
 528         int itemIndex = 0;
 529 
 530         line++;
 531         start = idx = lineBuffer;
 532         endp = &lineBuffer[sizeof(lineBuffer)];
 533 
 534         /*
 535          * Ignore comment and blank lines.
 536          */
 537         if (*idx == '#' || *idx == '\n') {
 538             continue;
 539         }
 540 
 541         for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) {
 542             items[itemIndex] = start;
 543             while (*idx && *idx != ':') {
 544                 if (++idx >= endp) {
 545                     goto illegal_format;
 546                 }
 547             }
 548             if (*idx == '\0') {
 549                 goto illegal_format;
 550             }
 551             *idx++ = '\0';
 552             start = idx;
 553         }
 554 
 555         if (*idx != '\n') {
 556             goto illegal_format;
 557         }
 558 
 559         if (noMapID || strcmp(mapID, items[TZ_MAPID]) == 0) {
 560             /*
 561              * When there's no mapID, we need to scan items until the
 562              * exact match is found or the end of data is detected.
 563              */
 564             if (!noMapID) {
 565                 IDmatched = 1;
 566             }
 567             if (strcmp(items[TZ_WIN_NAME], tzName) == 0) {
 568                 /*
 569                  * Found the time zone in the mapping table.
 570                  */
 571                 javaTZName = _strdup(items[TZ_JAVA_NAME]);
 572                 break;
 573             }
 574         } else {
 575             if (IDmatched == 1) {
 576                 /*
 577                  * No need to look up the mapping table further.
 578                  */
 579                 break;
 580             }
 581         }
 582     }
 583     fclose(fp);
 584 
 585     return javaTZName;
 586 
 587  illegal_format:
 588     (void) fclose(fp);
 589     jio_fprintf(stderr, "tzmappings: Illegal format at line %d.\n", line);
 590     return NULL;
 591 }
 592 
 593 /*
 594  * Detects the platform time zone which maps to a Java time zone ID.
 595  */
 596 char *findJavaTZ_md(const char *java_home_dir)
 597 {
 598     char winZoneName[MAX_ZONE_CHAR];
 599     char winMapID[MAX_MAPID_LENGTH];
 600     char *std_timezone = NULL;
 601     int  result;
 602 
 603     winMapID[0] = 0;
 604     result = getWinTimeZone(winZoneName, winMapID);
 605 
 606     if (result != VALUE_UNKNOWN) {
 607         if (result == VALUE_GMTOFFSET) {
 608             std_timezone = _strdup(winZoneName);
 609         } else {
 610             std_timezone = matchJavaTZ(java_home_dir, result,
 611                                        winZoneName, winMapID);
 612             if (std_timezone == NULL) {
 613                 std_timezone = getGMTOffsetID();
 614             }
 615         }
 616     }
 617     return std_timezone;
 618 }
 619 
 620 /**
 621  * Returns a GMT-offset-based time zone ID.
 622  */
 623 char *
 624 getGMTOffsetID()
 625 {
 626     LONG bias = 0;
 627     LONG ret;
 628     HANDLE hKey = NULL;
 629     char zonename[32];
 630 
 631     // Obtain the current GMT offset value of ActiveTimeBias.
 632     ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
 633                        KEY_READ, (PHKEY)&hKey);
 634     if (ret == ERROR_SUCCESS) {
 635         DWORD val;
 636         DWORD bufSize = sizeof(val);
 637         ULONG valueType = 0;
 638         ret = RegQueryValueExA(hKey, "ActiveTimeBias",
 639                                NULL, &valueType, (LPBYTE) &val, &bufSize);
 640         if (ret == ERROR_SUCCESS) {
 641             bias = (LONG) val;
 642         }
 643         (void) RegCloseKey(hKey);
 644     }
 645 
 646     // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation.
 647     // Note: Bias doesn't reflect current daylight saving.
 648     if (ret != ERROR_SUCCESS) {
 649         TIME_ZONE_INFORMATION tzi;
 650         if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) {
 651             bias = tzi.Bias;
 652         }
 653     }
 654 
 655     customZoneName(bias, zonename);
 656     return _strdup(zonename);
 657 }