diff --git a/tz.cpp b/tz.cpp index 78ef91f..6847912 100644 --- a/tz.cpp +++ b/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 #include -#ifdef _WIN32 -#include -#include -#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>& 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(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(value), &size) == ERROR_SUCCESS - && (int) size == value_size) - return true; - return false; - } -}; - } // anonymous namespace static @@ -586,7 +498,7 @@ sort_zone_mappings(std::vector& 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& 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> 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> 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); } diff --git a/tz.h b/tz.h index 26b7768..1093e6a 100644 --- a/tz.h +++ b/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 mappings; - std::vector 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; }