147 lines
5.9 KiB
C++
147 lines
5.9 KiB
C++
// © 2016 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
/*
|
|
********************************************************************************
|
|
* Copyright (C) 2005-2015, International Business Machines
|
|
* Corporation and others. All Rights Reserved.
|
|
********************************************************************************
|
|
*
|
|
* File WINTZ.CPP
|
|
*
|
|
********************************************************************************
|
|
*/
|
|
|
|
#include "unicode/utypes.h"
|
|
|
|
#if U_PLATFORM_USES_ONLY_WIN32_API
|
|
|
|
#include "wintz.h"
|
|
#include "charstr.h"
|
|
#include "cmemory.h"
|
|
#include "cstring.h"
|
|
|
|
#include "unicode/ures.h"
|
|
#include "unicode/unistr.h"
|
|
#include "uresimp.h"
|
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
# define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
# define VC_EXTRALEAN
|
|
# define NOUSER
|
|
# define NOSERVICE
|
|
# define NOIME
|
|
# define NOMCX
|
|
#include <windows.h>
|
|
|
|
U_NAMESPACE_BEGIN
|
|
|
|
/**
|
|
* Main Windows time zone detection function.
|
|
* Returns the Windows time zone converted to an ICU time zone as a heap-allocated buffer, or nullptr upon failure.
|
|
*
|
|
* Note: We use the Win32 API GetDynamicTimeZoneInformation (available since Vista+) to get the current time zone info.
|
|
* This API returns a non-localized time zone name, which is mapped to an ICU time zone ID (~ Olsen ID).
|
|
*/
|
|
U_CAPI const char* U_EXPORT2
|
|
uprv_detectWindowsTimeZone()
|
|
{
|
|
// Obtain the DYNAMIC_TIME_ZONE_INFORMATION info to get the non-localized time zone name.
|
|
DYNAMIC_TIME_ZONE_INFORMATION dynamicTZI;
|
|
uprv_memset(&dynamicTZI, 0, sizeof(dynamicTZI));
|
|
SYSTEMTIME systemTimeAllZero;
|
|
uprv_memset(&systemTimeAllZero, 0, sizeof(systemTimeAllZero));
|
|
|
|
if (GetDynamicTimeZoneInformation(&dynamicTZI) == TIME_ZONE_ID_INVALID) {
|
|
return nullptr;
|
|
}
|
|
|
|
// If the DST setting has been turned off in the Control Panel, then return "Etc/GMT<offset>".
|
|
//
|
|
// Note: This logic is based on how the Control Panel itself determines if DST is 'off' on Windows.
|
|
// The code is somewhat convoluted; in a sort of pseudo-code it looks like this:
|
|
//
|
|
// IF (GetDynamicTimeZoneInformation != TIME_ZONE_ID_INVALID) && (DynamicDaylightTimeDisabled != 0) &&
|
|
// (StandardDate == DaylightDate) &&
|
|
// (
|
|
// (TimeZoneKeyName != Empty && StandardDate == 0) ||
|
|
// (TimeZoneKeyName == Empty && StandardDate != 0)
|
|
// )
|
|
// THEN
|
|
// DST setting is "Disabled".
|
|
//
|
|
if (dynamicTZI.DynamicDaylightTimeDisabled != 0 &&
|
|
uprv_memcmp(&dynamicTZI.StandardDate, &dynamicTZI.DaylightDate, sizeof(dynamicTZI.StandardDate)) == 0 &&
|
|
((dynamicTZI.TimeZoneKeyName[0] != L'\0' && uprv_memcmp(&dynamicTZI.StandardDate, &systemTimeAllZero, sizeof(systemTimeAllZero)) == 0) ||
|
|
(dynamicTZI.TimeZoneKeyName[0] == L'\0' && uprv_memcmp(&dynamicTZI.StandardDate, &systemTimeAllZero, sizeof(systemTimeAllZero)) != 0)))
|
|
{
|
|
LONG utcOffsetMins = dynamicTZI.Bias;
|
|
if (utcOffsetMins == 0) {
|
|
return uprv_strdup("Etc/UTC");
|
|
}
|
|
|
|
// No way to support when DST is turned off and the offset in minutes is not a multiple of 60.
|
|
if (utcOffsetMins % 60 == 0) {
|
|
char gmtOffsetTz[11] = {}; // "Etc/GMT+dd" is 11-char long with a terminal null.
|
|
// Note '-' before 'utcOffsetMin'. The timezone ID's sign convention
|
|
// is that a timezone ahead of UTC is Etc/GMT-<offset> and a timezone
|
|
// behind UTC is Etc/GMT+<offset>.
|
|
int ret = snprintf(gmtOffsetTz, UPRV_LENGTHOF(gmtOffsetTz), "Etc/GMT%+d", -utcOffsetMins / 60);
|
|
if (ret > 0 && ret < UPRV_LENGTHOF(gmtOffsetTz)) {
|
|
return uprv_strdup(gmtOffsetTz);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If DST is NOT disabled, but we have an empty TimeZoneKeyName, then it is unclear
|
|
// what we should do as this should not happen.
|
|
if (dynamicTZI.TimeZoneKeyName[0] == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
CharString winTZ;
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
winTZ.appendInvariantChars(UnicodeString(TRUE, dynamicTZI.TimeZoneKeyName, -1), status);
|
|
|
|
// Map Windows Timezone name (non-localized) to ICU timezone ID (~ Olson timezone id).
|
|
StackUResourceBundle winTZBundle;
|
|
ures_openDirectFillIn(winTZBundle.getAlias(), nullptr, "windowsZones", &status);
|
|
ures_getByKey(winTZBundle.getAlias(), "mapTimezones", winTZBundle.getAlias(), &status);
|
|
ures_getByKey(winTZBundle.getAlias(), winTZ.data(), winTZBundle.getAlias(), &status);
|
|
|
|
if (U_FAILURE(status)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Note: Since the ISO 3166 country/region codes are all invariant ASCII chars, we can
|
|
// directly downcast from wchar_t to do the conversion.
|
|
// We could call the A version of the GetGeoInfo API, but that would be slightly slower than calling the W API,
|
|
// as the A version of the API will end up calling MultiByteToWideChar anyways internally.
|
|
wchar_t regionCodeW[3] = {};
|
|
char regionCode[3] = {}; // 2 letter ISO 3166 country/region code made entirely of invariant chars.
|
|
int geoId = GetUserGeoID(GEOCLASS_NATION);
|
|
int regionCodeLen = GetGeoInfoW(geoId, GEO_ISO2, regionCodeW, UPRV_LENGTHOF(regionCodeW), 0);
|
|
|
|
const UChar *icuTZ16 = nullptr;
|
|
int32_t tzLen;
|
|
|
|
if (regionCodeLen != 0) {
|
|
for (int i = 0; i < UPRV_LENGTHOF(regionCodeW); i++) {
|
|
regionCode[i] = static_cast<char>(regionCodeW[i]);
|
|
}
|
|
icuTZ16 = ures_getStringByKey(winTZBundle.getAlias(), regionCode, &tzLen, &status);
|
|
}
|
|
if (regionCodeLen == 0 || U_FAILURE(status)) {
|
|
// fallback to default "001" (world)
|
|
status = U_ZERO_ERROR;
|
|
icuTZ16 = ures_getStringByKey(winTZBundle.getAlias(), "001", &tzLen, &status);
|
|
}
|
|
|
|
// Note: cloneData returns nullptr if the status is a failure, so this
|
|
// will return nullptr if the above look-up fails.
|
|
CharString icuTZStr;
|
|
return icuTZStr.appendInvariantChars(icuTZ16, tzLen, status).cloneData(status);
|
|
}
|
|
|
|
U_NAMESPACE_END
|
|
#endif /* U_PLATFORM_USES_ONLY_WIN32_API */
|