Skip to content

Commit 4fd4ce9

Browse files
authored
Fix for windows time zone name and other Unicode issues (#1375)
* Fix for Unicode characters above U+FFFF. * Add error handling to time zone conversion. * Fix scoping error. * Convert Windows TZ environment variable from CP-1252 to Unicode. * Don't use a variable length array. * Fix compiler error. * Modify readme. * Fix array lookup. * Update readme. * Also fix the fallback daylight saving time string encoding. * Rephrase readme. * Add unit test for conversions on Windows. * Add test for CP1252 conversion and fix conversion function that we have a test for it. * Remove duplicate code. * Add missing platform header. * Add additional characters to locale test. * Let's go wild, even more characters. This should cover most of what we need. * Format code. * Modified locale test to work with Windows wchar_t. * Format code. * Set thread language to English and use GetTimeZoneInformation. * Format code. * Add a comment about why we don't use daylight savings time in one spot.
1 parent 76a58c3 commit 4fd4ce9

File tree

6 files changed

+90
-28
lines changed

6 files changed

+90
-28
lines changed

app/src/heartbeat/heartbeat_storage_desktop.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ std::string CreateFilename(const std::string& app_id, const Logger& logger) {
6969
std::string final_path_utf8 =
7070
app_dir + "/" + kHeartbeatFilenamePrefix + app_id_without_symbols;
7171
#if FIREBASE_PLATFORM_WINDOWS
72-
std::wstring_convert<std::codecvt_utf8<wchar_t>> final_path_w;
72+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> final_path_w;
7373
return final_path_w.from_bytes(final_path_utf8);
7474
#else
7575
return final_path_utf8;

app/src/locale.cc

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
#include <string>
4545
#include <vector>
4646

47+
#include "app/src/thread.h"
48+
4749
namespace firebase {
4850
namespace internal {
4951

@@ -94,6 +96,27 @@ std::string GetLocale() {
9496
#endif // platform selector
9597
}
9698

99+
#if FIREBASE_PLATFORM_WINDOWS
100+
std::wstring GetWindowsTimezoneInEnglish(int daylight) {
101+
struct TzNames {
102+
std::wstring standard;
103+
std::wstring daylight;
104+
} tz_names;
105+
Thread thread(
106+
[](TzNames* tz_names_ptr) {
107+
SetThreadUILanguage(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US));
108+
TIME_ZONE_INFORMATION tzi;
109+
GetTimeZoneInformation(&tzi);
110+
tz_names_ptr->standard = tzi.StandardName;
111+
tz_names_ptr->daylight = tzi.DaylightName;
112+
},
113+
&tz_names);
114+
thread.Join();
115+
116+
return daylight ? tz_names.daylight : tz_names.standard;
117+
}
118+
#endif // FIREBASE_PLATFORM_WINDOWS
119+
97120
// Get the current time zone, e.g. "US/Pacific"
98121
std::string GetTimezone() {
99122
#if FIREBASE_PLATFORM_WINDOWS
@@ -103,20 +126,17 @@ std::string GetTimezone() {
103126
// settings or the TZ variable, as appropriate.
104127
tz_was_set = true;
105128
}
106-
// Get the standard time zone name.
129+
130+
// Get the non-daylight time zone, as the IANA conversion below requires the
131+
// name of the standard time zone. For example, "Central European Standard
132+
// Time" which converts to "Europe/Warsaw" or similar.
133+
std::wstring windows_tz_utf16 = GetWindowsTimezoneInEnglish(0);
107134
std::string windows_tz_utf8;
108135
{
109-
size_t length = 0; // get the needed string length
110-
if (_get_tzname(&length, nullptr, 0, 0) != 0) return "";
111-
std::vector<char> namebuf(length);
112-
if (_get_tzname(&length, &namebuf[0], length, 0) != 0) return "";
113-
windows_tz_utf8 = std::string(&namebuf[0]);
136+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> to_utf8;
137+
windows_tz_utf8 = to_utf8.to_bytes(windows_tz_utf16);
114138
}
115139

116-
// Convert time zone name to wide string
117-
std::wstring_convert<std::codecvt_utf8<wchar_t>> to_utf16;
118-
std::wstring windows_tz_utf16 = to_utf16.from_bytes(windows_tz_utf8);
119-
120140
std::string locale_name = GetLocale();
121141
wchar_t iana_time_zone_buffer[128];
122142
bool got_time_zone = false;
@@ -159,28 +179,47 @@ std::string GetTimezone() {
159179
windows_tz_utf8.c_str(), u_errorName(error_code), error_code);
160180
}
161181
}
182+
if (got_time_zone) {
183+
// One of the above two succeeded, convert the new time zone name back to
184+
// UTF-8.
185+
std::wstring iana_tz_utf16(iana_time_zone_buffer);
186+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> to_utf8;
187+
std::string iana_tz_utf8;
188+
try {
189+
iana_tz_utf8 = to_utf8.to_bytes(iana_tz_utf16);
190+
} catch (std::range_error& ex) {
191+
LogError("Failed to convert IANA time zone to UTF-8: %s", ex.what());
192+
got_time_zone = false;
193+
}
194+
if (got_time_zone) {
195+
return iana_tz_utf8;
196+
}
197+
}
162198
if (!got_time_zone) {
163-
// Return the Windows time zone ID as a backup.
164-
// In this case, we need to get the correct daylight savings time
165-
// setting to get the right name.
199+
// Either the IANA time zone couldn't be determined, or couldn't be
200+
// converted into UTF-8 for some reason (the std::range_error above).
201+
//
202+
// In any case, return the Windows time zone ID as a backup. We now need to
203+
// get the correct daylight saving time setting to get the right name.
204+
//
205+
// Also, note as above that _get_tzname() doesn't return a UTF-8 name,
206+
// rather CP-1252, so convert it to UTF-8 (via UTF-16) before returning.
207+
166208
int daylight = 0; // daylight savings time?
167-
if (_get_daylight(&daylight) != 0) return windows_tz_utf8;
209+
if (_get_daylight(&daylight) != 0) {
210+
// Couldn't determine daylight saving time, return the old name.
211+
return windows_tz_utf8;
212+
}
168213
if (daylight) {
169-
size_t length = 0; // get the needed string length
170-
if (_get_tzname(&length, nullptr, 0, 1) != 0) return windows_tz_utf8;
171-
std::vector<char> namebuf(length);
172-
if (_get_tzname(&length, &namebuf[0], length, 1) != 0)
173-
return windows_tz_utf8;
174-
windows_tz_utf8 = std::string(&namebuf[0]);
214+
windows_tz_utf16 = GetWindowsTimezoneInEnglish(daylight);
215+
{
216+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> to_utf8;
217+
windows_tz_utf8 = to_utf8.to_bytes(windows_tz_utf16);
218+
}
175219
}
176220
return windows_tz_utf8;
177221
}
178222

179-
std::wstring iana_tz_utf16(iana_time_zone_buffer);
180-
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> to_utf8;
181-
std::string iana_tz_utf8 = to_utf8.to_bytes(iana_tz_utf16);
182-
return iana_tz_utf8;
183-
184223
#elif FIREBASE_PLATFORM_LINUX
185224
// Ubuntu: Check /etc/timezone for the full time zone name.
186225
FILE* tz_file = fopen("/etc/timezone", "r");

app/src/locale.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
#include <string>
2222

23+
#include "app/src/include/firebase/internal/platform.h"
24+
2325
namespace firebase {
2426
namespace internal {
2527

app/tests/locale_test.cc

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "app/src/locale.h"
1818

19+
#include <codecvt>
1920
#include <string>
2021

2122
#include "app/src/include/firebase/internal/platform.h"
@@ -24,7 +25,7 @@
2425
#include "gtest/gtest.h"
2526

2627
#if FIREBASE_PLATFORM_WINDOWS
27-
28+
#include <windows.h>
2829
#else
2930
#include <clocale>
3031
#endif // FIREBASE_PLATFORM_WINDOWS
@@ -52,5 +53,23 @@ TEST_F(LocaleTest, TestGetLocale) {
5253
EXPECT_NE(loc.find('_'), std::string::npos);
5354
}
5455

56+
#if FIREBASE_PLATFORM_WINDOWS
57+
58+
TEST_F(LocaleTest, TestTimeZoneNameInEnglish) {
59+
LANGID prev_lang = GetThreadUILanguage();
60+
61+
SetThreadUILanguage(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US));
62+
std::string timezone_english = GetTimezone();
63+
64+
SetThreadUILanguage(MAKELANGID(LANG_GERMAN, SUBLANG_GERMAN));
65+
std::string timezone_german = GetTimezone();
66+
67+
EXPECT_EQ(timezone_english, timezone_german);
68+
69+
SetThreadUILanguage(prev_lang);
70+
}
71+
72+
#endif // FIREBASE_PLATFORM_WINDOWS
73+
5574
} // namespace internal
5675
} // namespace firebase

release_build_files/readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,8 @@ code.
631631
- Changes
632632
- Auth (Android): Fixed an issue where VerifyPhoneNumber's internal
633633
builder failed to create PhoneAuthOptions with certain compiler settings.
634+
- Remote Config (Desktop): Additional fix for handling of non-English time
635+
zone names on Windows.
634636

635637
### 11.2.0
636638
- Changes

remote_config/src/desktop/file_manager.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ RemoteConfigFileManager::RemoteConfigFileManager(const std::string& filename,
4242
AppDataDir(app_data_prefix.c_str(), /*should_create=*/true) + "/" +
4343
filename;
4444
#if FIREBASE_PLATFORM_WINDOWS
45-
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_to_wstring;
45+
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> utf8_to_wstring;
4646
file_path_ = utf8_to_wstring.from_bytes(file_path);
4747
#else
4848
file_path_ = file_path;

0 commit comments

Comments
 (0)