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:
Howard Hinnant
2017-04-16 14:06:20 -04:00
parent 885910375f
commit c64d69b1e1
2 changed files with 31 additions and 232 deletions

239
tz.cpp
View File

@@ -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
View File

@@ -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;
}