Skip to content

Commit 974fb07

Browse files
authored
Handle time zone environment variable having international characters. (#1332)
* Handle time zone environment variable having international characters. * Fix environment variable string. * Fix build error. * Try a French time zone. * Fix workflow. * Escape characters. * Use GITHUB_ENV * Fix shell command. * Change the approach, using tzset() instead. * Logging. * Format code. * Set the time zone via tzutil. * List time zones available. * Remove comment. * Set to Paris time. * Fix test timezone. * New method using ICL. * Clean up string conversion. * Clean up log. * Fix compile error. * Add ICU DLL. * Fix log; test France. * Initialize variables. * Cast * Don't get the daylight zone. * Fix * Fix 2 * Fix daylight mode fallback. * Remove French language test code. * Change log messages to debug. * Change errors. * Add icu.dll to library dependencies. * Update release notes. * Remove version number * Added icu.dll dep to locale unit test. * Update release note. * Remove extraneous printfs. * Add comment to test cmake.
1 parent 25d6324 commit 974fb07

File tree

5 files changed

+112
-19
lines changed

5 files changed

+112
-19
lines changed

app/src/locale.cc

Lines changed: 89 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,20 @@
2626
#elif FIREBASE_PLATFORM_WINDOWS
2727
#include <time.h>
2828
#include <windows.h>
29+
// To convert Windows time zone names to IANA time zone names:
30+
#define UCHAR_TYPE wchar_t
31+
#include <icu.h>
2932
#elif FIREBASE_PLATFORM_LINUX
3033
#include <clocale>
3134
#include <ctime>
32-
#include <locale>
3335
#else
3436
#error "Unknown platform."
3537
#endif // platform selector
3638

3739
#include <algorithm>
40+
#include <codecvt>
41+
#include <locale>
42+
#include <string>
3843
#include <vector>
3944

4045
namespace firebase {
@@ -90,22 +95,90 @@ std::string GetLocale() {
9095
// Get the current time zone, e.g. "US/Pacific"
9196
std::string GetTimezone() {
9297
#if FIREBASE_PLATFORM_WINDOWS
93-
// If "TZ" environment variable is defined, use it, else use _get_tzname.
94-
int tz_bytes = GetEnvironmentVariable("TZ", nullptr, 0);
95-
if (tz_bytes > 0) {
96-
std::vector<char> buffer(tz_bytes);
97-
GetEnvironmentVariable("TZ", &buffer[0], tz_bytes);
98-
return std::string(&buffer[0]);
98+
static bool tz_was_set = false;
99+
if (!tz_was_set) {
100+
_tzset(); // Set the time zone used by the below functions, based on OS
101+
// settings or the TZ variable, as appropriate.
102+
tz_was_set = true;
99103
}
100-
int daylight; // daylight savings time?
101-
if (_get_daylight(&daylight) != 0) return "";
102-
size_t length = 0; // get the needed string length
103-
if (_get_tzname(&length, nullptr, 0, daylight ? 1 : 0) != 0) return "";
104-
std::vector<char> namebuf(length);
105-
if (_get_tzname(&length, &namebuf[0], length, daylight ? 1 : 0) != 0)
106-
return "";
107-
std::string name_str(&namebuf[0]);
108-
return name_str;
104+
// Get the standard time zone name.
105+
std::string windows_tz_utf8;
106+
{
107+
size_t length = 0; // get the needed string length
108+
if (_get_tzname(&length, nullptr, 0, 0) != 0) return "";
109+
std::vector<char> namebuf(length);
110+
if (_get_tzname(&length, &namebuf[0], length, 0) != 0) return "";
111+
windows_tz_utf8 = std::string(&namebuf[0]);
112+
}
113+
114+
// Convert time zone name to wide string
115+
std::wstring_convert<std::codecvt_utf8<wchar_t>> to_utf16;
116+
std::wstring windows_tz_utf16 = to_utf16.from_bytes(windows_tz_utf8);
117+
118+
std::string locale_name = GetLocale();
119+
wchar_t iana_time_zone_buffer[128];
120+
bool got_time_zone = false;
121+
if (locale_name.size() >= 5) {
122+
wcscpy(iana_time_zone_buffer, L"");
123+
UErrorCode error_code = (UErrorCode)0;
124+
int32_t size = 0;
125+
// Try time zone first with the region code returned above, assuming it's at
126+
// least 5 characters. For example, "en_US" -> "US"
127+
std::string region_code = std::string(&locale_name[3], 2);
128+
size = ucal_getTimeZoneIDForWindowsID(
129+
windows_tz_utf16.c_str(), -1, region_code.c_str(),
130+
iana_time_zone_buffer,
131+
sizeof(iana_time_zone_buffer) / sizeof(iana_time_zone_buffer[0]),
132+
&error_code);
133+
got_time_zone = (U_SUCCESS(error_code) && size > 0);
134+
if (!got_time_zone) {
135+
LogDebug(
136+
"Couldn't convert Windows time zone '%s' with region '%s' to IANA: "
137+
"%s (%x). Falling back to non-region time zone conversion.",
138+
windows_tz_utf8.c_str(), region_code.c_str(), u_errorName(error_code),
139+
error_code);
140+
}
141+
}
142+
if (!got_time_zone) {
143+
wcscpy(iana_time_zone_buffer, L"");
144+
UErrorCode error_code = (UErrorCode)0;
145+
int32_t size = 0;
146+
// Try without specifying a region
147+
size = ucal_getTimeZoneIDForWindowsID(
148+
windows_tz_utf16.c_str(), -1, nullptr, iana_time_zone_buffer,
149+
sizeof(iana_time_zone_buffer) / sizeof(iana_time_zone_buffer[0]),
150+
&error_code);
151+
got_time_zone = (U_SUCCESS(error_code) && size > 0);
152+
if (!got_time_zone) {
153+
// Couldn't convert to IANA
154+
LogDebug(
155+
"Couldn't convert time zone '%s' to IANA: %s (%x). Falling back to "
156+
"Windows time zone name.",
157+
windows_tz_utf8.c_str(), u_errorName(error_code), error_code);
158+
}
159+
}
160+
if (!got_time_zone) {
161+
// Return the Windows time zone ID as a backup.
162+
// In this case, we need to get the correct daylight savings time
163+
// setting to get the right name.
164+
int daylight = 0; // daylight savings time?
165+
if (_get_daylight(&daylight) != 0) return windows_tz_utf8;
166+
if (daylight) {
167+
size_t length = 0; // get the needed string length
168+
if (_get_tzname(&length, nullptr, 0, 1) != 0) return windows_tz_utf8;
169+
std::vector<char> namebuf(length);
170+
if (_get_tzname(&length, &namebuf[0], length, 1) != 0)
171+
return windows_tz_utf8;
172+
windows_tz_utf8 = std::string(&namebuf[0]);
173+
}
174+
return windows_tz_utf8;
175+
}
176+
177+
std::wstring iana_tz_utf16(iana_time_zone_buffer);
178+
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> to_utf8;
179+
std::string iana_tz_utf8 = to_utf8.to_bytes(iana_tz_utf16);
180+
return iana_tz_utf8;
181+
109182
#elif FIREBASE_PLATFORM_LINUX
110183
// If TZ environment variable is defined and not empty, use it, else use
111184
// tzname.

app/tests/CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,11 +207,19 @@ firebase_cpp_cc_test(firebase_app_path_test
207207
firebase_app
208208
)
209209

210+
if (MSVC)
211+
# On Windows, our locale library's GetTimezone() function requires the icu.dll
212+
# system library for converting from Windows time zones to IANA time zones.
213+
set(LOCALE_TEST_DEPS icu)
214+
else()
215+
set(LOCALE_TEST_DEPS)
216+
endif()
217+
210218
firebase_cpp_cc_test(firebase_app_locale_test
211219
SOURCES
212220
locale_test.cc
213221
DEPENDS
214-
firebase_app
222+
firebase_app ${LOCALE_TEST_DEPS}
215223
)
216224

217225
firebase_cpp_cc_test(firebase_app_callback_test

release_build_files/readme.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ App Check | `advapi32, ws2_32, crypt32`
483483
Firestore | `advapi32, ws2_32, crypt32, rpcrt4, ole32, shell32, dbghelp, bcrypt`
484484
Functions | `advapi32, ws2_32, crypt32, rpcrt4, ole32`
485485
Realtime Database | `advapi32, ws2_32, crypt32, iphlpapi, psapi, userenv, shell32`
486-
Remote Config | `advapi32, ws2_32, crypt32, rpcrt4, ole32`
486+
Remote Config | `advapi32, ws2_32, crypt32, rpcrt4, ole32, icu`
487487
Storage | `advapi32, ws2_32, crypt32`
488488

489489
## Getting Started
@@ -627,6 +627,13 @@ workflow use only during the development of your app, not for publicly shipping
627627
code.
628628

629629
## Release Notes
630+
### Upcoming Release
631+
- Changes
632+
- Remote Config (Desktop): Fixed handling of time zones on Windows when the
633+
time zone name in the current system language contains an accented
634+
character or apostrophe. This adds a requirement for applications using
635+
Remote Config on Windows desktop to link the "icu.dll" system library.
636+
630637
### 11.1.0
631638
- Changes
632639
- General (Android): Update to Firebase Android BoM version 32.1.0.

remote_config/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ else()
107107
${PROJECT_BINARY_DIR}/..)
108108
set(additional_link_LIB
109109
firebase_rest_lib)
110+
if(MSVC)
111+
# Remote Config on Windows requires the icu.dll system library for time zone
112+
# handling.
113+
set(additional_link_LIB ${additional_link_LIB} icu)
114+
endif()
110115
endif()
111116

112117
add_library(firebase_remote_config STATIC

remote_config/integration_test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ else()
209209
"-framework Security"
210210
)
211211
elseif(MSVC)
212-
set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32)
212+
set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32 icu)
213213
else()
214214
set(ADDITIONAL_LIBS pthread)
215215
endif()

0 commit comments

Comments
 (0)