diff --git a/app/src/locale.cc b/app/src/locale.cc index c007208cef..77e1b80db2 100644 --- a/app/src/locale.cc +++ b/app/src/locale.cc @@ -26,15 +26,20 @@ #elif FIREBASE_PLATFORM_WINDOWS #include #include +// To convert Windows time zone names to IANA time zone names: +#define UCHAR_TYPE wchar_t +#include #elif FIREBASE_PLATFORM_LINUX #include #include -#include #else #error "Unknown platform." #endif // platform selector #include +#include +#include +#include #include namespace firebase { @@ -90,22 +95,90 @@ std::string GetLocale() { // Get the current time zone, e.g. "US/Pacific" std::string GetTimezone() { #if FIREBASE_PLATFORM_WINDOWS - // If "TZ" environment variable is defined, use it, else use _get_tzname. - int tz_bytes = GetEnvironmentVariable("TZ", nullptr, 0); - if (tz_bytes > 0) { - std::vector buffer(tz_bytes); - GetEnvironmentVariable("TZ", &buffer[0], tz_bytes); - return std::string(&buffer[0]); + static bool tz_was_set = false; + if (!tz_was_set) { + _tzset(); // Set the time zone used by the below functions, based on OS + // settings or the TZ variable, as appropriate. + tz_was_set = true; } - int daylight; // daylight savings time? - if (_get_daylight(&daylight) != 0) return ""; - size_t length = 0; // get the needed string length - if (_get_tzname(&length, nullptr, 0, daylight ? 1 : 0) != 0) return ""; - std::vector namebuf(length); - if (_get_tzname(&length, &namebuf[0], length, daylight ? 1 : 0) != 0) - return ""; - std::string name_str(&namebuf[0]); - return name_str; + // Get the standard time zone name. + 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]); + } + + // 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; + if (locale_name.size() >= 5) { + wcscpy(iana_time_zone_buffer, L""); + UErrorCode error_code = (UErrorCode)0; + int32_t size = 0; + // Try time zone first with the region code returned above, assuming it's at + // least 5 characters. For example, "en_US" -> "US" + std::string region_code = std::string(&locale_name[3], 2); + size = ucal_getTimeZoneIDForWindowsID( + windows_tz_utf16.c_str(), -1, region_code.c_str(), + iana_time_zone_buffer, + sizeof(iana_time_zone_buffer) / sizeof(iana_time_zone_buffer[0]), + &error_code); + got_time_zone = (U_SUCCESS(error_code) && size > 0); + if (!got_time_zone) { + LogDebug( + "Couldn't convert Windows time zone '%s' with region '%s' to IANA: " + "%s (%x). Falling back to non-region time zone conversion.", + windows_tz_utf8.c_str(), region_code.c_str(), u_errorName(error_code), + error_code); + } + } + if (!got_time_zone) { + wcscpy(iana_time_zone_buffer, L""); + UErrorCode error_code = (UErrorCode)0; + int32_t size = 0; + // Try without specifying a region + size = ucal_getTimeZoneIDForWindowsID( + windows_tz_utf16.c_str(), -1, nullptr, iana_time_zone_buffer, + sizeof(iana_time_zone_buffer) / sizeof(iana_time_zone_buffer[0]), + &error_code); + got_time_zone = (U_SUCCESS(error_code) && size > 0); + if (!got_time_zone) { + // Couldn't convert to IANA + LogDebug( + "Couldn't convert time zone '%s' to IANA: %s (%x). Falling back to " + "Windows time zone name.", + windows_tz_utf8.c_str(), u_errorName(error_code), error_code); + } + } + 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. + int daylight = 0; // daylight savings time? + if (_get_daylight(&daylight) != 0) 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]); + } + 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/tests/CMakeLists.txt b/app/tests/CMakeLists.txt index eaa8c80676..f6c1ec5eff 100644 --- a/app/tests/CMakeLists.txt +++ b/app/tests/CMakeLists.txt @@ -207,11 +207,19 @@ firebase_cpp_cc_test(firebase_app_path_test firebase_app ) +if (MSVC) + # On Windows, our locale library's GetTimezone() function requires the icu.dll + # system library for converting from Windows time zones to IANA time zones. + set(LOCALE_TEST_DEPS icu) +else() + set(LOCALE_TEST_DEPS) +endif() + firebase_cpp_cc_test(firebase_app_locale_test SOURCES locale_test.cc DEPENDS - firebase_app + firebase_app ${LOCALE_TEST_DEPS} ) firebase_cpp_cc_test(firebase_app_callback_test diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 702bcf6dc0..9d8fd15df6 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -483,7 +483,7 @@ App Check | `advapi32, ws2_32, crypt32` Firestore | `advapi32, ws2_32, crypt32, rpcrt4, ole32, shell32, dbghelp, bcrypt` Functions | `advapi32, ws2_32, crypt32, rpcrt4, ole32` Realtime Database | `advapi32, ws2_32, crypt32, iphlpapi, psapi, userenv, shell32` -Remote Config | `advapi32, ws2_32, crypt32, rpcrt4, ole32` +Remote Config | `advapi32, ws2_32, crypt32, rpcrt4, ole32, icu` Storage | `advapi32, ws2_32, crypt32` ## Getting Started @@ -627,6 +627,13 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes +### Upcoming Release +- Changes + - Remote Config (Desktop): Fixed handling of time zones on Windows when the + time zone name in the current system language contains an accented + character or apostrophe. This adds a requirement for applications using + Remote Config on Windows desktop to link the "icu.dll" system library. + ### 11.1.0 - Changes - General (Android): Update to Firebase Android BoM version 32.1.0. diff --git a/remote_config/CMakeLists.txt b/remote_config/CMakeLists.txt index 906a129e91..624f28a8b5 100644 --- a/remote_config/CMakeLists.txt +++ b/remote_config/CMakeLists.txt @@ -107,6 +107,11 @@ else() ${PROJECT_BINARY_DIR}/..) set(additional_link_LIB firebase_rest_lib) + if(MSVC) + # Remote Config on Windows requires the icu.dll system library for time zone + # handling. + set(additional_link_LIB ${additional_link_LIB} icu) + endif() endif() add_library(firebase_remote_config STATIC diff --git a/remote_config/integration_test/CMakeLists.txt b/remote_config/integration_test/CMakeLists.txt index 3b61f6f729..6e99d6836e 100644 --- a/remote_config/integration_test/CMakeLists.txt +++ b/remote_config/integration_test/CMakeLists.txt @@ -209,7 +209,7 @@ else() "-framework Security" ) elseif(MSVC) - set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32 icu) else() set(ADDITIONAL_LIBS pthread) endif()