diff --git a/app/src/heartbeat/heartbeat_storage_desktop.cc b/app/src/heartbeat/heartbeat_storage_desktop.cc index ed94073b5f..8b2f6389f3 100644 --- a/app/src/heartbeat/heartbeat_storage_desktop.cc +++ b/app/src/heartbeat/heartbeat_storage_desktop.cc @@ -69,7 +69,7 @@ std::string CreateFilename(const std::string& app_id, const Logger& logger) { std::string final_path_utf8 = app_dir + "/" + kHeartbeatFilenamePrefix + app_id_without_symbols; #if FIREBASE_PLATFORM_WINDOWS - std::wstring_convert> final_path_w; + std::wstring_convert> final_path_w; return final_path_w.from_bytes(final_path_utf8); #else return final_path_utf8; diff --git a/app/src/locale.cc b/app/src/locale.cc index 77e1b80db2..887733268b 100644 --- a/app/src/locale.cc +++ b/app/src/locale.cc @@ -42,6 +42,8 @@ #include #include +#include "app/src/thread.h" + namespace firebase { namespace internal { @@ -92,6 +94,27 @@ std::string GetLocale() { #endif // platform selector } +#if FIREBASE_PLATFORM_WINDOWS +std::wstring GetWindowsTimezoneInEnglish(int daylight) { + struct TzNames { + std::wstring standard; + std::wstring daylight; + } tz_names; + Thread thread( + [](TzNames* tz_names_ptr) { + SetThreadUILanguage(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)); + TIME_ZONE_INFORMATION tzi; + GetTimeZoneInformation(&tzi); + tz_names_ptr->standard = tzi.StandardName; + tz_names_ptr->daylight = tzi.DaylightName; + }, + &tz_names); + thread.Join(); + + return daylight ? tz_names.daylight : tz_names.standard; +} +#endif // FIREBASE_PLATFORM_WINDOWS + // Get the current time zone, e.g. "US/Pacific" std::string GetTimezone() { #if FIREBASE_PLATFORM_WINDOWS @@ -101,20 +124,17 @@ std::string GetTimezone() { // settings or the TZ variable, as appropriate. tz_was_set = true; } - // Get the standard time zone name. + + // Get the non-daylight time zone, as the IANA conversion below requires the + // name of the standard time zone. For example, "Central European Standard + // Time" which converts to "Europe/Warsaw" or similar. + std::wstring windows_tz_utf16 = GetWindowsTimezoneInEnglish(0); std::string windows_tz_utf8; { - size_t length = 0; // get the needed string length - if (_get_tzname(&length, nullptr, 0, 0) != 0) return ""; - std::vector namebuf(length); - if (_get_tzname(&length, &namebuf[0], length, 0) != 0) return ""; - windows_tz_utf8 = std::string(&namebuf[0]); + std::wstring_convert, wchar_t> to_utf8; + windows_tz_utf8 = to_utf8.to_bytes(windows_tz_utf16); } - // Convert time zone name to wide string - std::wstring_convert> to_utf16; - std::wstring windows_tz_utf16 = to_utf16.from_bytes(windows_tz_utf8); - std::string locale_name = GetLocale(); wchar_t iana_time_zone_buffer[128]; bool got_time_zone = false; @@ -157,28 +177,47 @@ std::string GetTimezone() { windows_tz_utf8.c_str(), u_errorName(error_code), error_code); } } + if (got_time_zone) { + // One of the above two succeeded, convert the new time zone name back to + // UTF-8. + std::wstring iana_tz_utf16(iana_time_zone_buffer); + std::wstring_convert, wchar_t> to_utf8; + std::string iana_tz_utf8; + try { + iana_tz_utf8 = to_utf8.to_bytes(iana_tz_utf16); + } catch (std::range_error& ex) { + LogError("Failed to convert IANA time zone to UTF-8: %s", ex.what()); + got_time_zone = false; + } + if (got_time_zone) { + return iana_tz_utf8; + } + } if (!got_time_zone) { - // Return the Windows time zone ID as a backup. - // In this case, we need to get the correct daylight savings time - // setting to get the right name. + // Either the IANA time zone couldn't be determined, or couldn't be + // converted into UTF-8 for some reason (the std::range_error above). + // + // In any case, return the Windows time zone ID as a backup. We now need to + // get the correct daylight saving time setting to get the right name. + // + // Also, note as above that _get_tzname() doesn't return a UTF-8 name, + // rather CP-1252, so convert it to UTF-8 (via UTF-16) before returning. + int daylight = 0; // daylight savings time? - if (_get_daylight(&daylight) != 0) return windows_tz_utf8; + if (_get_daylight(&daylight) != 0) { + // Couldn't determine daylight saving time, return the old name. + return windows_tz_utf8; + } if (daylight) { - size_t length = 0; // get the needed string length - if (_get_tzname(&length, nullptr, 0, 1) != 0) return windows_tz_utf8; - std::vector namebuf(length); - if (_get_tzname(&length, &namebuf[0], length, 1) != 0) - return windows_tz_utf8; - windows_tz_utf8 = std::string(&namebuf[0]); + windows_tz_utf16 = GetWindowsTimezoneInEnglish(daylight); + { + std::wstring_convert, wchar_t> to_utf8; + windows_tz_utf8 = to_utf8.to_bytes(windows_tz_utf16); + } } return windows_tz_utf8; } - std::wstring iana_tz_utf16(iana_time_zone_buffer); - std::wstring_convert, wchar_t> to_utf8; - std::string iana_tz_utf8 = to_utf8.to_bytes(iana_tz_utf16); - return iana_tz_utf8; - #elif FIREBASE_PLATFORM_LINUX // If TZ environment variable is defined and not empty, use it, else use // tzname. diff --git a/app/src/locale.h b/app/src/locale.h index bf904f4278..4cf6b4240f 100644 --- a/app/src/locale.h +++ b/app/src/locale.h @@ -20,6 +20,8 @@ #include +#include "app/src/include/firebase/internal/platform.h" + namespace firebase { namespace internal { diff --git a/app/tests/locale_test.cc b/app/tests/locale_test.cc index 012ca69523..ce809afc40 100644 --- a/app/tests/locale_test.cc +++ b/app/tests/locale_test.cc @@ -16,6 +16,7 @@ #include "app/src/locale.h" +#include #include #include "app/src/include/firebase/internal/platform.h" @@ -24,7 +25,7 @@ #include "gtest/gtest.h" #if FIREBASE_PLATFORM_WINDOWS - +#include #else #include #endif // FIREBASE_PLATFORM_WINDOWS @@ -52,5 +53,23 @@ TEST_F(LocaleTest, TestGetLocale) { EXPECT_NE(loc.find('_'), std::string::npos); } +#if FIREBASE_PLATFORM_WINDOWS + +TEST_F(LocaleTest, TestTimeZoneNameInEnglish) { + LANGID prev_lang = GetThreadUILanguage(); + + SetThreadUILanguage(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)); + std::string timezone_english = GetTimezone(); + + SetThreadUILanguage(MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN)); + std::string timezone_german = GetTimezone(); + + EXPECT_EQ(timezone_english, timezone_german); + + SetThreadUILanguage(prev_lang); +} + +#endif // FIREBASE_PLATFORM_WINDOWS + } // namespace internal } // namespace firebase diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 58ad89bba2..09e380cf23 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -631,6 +631,8 @@ code. - Changes - Auth (Android): Fixed an issue where VerifyPhoneNumber's internal builder failed to create PhoneAuthOptions with certain compiler settings. + - Remote Config (Desktop): Additional fix for handling of non-English time + zone names on Windows. ### 11.2.0 - Changes diff --git a/remote_config/src/desktop/file_manager.cc b/remote_config/src/desktop/file_manager.cc index 3240c40cf4..f4cf5aeb5f 100644 --- a/remote_config/src/desktop/file_manager.cc +++ b/remote_config/src/desktop/file_manager.cc @@ -42,7 +42,7 @@ RemoteConfigFileManager::RemoteConfigFileManager(const std::string& filename, AppDataDir(app_data_prefix.c_str(), /*should_create=*/true) + "/" + filename; #if FIREBASE_PLATFORM_WINDOWS - std::wstring_convert> utf8_to_wstring; + std::wstring_convert> utf8_to_wstring; file_path_ = utf8_to_wstring.from_bytes(file_path); #else file_path_ = file_path;