Path: blob/master/src/java.base/windows/native/libjava/TimeZone_md.c
41119 views
/*1* Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425#include <windows.h>26#include <stdio.h>27#include <stdlib.h>28#include "jvm.h"29#include "TimeZone_md.h"3031#define VALUE_UNKNOWN 032#define VALUE_KEY 133#define VALUE_MAPID 234#define VALUE_GMTOFFSET 33536#define MAX_ZONE_CHAR 25637#define MAX_MAPID_LENGTH 3238#define MAX_REGION_LENGTH 43940#define NT_TZ_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"41#define WIN_TZ_KEY "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones"42#define WIN_CURRENT_TZ_KEY "System\\CurrentControlSet\\Control\\TimeZoneInformation"4344typedef struct _TziValue {45LONG bias;46LONG stdBias;47LONG dstBias;48SYSTEMTIME stdDate;49SYSTEMTIME dstDate;50} TziValue;5152/*53* Registry key names54*/55static void *keyNames[] = {56(void *) L"StandardName",57(void *) "StandardName",58(void *) L"Std",59(void *) "Std"60};6162/*63* Indices to keyNames[]64*/65#define STANDARD_NAME 066#define STD_NAME 26768/*69* Calls RegQueryValueEx() to get the value for the specified key. If70* the platform is NT, 2000 or XP, it calls the Unicode71* version. Otherwise, it calls the ANSI version and converts the72* value to Unicode. In this case, it assumes that the current ANSI73* Code Page is the same as the native platform code page (e.g., Code74* Page 932 for the Japanese Windows systems.75*76* `keyIndex' is an index value to the keyNames in Unicode77* (WCHAR). `keyIndex' + 1 points to its ANSI value.78*79* Returns the status value. ERROR_SUCCESS if succeeded, a80* non-ERROR_SUCCESS value otherwise.81*/82static LONG83getValueInRegistry(HKEY hKey,84int keyIndex,85LPDWORD typePtr,86LPBYTE buf,87LPDWORD bufLengthPtr)88{89LONG ret;90DWORD bufLength = *bufLengthPtr;91char val[MAX_ZONE_CHAR];92DWORD valSize;93int len;9495*typePtr = 0;96ret = RegQueryValueExW(hKey, (WCHAR *) keyNames[keyIndex], NULL,97typePtr, buf, bufLengthPtr);98if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) {99return ret;100}101102valSize = sizeof(val);103ret = RegQueryValueExA(hKey, (char *) keyNames[keyIndex + 1], NULL,104typePtr, val, &valSize);105if (ret != ERROR_SUCCESS) {106return ret;107}108if (*typePtr != REG_SZ) {109return ERROR_BADKEY;110}111112len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,113(LPCSTR) val, -1,114(LPWSTR) buf, bufLength/sizeof(WCHAR));115if (len <= 0) {116return ERROR_BADKEY;117}118return ERROR_SUCCESS;119}120121/*122* Produces custom name "GMT+hh:mm" from the given bias in buffer.123*/124static void customZoneName(LONG bias, char *buffer) {125LONG gmtOffset;126int sign;127128if (bias > 0) {129gmtOffset = bias;130sign = -1;131} else {132gmtOffset = -bias;133sign = 1;134}135if (gmtOffset != 0) {136sprintf(buffer, "GMT%c%02d:%02d",137((sign >= 0) ? '+' : '-'),138gmtOffset / 60,139gmtOffset % 60);140} else {141strcpy(buffer, "GMT");142}143}144145/*146* Gets the current time zone entry in the "Time Zones" registry.147*/148static int getWinTimeZone(char *winZoneName)149{150DYNAMIC_TIME_ZONE_INFORMATION dtzi;151DWORD timeType;152DWORD bufSize;153DWORD val;154HANDLE hKey = NULL;155LONG ret;156ULONG valueType;157158/*159* Get the dynamic time zone information so that time zone redirection160* can be supported. (see JDK-7044727)161*/162timeType = GetDynamicTimeZoneInformation(&dtzi);163if (timeType == TIME_ZONE_ID_INVALID) {164goto err;165}166167/*168* Make sure TimeZoneKeyName is available from the API call. If169* DynamicDaylightTime is disabled, return a custom time zone name170* based on the GMT offset. Otherwise, return the TimeZoneKeyName171* value.172*/173if (dtzi.TimeZoneKeyName[0] != 0) {174if (dtzi.DynamicDaylightTimeDisabled) {175customZoneName(dtzi.Bias, winZoneName);176return VALUE_GMTOFFSET;177}178wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR);179return VALUE_KEY;180}181182/*183* If TimeZoneKeyName is not available, check whether StandardName184* is available to fall back to the older API GetTimeZoneInformation.185* If not, directly read the value from registry keys.186*/187if (dtzi.StandardName[0] == 0) {188ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,189KEY_READ, (PHKEY)&hKey);190if (ret != ERROR_SUCCESS) {191goto err;192}193194/*195* Determine if auto-daylight time adjustment is turned off.196*/197bufSize = sizeof(val);198ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL,199&valueType, (LPBYTE) &val, &bufSize);200if (ret != ERROR_SUCCESS) {201goto err;202}203/*204* Return a custom time zone name if auto-daylight time adjustment205* is disabled.206*/207if (val == 1) {208customZoneName(dtzi.Bias, winZoneName);209(void) RegCloseKey(hKey);210return VALUE_GMTOFFSET;211}212213bufSize = MAX_ZONE_CHAR;214ret = RegQueryValueExA(hKey, "TimeZoneKeyName", NULL,215&valueType, (LPBYTE) winZoneName, &bufSize);216if (ret != ERROR_SUCCESS) {217goto err;218}219(void) RegCloseKey(hKey);220return VALUE_KEY;221} else {222/*223* Fall back to GetTimeZoneInformation224*/225TIME_ZONE_INFORMATION tzi;226HANDLE hSubKey = NULL;227DWORD nSubKeys, i;228ULONG valueType;229TCHAR subKeyName[MAX_ZONE_CHAR];230TCHAR szValue[MAX_ZONE_CHAR];231WCHAR stdNameInReg[MAX_ZONE_CHAR];232TziValue tempTzi;233WCHAR *stdNamePtr = tzi.StandardName;234int onlyMapID;235236timeType = GetTimeZoneInformation(&tzi);237if (timeType == TIME_ZONE_ID_INVALID) {238goto err;239}240241ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,242KEY_READ, (PHKEY)&hKey);243if (ret == ERROR_SUCCESS) {244/*245* Determine if auto-daylight time adjustment is turned off.246*/247bufSize = sizeof(val);248ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", NULL,249&valueType, (LPBYTE) &val, &bufSize);250if (ret == ERROR_SUCCESS) {251if (val == 1 && tzi.DaylightDate.wMonth != 0) {252(void) RegCloseKey(hKey);253customZoneName(tzi.Bias, winZoneName);254return VALUE_GMTOFFSET;255}256}257258/*259* Win32 problem: If the length of the standard time name is equal260* to (or probably longer than) 32 in the registry,261* GetTimeZoneInformation() on NT returns a null string as its262* standard time name. We need to work around this problem by263* getting the same information from the TimeZoneInformation264* registry.265*/266if (tzi.StandardName[0] == 0) {267bufSize = sizeof(stdNameInReg);268ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType,269(LPBYTE) stdNameInReg, &bufSize);270if (ret != ERROR_SUCCESS) {271goto err;272}273stdNamePtr = stdNameInReg;274}275(void) RegCloseKey(hKey);276}277278/*279* Open the "Time Zones" registry.280*/281ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey);282if (ret != ERROR_SUCCESS) {283ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, (PHKEY)&hKey);284/*285* If both failed, then give up.286*/287if (ret != ERROR_SUCCESS) {288return VALUE_UNKNOWN;289}290}291292/*293* Get the number of subkeys of the "Time Zones" registry for294* enumeration.295*/296ret = RegQueryInfoKey(hKey, NULL, NULL, NULL, &nSubKeys,297NULL, NULL, NULL, NULL, NULL, NULL, NULL);298if (ret != ERROR_SUCCESS) {299goto err;300}301302/*303* Compare to the "Std" value of each subkey and find the entry that304* matches the current control panel setting.305*/306onlyMapID = 0;307for (i = 0; i < nSubKeys; ++i) {308DWORD size = sizeof(subKeyName);309ret = RegEnumKeyEx(hKey, i, subKeyName, &size, NULL, NULL, NULL, NULL);310if (ret != ERROR_SUCCESS) {311goto err;312}313ret = RegOpenKeyEx(hKey, subKeyName, 0, KEY_READ, (PHKEY)&hSubKey);314if (ret != ERROR_SUCCESS) {315goto err;316}317318size = sizeof(szValue);319ret = getValueInRegistry(hSubKey, STD_NAME, &valueType,320szValue, &size);321if (ret != ERROR_SUCCESS) {322/*323* NT 4.0 SP3 fails here since it doesn't have the "Std"324* entry in the Time Zones registry.325*/326RegCloseKey(hSubKey);327onlyMapID = 1;328ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, (PHKEY)&hSubKey);329if (ret != ERROR_SUCCESS) {330goto err;331}332break;333}334335if (wcscmp((WCHAR *)szValue, stdNamePtr) == 0) {336/*337* Some localized Win32 platforms use a same name to338* different time zones. So, we can't rely only on the name339* here. We need to check GMT offsets and transition dates340* to make sure it's the registry of the current time341* zone.342*/343DWORD tziValueSize = sizeof(tempTzi);344ret = RegQueryValueEx(hSubKey, "TZI", NULL, &valueType,345(unsigned char *) &tempTzi, &tziValueSize);346if (ret == ERROR_SUCCESS) {347if ((tzi.Bias != tempTzi.bias) ||348(memcmp((const void *) &tzi.StandardDate,349(const void *) &tempTzi.stdDate,350sizeof(SYSTEMTIME)) != 0)) {351goto out;352}353354if (tzi.DaylightBias != 0) {355if ((tzi.DaylightBias != tempTzi.dstBias) ||356(memcmp((const void *) &tzi.DaylightDate,357(const void *) &tempTzi.dstDate,358sizeof(SYSTEMTIME)) != 0)) {359goto out;360}361}362}363364/*365* found matched record, terminate search366*/367strcpy(winZoneName, subKeyName);368break;369}370out:371(void) RegCloseKey(hSubKey);372}373374(void) RegCloseKey(hKey);375}376377return VALUE_KEY;378379err:380if (hKey != NULL) {381(void) RegCloseKey(hKey);382}383return VALUE_UNKNOWN;384}385386/*387* The mapping table file name.388*/389#define MAPPINGS_FILE "\\lib\\tzmappings"390391/*392* Index values for the mapping table.393*/394#define TZ_WIN_NAME 0395#define TZ_REGION 1396#define TZ_JAVA_NAME 2397398#define TZ_NITEMS 3 /* number of items (fields) */399400/*401* Looks up the mapping table (tzmappings) and returns a Java time402* zone ID (e.g., "America/Los_Angeles") if found. Otherwise, NULL is403* returned.404*/405static char *matchJavaTZ(const char *java_home_dir, char *tzName)406{407int line;408int IDmatched = 0;409FILE *fp;410char *javaTZName = NULL;411char *items[TZ_NITEMS];412char *mapFileName;413char lineBuffer[MAX_ZONE_CHAR * 4];414int offset = 0;415const char* errorMessage = "unknown error";416char region[MAX_REGION_LENGTH];417418// Get the user's location419if (GetGeoInfo(GetUserGeoID(GEOCLASS_NATION),420GEO_ISO2, region, MAX_REGION_LENGTH, 0) == 0) {421// If GetGeoInfo fails, fallback to LCID's country422LCID lcid = GetUserDefaultLCID();423if (GetLocaleInfo(lcid,424LOCALE_SISO3166CTRYNAME, region, MAX_REGION_LENGTH) == 0 &&425GetLocaleInfo(lcid,426LOCALE_SISO3166CTRYNAME2, region, MAX_REGION_LENGTH) == 0) {427region[0] = '\0';428}429}430431mapFileName = malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1);432if (mapFileName == NULL) {433return NULL;434}435strcpy(mapFileName, java_home_dir);436strcat(mapFileName, MAPPINGS_FILE);437438if ((fp = fopen(mapFileName, "r")) == NULL) {439jio_fprintf(stderr, "can't open %s.\n", mapFileName);440free((void *) mapFileName);441return NULL;442}443free((void *) mapFileName);444445line = 0;446while (fgets(lineBuffer, sizeof(lineBuffer), fp) != NULL) {447char *start, *idx, *endp;448int itemIndex = 0;449450line++;451start = idx = lineBuffer;452endp = &lineBuffer[sizeof(lineBuffer)];453454/*455* Ignore comment and blank lines.456*/457if (*idx == '#' || *idx == '\n') {458continue;459}460461for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) {462items[itemIndex] = start;463while (*idx && *idx != ':') {464if (++idx >= endp) {465errorMessage = "premature end of line";466offset = (int)(idx - lineBuffer);467goto illegal_format;468}469}470if (*idx == '\0') {471errorMessage = "illegal null character found";472offset = (int)(idx - lineBuffer);473goto illegal_format;474}475*idx++ = '\0';476start = idx;477}478479if (*idx != '\n') {480errorMessage = "illegal non-newline character found";481offset = (int)(idx - lineBuffer);482goto illegal_format;483}484485/*486* We need to scan items until the487* exact match is found or the end of data is detected.488*/489if (strcmp(items[TZ_WIN_NAME], tzName) == 0) {490/*491* Found the time zone in the mapping table.492* Check the region code and select the appropriate entry493*/494if (strcmp(items[TZ_REGION], region) == 0 ||495strcmp(items[TZ_REGION], "001") == 0) {496javaTZName = _strdup(items[TZ_JAVA_NAME]);497break;498}499}500}501fclose(fp);502503return javaTZName;504505illegal_format:506(void) fclose(fp);507jio_fprintf(stderr, "Illegal format in tzmappings file: %s at line %d, offset %d.\n",508errorMessage, line, offset);509return NULL;510}511512/*513* Detects the platform time zone which maps to a Java time zone ID.514*/515char *findJavaTZ_md(const char *java_home_dir)516{517char winZoneName[MAX_ZONE_CHAR];518char *std_timezone = NULL;519int result;520521result = getWinTimeZone(winZoneName);522523if (result != VALUE_UNKNOWN) {524if (result == VALUE_GMTOFFSET) {525std_timezone = _strdup(winZoneName);526} else {527std_timezone = matchJavaTZ(java_home_dir, winZoneName);528if (std_timezone == NULL) {529std_timezone = getGMTOffsetID();530}531}532}533return std_timezone;534}535536/**537* Returns a GMT-offset-based time zone ID.538*/539char *540getGMTOffsetID()541{542LONG bias = 0;543LONG ret;544HANDLE hKey = NULL;545char zonename[32];546547// Obtain the current GMT offset value of ActiveTimeBias.548ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,549KEY_READ, (PHKEY)&hKey);550if (ret == ERROR_SUCCESS) {551DWORD val;552DWORD bufSize = sizeof(val);553ULONG valueType = 0;554ret = RegQueryValueExA(hKey, "ActiveTimeBias",555NULL, &valueType, (LPBYTE) &val, &bufSize);556if (ret == ERROR_SUCCESS) {557bias = (LONG) val;558}559(void) RegCloseKey(hKey);560}561562// If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation.563// Note: Bias doesn't reflect current daylight saving.564if (ret != ERROR_SUCCESS) {565TIME_ZONE_INFORMATION tzi;566if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) {567bias = tzi.Bias;568}569}570571customZoneName(bias, zonename);572return _strdup(zonename);573}574575576