TestTimeZone.cpp (8621B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 #include "gtest/gtest.h" 5 6 #include "mozilla/intl/TimeZone.h" 7 #include "mozilla/Maybe.h" 8 #include "mozilla/Span.h" 9 #include "mozilla/TextUtils.h" 10 #include "TestBuffer.h" 11 12 #include <algorithm> 13 #include <string> 14 15 namespace mozilla::intl { 16 17 // Firefox 1.0 release date. 18 static constexpr int64_t RELEASE_DATE = 1'032'800'850'000; 19 20 // Date.UTC(2021, 11-1, 7, 2, 0, 0) 21 static constexpr int64_t DST_CHANGE_DATE = 1'636'250'400'000; 22 23 // These tests are dependent on the machine that this test is being run on. 24 // Unwrap the results to ensure it doesn't fail, but don't check the values. 25 TEST(IntlTimeZone, SystemDependentTests) 26 { 27 // e.g. "America/Chicago" 28 TestBuffer<char16_t> buffer; 29 TimeZone::GetDefaultTimeZone(buffer).unwrap(); 30 } 31 32 TEST(IntlTimeZone, GetRawOffsetMs) 33 { 34 auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan("GMT+3"))).unwrap(); 35 ASSERT_EQ(timeZone->GetRawOffsetMs().unwrap(), 3 * 60 * 60 * 1000); 36 37 timeZone = TimeZone::TryCreate(Some(MakeStringSpan("Etc/GMT+3"))).unwrap(); 38 ASSERT_EQ(timeZone->GetRawOffsetMs().unwrap(), -(3 * 60 * 60 * 1000)); 39 40 timeZone = 41 TimeZone::TryCreate(Some(MakeStringSpan("America/New_York"))).unwrap(); 42 ASSERT_EQ(timeZone->GetRawOffsetMs().unwrap(), -(5 * 60 * 60 * 1000)); 43 } 44 45 TEST(IntlTimeZone, GetDSTOffsetMs) 46 { 47 auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan("GMT+3"))).unwrap(); 48 ASSERT_EQ(timeZone->GetDSTOffsetMs(0).unwrap(), 0); 49 50 timeZone = TimeZone::TryCreate(Some(MakeStringSpan("Etc/GMT+3"))).unwrap(); 51 ASSERT_EQ(timeZone->GetDSTOffsetMs(0).unwrap(), 0); 52 53 timeZone = 54 TimeZone::TryCreate(Some(MakeStringSpan("America/New_York"))).unwrap(); 55 ASSERT_EQ(timeZone->GetDSTOffsetMs(0).unwrap(), 0); 56 ASSERT_EQ(timeZone->GetDSTOffsetMs(RELEASE_DATE).unwrap(), 57 1 * 60 * 60 * 1000); 58 ASSERT_EQ(timeZone->GetDSTOffsetMs(DST_CHANGE_DATE).unwrap(), 59 1 * 60 * 60 * 1000); 60 } 61 62 TEST(IntlTimeZone, GetOffsetMs) 63 { 64 auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan("GMT+3"))).unwrap(); 65 ASSERT_EQ(timeZone->GetOffsetMs(0).unwrap(), 3 * 60 * 60 * 1000); 66 67 timeZone = TimeZone::TryCreate(Some(MakeStringSpan("Etc/GMT+3"))).unwrap(); 68 ASSERT_EQ(timeZone->GetOffsetMs(0).unwrap(), -(3 * 60 * 60 * 1000)); 69 70 timeZone = 71 TimeZone::TryCreate(Some(MakeStringSpan("America/New_York"))).unwrap(); 72 ASSERT_EQ(timeZone->GetOffsetMs(0).unwrap(), -(5 * 60 * 60 * 1000)); 73 ASSERT_EQ(timeZone->GetOffsetMs(RELEASE_DATE).unwrap(), 74 -(4 * 60 * 60 * 1000)); 75 ASSERT_EQ(timeZone->GetOffsetMs(DST_CHANGE_DATE).unwrap(), 76 -(4 * 60 * 60 * 1000)); 77 } 78 79 TEST(IntlTimeZone, GetUTCOffsetMs) 80 { 81 auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan("GMT+3"))).unwrap(); 82 ASSERT_EQ(timeZone->GetUTCOffsetMs(0).unwrap(), 3 * 60 * 60 * 1000); 83 84 timeZone = TimeZone::TryCreate(Some(MakeStringSpan("Etc/GMT+3"))).unwrap(); 85 ASSERT_EQ(timeZone->GetUTCOffsetMs(0).unwrap(), -(3 * 60 * 60 * 1000)); 86 87 timeZone = 88 TimeZone::TryCreate(Some(MakeStringSpan("America/New_York"))).unwrap(); 89 ASSERT_EQ(timeZone->GetUTCOffsetMs(0).unwrap(), -(5 * 60 * 60 * 1000)); 90 ASSERT_EQ(timeZone->GetUTCOffsetMs(RELEASE_DATE).unwrap(), 91 -(4 * 60 * 60 * 1000)); 92 ASSERT_EQ(timeZone->GetUTCOffsetMs(DST_CHANGE_DATE).unwrap(), 93 -(5 * 60 * 60 * 1000)); 94 } 95 96 TEST(IntlTimeZone, GetDisplayName) 97 { 98 using DaylightSavings = TimeZone::DaylightSavings; 99 100 TestBuffer<char16_t> buffer; 101 102 auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan("GMT+3"))).unwrap(); 103 104 buffer.clear(); 105 timeZone->GetDisplayName("en", DaylightSavings::No, buffer).unwrap(); 106 ASSERT_EQ(buffer.get_string_view(), u"GMT+03:00"); 107 108 buffer.clear(); 109 timeZone->GetDisplayName("en", DaylightSavings::Yes, buffer).unwrap(); 110 ASSERT_EQ(buffer.get_string_view(), u"GMT+03:00"); 111 112 timeZone = TimeZone::TryCreate(Some(MakeStringSpan("Etc/GMT+3"))).unwrap(); 113 114 buffer.clear(); 115 timeZone->GetDisplayName("en", DaylightSavings::No, buffer).unwrap(); 116 ASSERT_EQ(buffer.get_string_view(), u"GMT-03:00"); 117 118 buffer.clear(); 119 timeZone->GetDisplayName("en", DaylightSavings::Yes, buffer).unwrap(); 120 ASSERT_EQ(buffer.get_string_view(), u"GMT-03:00"); 121 122 timeZone = 123 TimeZone::TryCreate(Some(MakeStringSpan("America/New_York"))).unwrap(); 124 125 buffer.clear(); 126 timeZone->GetDisplayName("en", DaylightSavings::No, buffer).unwrap(); 127 ASSERT_EQ(buffer.get_string_view(), u"Eastern Standard Time"); 128 129 buffer.clear(); 130 timeZone->GetDisplayName("en", DaylightSavings::Yes, buffer).unwrap(); 131 ASSERT_EQ(buffer.get_string_view(), u"Eastern Daylight Time"); 132 } 133 134 TEST(IntlTimeZone, GetCanonicalTimeZoneID) 135 { 136 TestBuffer<char16_t> buffer; 137 138 // Providing a canonical time zone results in the same string at the end. 139 TimeZone::GetCanonicalTimeZoneID(MakeStringSpan(u"America/Chicago"), buffer) 140 .unwrap(); 141 ASSERT_EQ(buffer.get_string_view(), u"America/Chicago"); 142 143 // Providing an alias will result in the canonical representation. 144 TimeZone::GetCanonicalTimeZoneID(MakeStringSpan(u"Europe/Belfast"), buffer) 145 .unwrap(); 146 ASSERT_EQ(buffer.get_string_view(), u"Europe/London"); 147 148 // An unknown time zone results in an error. 149 ASSERT_TRUE(TimeZone::GetCanonicalTimeZoneID( 150 MakeStringSpan(u"Not a time zone"), buffer) 151 .isErr()); 152 } 153 154 TEST(IntlTimeZone, GetAvailableTimeZones) 155 { 156 constexpr auto EuropeBerlin = MakeStringSpan("Europe/Berlin"); 157 constexpr auto EuropeBusingen = MakeStringSpan("Europe/Busingen"); 158 159 auto timeZones = TimeZone::GetAvailableTimeZones("DE").unwrap(); 160 161 bool hasEuropeBerlin = false; 162 bool hasEuropeBusingen = false; 163 164 for (auto timeZone : timeZones) { 165 auto span = timeZone.unwrap(); 166 if (span == EuropeBerlin) { 167 ASSERT_FALSE(hasEuropeBerlin); 168 hasEuropeBerlin = true; 169 } else if (span == EuropeBusingen) { 170 ASSERT_FALSE(hasEuropeBusingen); 171 hasEuropeBusingen = true; 172 } else { 173 std::string str(span.data(), span.size()); 174 ADD_FAILURE() << "Unexpected time zone: " << str; 175 } 176 } 177 178 ASSERT_TRUE(hasEuropeBerlin); 179 ASSERT_TRUE(hasEuropeBusingen); 180 } 181 182 TEST(IntlTimeZone, GetAvailableTimeZonesNoRegion) 183 { 184 constexpr auto AmericaNewYork = MakeStringSpan("America/New_York"); 185 constexpr auto AsiaTokyo = MakeStringSpan("Asia/Tokyo"); 186 constexpr auto EuropeParis = MakeStringSpan("Europe/Paris"); 187 188 auto timeZones = TimeZone::GetAvailableTimeZones().unwrap(); 189 190 bool hasAmericaNewYork = false; 191 bool hasAsiaTokyo = false; 192 bool hasEuropeParis = false; 193 194 for (auto timeZone : timeZones) { 195 auto span = timeZone.unwrap(); 196 if (span == AmericaNewYork) { 197 ASSERT_FALSE(hasAmericaNewYork); 198 hasAmericaNewYork = true; 199 } else if (span == AsiaTokyo) { 200 ASSERT_FALSE(hasAsiaTokyo); 201 hasAsiaTokyo = true; 202 } else if (span == EuropeParis) { 203 ASSERT_FALSE(hasEuropeParis); 204 hasEuropeParis = true; 205 } 206 } 207 208 ASSERT_TRUE(hasAmericaNewYork); 209 ASSERT_TRUE(hasAsiaTokyo); 210 ASSERT_TRUE(hasEuropeParis); 211 } 212 213 TEST(IntlTimeZone, GetTZDataVersion) 214 { 215 // From <https://data.iana.org/time-zones/tz-link.html#download>: 216 // 217 // "Since 1996, each version has been a four-digit year followed by lower-case 218 // letter (a through z, then za through zz, then zza through zzz, and so on)." 219 // 220 // More than 26 releases are unlikely or at least never happend. 2009 got 221 // quite close with 21 releases, but that was the first time ever with more 222 // than twenty releases in a single year. 223 // 224 // Should this assertion ever fail, because more than 26 releases were issued, 225 // update it accordingly. And in that case we should be extra cautious that 226 // all time zone functionality in Firefox and in external libraries we're 227 // using can cope with more than 26 tzdata releases. 228 // 229 // Also see <https://mm.icann.org/pipermail/tz/2021-September/030621.html>: 230 // 231 // "For Android having 2021a1 and 2021b would be inconvenient. Because there 232 // are hardcoded places which expect that tzdata version is exactly 5 233 // characters." 234 235 auto version = TimeZone::GetTZDataVersion().unwrap(); 236 auto [year, release] = version.SplitAt(4); 237 238 ASSERT_TRUE(std::all_of(year.begin(), year.end(), IsAsciiDigit<char>)); 239 ASSERT_TRUE(IsAsciiAlpha(release[0])); 240 241 // ICU issued a non-standard release "2021a1". 242 ASSERT_TRUE(release.Length() == 1 || release.Length() == 2); 243 244 if (release.Length() == 2) { 245 ASSERT_TRUE(IsAsciiDigit(release[1])); 246 } 247 } 248 249 } // namespace mozilla::intl