mirror of
https://github.com/HowardHinnant/date.git
synced 2025-08-03 12:44:27 +02:00
Avoid getting localized time zone names from Windows OS.
* Use GetDynamicTimeZoneInformation to get .TimeZoneKeyName https://msdn.microsoft.com/en-us/library/windows/desktop/ms724318(v=vs.85).aspx > Retrieves the current time zone and dynamic daylight saving time settings. https://msdn.microsoft.com/en-us/library/windows/desktop/ms724253(v=vs.85).aspx > The name of the time zone registry key on the local computer. * This avoids the need to map from StandardName to TimeZoneKeyName. * Using .DynamicDaylightTimeDisabled = true forces the OS to return a non-daylight saving time result. * Use of wcstombs is preferred over wstring_convert as the latter is now deprecated in C++17 because the implementors could not interpret the specification.
This commit is contained in:
239
tz.cpp
239
tz.cpp
@@ -43,6 +43,11 @@
|
|||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
|
// for wcstombs
|
||||||
|
#ifndef _CRT_SECURE_NO_WARNINGS
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
// None of this happens with the MS SDK (at least VS14 which I tested), but:
|
// None of this happens with the MS SDK (at least VS14 which I tested), but:
|
||||||
// Compiling with mingw, we get "error: 'KF_FLAG_DEFAULT' was not declared in this scope."
|
// Compiling with mingw, we get "error: 'KF_FLAG_DEFAULT' was not declared in this scope."
|
||||||
// and error: 'SHGetKnownFolderPath' was not declared in this scope.".
|
// and error: 'SHGetKnownFolderPath' was not declared in this scope.".
|
||||||
@@ -93,11 +98,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <locale>
|
|
||||||
#include <codecvt>
|
|
||||||
#endif // _WIN32
|
|
||||||
|
|
||||||
// unistd.h is used on some platforms as part of the the means to get
|
// unistd.h is used on some platforms as part of the the means to get
|
||||||
// the current time zone. On Win32 Windows.h provides a means to do it.
|
// the current time zone. On Win32 Windows.h provides a means to do it.
|
||||||
// gcc/mingw supports unistd.h on Win32 but MSVC does not.
|
// gcc/mingw supports unistd.h on Win32 but MSVC does not.
|
||||||
@@ -224,7 +224,6 @@ get_download_folder()
|
|||||||
return expand_path("~/Downloads");
|
return expand_path("~/Downloads");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif // !_WIN32
|
#endif // !_WIN32
|
||||||
|
|
||||||
namespace date
|
namespace date
|
||||||
@@ -317,93 +316,6 @@ get_windows_zones_install()
|
|||||||
return install;
|
return install;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A simple type to manage RAII for key handles and to
|
|
||||||
// implement the trivial registry interface we need.
|
|
||||||
// Not intended to be general-purpose.
|
|
||||||
class reg_key
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
// Note there is no value documented to be an invalid handle value.
|
|
||||||
// Not NULL nor INVALID_HANDLE_VALUE. We must rely on is_open.
|
|
||||||
HKEY m_key = nullptr;
|
|
||||||
bool m_is_open = false;
|
|
||||||
public:
|
|
||||||
~reg_key()
|
|
||||||
{
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
reg_key() = default;
|
|
||||||
reg_key(const reg_key&) = delete;
|
|
||||||
reg_key& operator=(const reg_key&) = delete;
|
|
||||||
|
|
||||||
HKEY handle()
|
|
||||||
{
|
|
||||||
return m_key;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_open() const
|
|
||||||
{
|
|
||||||
return m_is_open;
|
|
||||||
}
|
|
||||||
|
|
||||||
LONG open(const wchar_t* key_name)
|
|
||||||
{
|
|
||||||
LONG result;
|
|
||||||
result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, key_name, 0, KEY_READ, &m_key);
|
|
||||||
if (result == ERROR_SUCCESS)
|
|
||||||
m_is_open = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
LONG close()
|
|
||||||
{
|
|
||||||
if (m_is_open)
|
|
||||||
{
|
|
||||||
auto result = RegCloseKey(m_key);
|
|
||||||
assert(result == ERROR_SUCCESS);
|
|
||||||
if (result == ERROR_SUCCESS)
|
|
||||||
{
|
|
||||||
m_is_open = false;
|
|
||||||
m_key = nullptr;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return ERROR_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING: this function is not a general-purpose function.
|
|
||||||
// It has a hard-coded value size limit that should be sufficient for our use cases.
|
|
||||||
bool get_string(const wchar_t* key_name, std::string& value, std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>& converter)
|
|
||||||
{
|
|
||||||
value.clear();
|
|
||||||
wchar_t value_buffer[256];
|
|
||||||
// in/out parameter. Documentation say that size is a count of bytes not chars.
|
|
||||||
DWORD size = sizeof(value_buffer) - sizeof(value_buffer[0]);
|
|
||||||
DWORD tzi_type = REG_SZ;
|
|
||||||
if (RegQueryValueExW(handle(), key_name, nullptr, &tzi_type,
|
|
||||||
reinterpret_cast<LPBYTE>(value_buffer), &size) == ERROR_SUCCESS)
|
|
||||||
{
|
|
||||||
// Function does not guarantee to null terminate.
|
|
||||||
value_buffer[size/sizeof(value_buffer[0])] = L'\0';
|
|
||||||
value = converter.to_bytes(value_buffer);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_binary(const wchar_t* key_name, void* value, int value_size)
|
|
||||||
{
|
|
||||||
DWORD size = value_size;
|
|
||||||
DWORD type = REG_BINARY;
|
|
||||||
if (RegQueryValueExW(handle(), key_name, nullptr, &type,
|
|
||||||
reinterpret_cast<LPBYTE>(value), &size) == ERROR_SUCCESS
|
|
||||||
&& (int) size == value_size)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
static
|
static
|
||||||
@@ -586,7 +498,7 @@ sort_zone_mappings(std::vector<date::detail::timezone_mapping>& mappings)
|
|||||||
auto territory_result = lhs.territory.compare(rhs.territory);
|
auto territory_result = lhs.territory.compare(rhs.territory);
|
||||||
if (territory_result < 0)
|
if (territory_result < 0)
|
||||||
return true;
|
return true;
|
||||||
else if (territory_result == 9)
|
else if (territory_result == 0)
|
||||||
{
|
{
|
||||||
if (lhs.type < rhs.type)
|
if (lhs.type < rhs.type)
|
||||||
return true;
|
return true;
|
||||||
@@ -627,104 +539,6 @@ get_win32_message(DWORD error_code)
|
|||||||
return std::string(message_buffer.get());
|
return std::string(message_buffer.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function returns an exhaustive list of time zone information
|
|
||||||
// from the Windows registry.
|
|
||||||
// The routine tries to load as many time zone entries as possible despite errors.
|
|
||||||
// We don't want to fail to load the whole database just because one record can't be read.
|
|
||||||
|
|
||||||
static
|
|
||||||
void
|
|
||||||
get_windows_timezone_info(std::vector<detail::timezone_info>& tz_list)
|
|
||||||
{
|
|
||||||
tz_list.clear();
|
|
||||||
LONG result;
|
|
||||||
|
|
||||||
// Open the parent time zone key that has the list of timezones in.
|
|
||||||
reg_key zones_key;
|
|
||||||
static const wchar_t zones_key_name[] =
|
|
||||||
{ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones" };
|
|
||||||
result = zones_key.open(zones_key_name);
|
|
||||||
// TODO! Review if this should happen here or be signalled later.
|
|
||||||
// We don't want the process to fail on startup because of this.
|
|
||||||
if (result != ERROR_SUCCESS)
|
|
||||||
throw std::runtime_error("Time Zone registry key could not be opened: "
|
|
||||||
+ get_win32_message(result));
|
|
||||||
|
|
||||||
DWORD size;
|
|
||||||
wchar_t zone_key_name[256];
|
|
||||||
std::wstring value;
|
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
||||||
|
|
||||||
// Iterate through the list of keys of the parent time zones key to get
|
|
||||||
// each key that identifies each individual timezone.
|
|
||||||
std::wstring full_zone_key_name;
|
|
||||||
for (DWORD zone_index = 0; ; ++zone_index)
|
|
||||||
{
|
|
||||||
detail::timezone_info tz;
|
|
||||||
|
|
||||||
size = (DWORD) sizeof(zone_key_name)/sizeof(zone_key_name[0]);
|
|
||||||
auto status = RegEnumKeyExW(zones_key.handle(), zone_index, zone_key_name, &size,
|
|
||||||
nullptr, nullptr, nullptr, nullptr);
|
|
||||||
if (status != ERROR_SUCCESS && status != ERROR_NO_MORE_ITEMS)
|
|
||||||
throw std::runtime_error("Can't enumerate time zone registry key"
|
|
||||||
+ get_win32_message(status));
|
|
||||||
if (status == ERROR_NO_MORE_ITEMS)
|
|
||||||
break;
|
|
||||||
tz.timezone_id = converter.to_bytes(zone_key_name);
|
|
||||||
|
|
||||||
full_zone_key_name = zones_key_name;
|
|
||||||
full_zone_key_name += L'\\';
|
|
||||||
full_zone_key_name += zone_key_name;
|
|
||||||
|
|
||||||
// If any field fails to be found, consider the whole time zone
|
|
||||||
// entry corrupt and move onto the next. See comments
|
|
||||||
// at the top of function.
|
|
||||||
|
|
||||||
reg_key zone_key;
|
|
||||||
if (zone_key.open(full_zone_key_name.c_str()) != ERROR_SUCCESS)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!zone_key.get_string(L"Std", tz.standard_name, converter))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// TBD these fields are not required yet.
|
|
||||||
// They might be useful for test cases though.
|
|
||||||
if (!zone_key.get_string("Display", tz.display_name, converter))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!zone_key.get_binary("TZI", &tz.tzi, sizeof(TZI)))
|
|
||||||
continue;
|
|
||||||
#endif
|
|
||||||
zone_key.close();
|
|
||||||
|
|
||||||
tz_list.push_back(std::move(tz));
|
|
||||||
}
|
|
||||||
result = zones_key.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// standard_name is the StandardName field from the Windows
|
|
||||||
// TIME_ZONE_INFORMATION structure.
|
|
||||||
// See the Windows API function GetTimeZoneInformation.
|
|
||||||
// The standard_name is also the value from STD field of
|
|
||||||
// under the windows registry key Time Zones.
|
|
||||||
// To be clear, standard_name does NOT represent a windows timezone id
|
|
||||||
// or an IANA tzid
|
|
||||||
static
|
|
||||||
const detail::timezone_info*
|
|
||||||
find_native_timezone_by_standard_name(const std::string& standard_name)
|
|
||||||
{
|
|
||||||
// TODO! we can improve on linear search.
|
|
||||||
const auto& native_zones = get_tzdb().native_zones;
|
|
||||||
for (const auto& tz : native_zones)
|
|
||||||
{
|
|
||||||
if (tz.standard_name == standard_name)
|
|
||||||
return &tz;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
static
|
||||||
bool
|
bool
|
||||||
native_to_standard_timezone_name(const std::string& native_tz_name,
|
native_to_standard_timezone_name(const std::string& native_tz_name,
|
||||||
@@ -2934,7 +2748,6 @@ init_tzdb()
|
|||||||
std::string mapping_file = get_windows_zones_install() + folder_delimiter + "windowsZones.xml";
|
std::string mapping_file = get_windows_zones_install() + folder_delimiter + "windowsZones.xml";
|
||||||
db.mappings = load_timezone_mappings_from_xml_file(mapping_file);
|
db.mappings = load_timezone_mappings_from_xml_file(mapping_file);
|
||||||
sort_zone_mappings(db.mappings);
|
sort_zone_mappings(db.mappings);
|
||||||
get_windows_timezone_info(db.native_zones);
|
|
||||||
#endif // TIMEZONE_MAPPING
|
#endif // TIMEZONE_MAPPING
|
||||||
|
|
||||||
return db;
|
return db;
|
||||||
@@ -3058,35 +2871,33 @@ operator<<(std::ostream& os, const TZ_DB& db)
|
|||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
||||||
|
static
|
||||||
|
std::string
|
||||||
|
getTimeZoneKeyName()
|
||||||
|
{
|
||||||
|
DYNAMIC_TIME_ZONE_INFORMATION dtzi{};
|
||||||
|
auto result = GetDynamicTimeZoneInformation(&dtzi);
|
||||||
|
if (result == TIME_ZONE_ID_INVALID)
|
||||||
|
throw std::runtime_error("current_zone(): GetDynamicTimeZoneInformation()"
|
||||||
|
" reported TIME_ZONE_ID_INVALID.");
|
||||||
|
auto wlen = wcslen(dtzi.TimeZoneKeyName);
|
||||||
|
char buf[128] = {};
|
||||||
|
assert(sizeof(buf) >= wlen+1);
|
||||||
|
wcstombs(buf, dtzi.TimeZoneKeyName, wlen);
|
||||||
|
return std::string(buf);
|
||||||
|
}
|
||||||
|
|
||||||
const time_zone*
|
const time_zone*
|
||||||
current_zone()
|
current_zone()
|
||||||
{
|
{
|
||||||
#ifdef TIMEZONE_MAPPING
|
#ifdef TIMEZONE_MAPPING
|
||||||
TIME_ZONE_INFORMATION tzi{};
|
std::string win_tzid = getTimeZoneKeyName();
|
||||||
DWORD tz_result = ::GetTimeZoneInformation(&tzi);
|
|
||||||
if (tz_result == TIME_ZONE_ID_INVALID)
|
|
||||||
{
|
|
||||||
auto error_code = ::GetLastError(); // Store this quick before it gets overwritten.
|
|
||||||
throw std::runtime_error("GetTimeZoneInformation failed: "
|
|
||||||
+ get_win32_message(error_code));
|
|
||||||
}
|
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
|
||||||
std::string standard_name(converter.to_bytes(tzi.StandardName));
|
|
||||||
auto tz = find_native_timezone_by_standard_name(standard_name);
|
|
||||||
if (!tz)
|
|
||||||
{
|
|
||||||
std::string msg;
|
|
||||||
msg = "current_zone() failed: ";
|
|
||||||
msg += standard_name;
|
|
||||||
msg += " was not found in the Windows Time Zone registry";
|
|
||||||
throw std::runtime_error( msg );
|
|
||||||
}
|
|
||||||
std::string standard_tzid;
|
std::string standard_tzid;
|
||||||
if (!native_to_standard_timezone_name(tz->timezone_id, standard_tzid))
|
if (!native_to_standard_timezone_name(win_tzid, standard_tzid))
|
||||||
{
|
{
|
||||||
std::string msg;
|
std::string msg;
|
||||||
msg = "current_zone() failed: A mapping from the Windows Time Zone id \"";
|
msg = "current_zone() failed: A mapping from the Windows Time Zone id \"";
|
||||||
msg += tz->timezone_id;
|
msg += win_tzid;
|
||||||
msg += "\" was not found in the time zone mapping database.";
|
msg += "\" was not found in the time zone mapping database.";
|
||||||
throw std::runtime_error(msg);
|
throw std::runtime_error(msg);
|
||||||
}
|
}
|
||||||
|
24
tz.h
24
tz.h
@@ -710,13 +710,6 @@ struct timezone_mapping
|
|||||||
std::string type;
|
std::string type;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct timezone_info
|
|
||||||
{
|
|
||||||
timezone_info() = default;
|
|
||||||
std::string timezone_id;
|
|
||||||
std::string standard_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // detail
|
} // detail
|
||||||
|
|
||||||
#endif // TIMEZONE_MAPPING
|
#endif // TIMEZONE_MAPPING
|
||||||
@@ -731,7 +724,6 @@ struct TZ_DB
|
|||||||
#ifdef TIMEZONE_MAPPING
|
#ifdef TIMEZONE_MAPPING
|
||||||
// TODO! These need some protection.
|
// TODO! These need some protection.
|
||||||
std::vector<detail::timezone_mapping> mappings;
|
std::vector<detail::timezone_mapping> mappings;
|
||||||
std::vector<detail::timezone_info> native_zones;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TZ_DB() = default;
|
TZ_DB() = default;
|
||||||
@@ -740,16 +732,13 @@ struct TZ_DB
|
|||||||
TZ_DB& operator=(TZ_DB&&) = default;
|
TZ_DB& operator=(TZ_DB&&) = default;
|
||||||
#else // defined(_MSC_VER) || (_MSC_VER >= 1900)
|
#else // defined(_MSC_VER) || (_MSC_VER >= 1900)
|
||||||
TZ_DB(TZ_DB&& src)
|
TZ_DB(TZ_DB&& src)
|
||||||
:
|
: version(std::move(src.version))
|
||||||
version(std::move(src.version)),
|
, zones(std::move(src.zones))
|
||||||
zones(std::move(src.zones)),
|
, links(std::move(src.links))
|
||||||
links(std::move(src.links)),
|
, leaps(std::move(src.leaps))
|
||||||
leaps(std::move(src.leaps)),
|
, rules(std::move(src.rules))
|
||||||
rules(std::move(src.rules))
|
|
||||||
#ifdef TIMEZONE_MAPPING
|
#ifdef TIMEZONE_MAPPING
|
||||||
,
|
, mappings(std::move(src.mappings))
|
||||||
mappings(std::move(src.mappings)),
|
|
||||||
native_zones(std::move(src.native_zones))
|
|
||||||
#endif
|
#endif
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -762,7 +751,6 @@ struct TZ_DB
|
|||||||
rules = std::move(src.rules);
|
rules = std::move(src.rules);
|
||||||
#ifdef TIMEZONE_MAPPING
|
#ifdef TIMEZONE_MAPPING
|
||||||
mappings = std::move(src.mappings);
|
mappings = std::move(src.mappings);
|
||||||
native_zones = std::move(src.native_zones);
|
|
||||||
#endif
|
#endif
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user