mirror of
https://github.com/HowardHinnant/date.git
synced 2025-08-01 03:34:26 +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
|
||||
#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:
|
||||
// Compiling with mingw, we get "error: 'KF_FLAG_DEFAULT' was not declared in this scope."
|
||||
// and error: 'SHGetKnownFolderPath' was not declared in this scope.".
|
||||
@@ -93,11 +98,6 @@
|
||||
#include <vector>
|
||||
#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
|
||||
// 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.
|
||||
@@ -224,7 +224,6 @@ get_download_folder()
|
||||
return expand_path("~/Downloads");
|
||||
}
|
||||
|
||||
|
||||
#endif // !_WIN32
|
||||
|
||||
namespace date
|
||||
@@ -317,93 +316,6 @@ get_windows_zones_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
|
||||
|
||||
static
|
||||
@@ -586,7 +498,7 @@ sort_zone_mappings(std::vector<date::detail::timezone_mapping>& mappings)
|
||||
auto territory_result = lhs.territory.compare(rhs.territory);
|
||||
if (territory_result < 0)
|
||||
return true;
|
||||
else if (territory_result == 9)
|
||||
else if (territory_result == 0)
|
||||
{
|
||||
if (lhs.type < rhs.type)
|
||||
return true;
|
||||
@@ -627,104 +539,6 @@ get_win32_message(DWORD error_code)
|
||||
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
|
||||
bool
|
||||
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";
|
||||
db.mappings = load_timezone_mappings_from_xml_file(mapping_file);
|
||||
sort_zone_mappings(db.mappings);
|
||||
get_windows_timezone_info(db.native_zones);
|
||||
#endif // TIMEZONE_MAPPING
|
||||
|
||||
return db;
|
||||
@@ -3058,35 +2871,33 @@ operator<<(std::ostream& os, const TZ_DB& db)
|
||||
|
||||
#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*
|
||||
current_zone()
|
||||
{
|
||||
#ifdef TIMEZONE_MAPPING
|
||||
TIME_ZONE_INFORMATION tzi{};
|
||||
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 win_tzid = getTimeZoneKeyName();
|
||||
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;
|
||||
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.";
|
||||
throw std::runtime_error(msg);
|
||||
}
|
||||
|
24
tz.h
24
tz.h
@@ -710,13 +710,6 @@ struct timezone_mapping
|
||||
std::string type;
|
||||
};
|
||||
|
||||
struct timezone_info
|
||||
{
|
||||
timezone_info() = default;
|
||||
std::string timezone_id;
|
||||
std::string standard_name;
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
#endif // TIMEZONE_MAPPING
|
||||
@@ -731,7 +724,6 @@ struct TZ_DB
|
||||
#ifdef TIMEZONE_MAPPING
|
||||
// TODO! These need some protection.
|
||||
std::vector<detail::timezone_mapping> mappings;
|
||||
std::vector<detail::timezone_info> native_zones;
|
||||
#endif
|
||||
|
||||
TZ_DB() = default;
|
||||
@@ -740,16 +732,13 @@ struct TZ_DB
|
||||
TZ_DB& operator=(TZ_DB&&) = default;
|
||||
#else // defined(_MSC_VER) || (_MSC_VER >= 1900)
|
||||
TZ_DB(TZ_DB&& src)
|
||||
:
|
||||
version(std::move(src.version)),
|
||||
zones(std::move(src.zones)),
|
||||
links(std::move(src.links)),
|
||||
leaps(std::move(src.leaps)),
|
||||
rules(std::move(src.rules))
|
||||
: version(std::move(src.version))
|
||||
, zones(std::move(src.zones))
|
||||
, links(std::move(src.links))
|
||||
, leaps(std::move(src.leaps))
|
||||
, rules(std::move(src.rules))
|
||||
#ifdef TIMEZONE_MAPPING
|
||||
,
|
||||
mappings(std::move(src.mappings)),
|
||||
native_zones(std::move(src.native_zones))
|
||||
, mappings(std::move(src.mappings))
|
||||
#endif
|
||||
{}
|
||||
|
||||
@@ -762,7 +751,6 @@ struct TZ_DB
|
||||
rules = std::move(src.rules);
|
||||
#ifdef TIMEZONE_MAPPING
|
||||
mappings = std::move(src.mappings);
|
||||
native_zones = std::move(src.native_zones);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
Reference in New Issue
Block a user