Serenity Operating System
at master 232 lines 11 kB view raw
1/* 2 * Copyright (c) 2022, Tim Flynn <trflynn89@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <LibTest/TestCase.h> 8 9#include <AK/StringView.h> 10#include <AK/Time.h> 11#include <LibTimeZone/TimeZone.h> 12 13using enum TimeZone::InDST; 14 15static void test_offset(StringView time_zone, i64 time, i64 expected_offset, TimeZone::InDST expected_in_dst) 16{ 17 auto actual_offset = TimeZone::get_time_zone_offset(time_zone, AK::Time::from_seconds(time)); 18 VERIFY(actual_offset.has_value()); 19 EXPECT_EQ(actual_offset->seconds, expected_offset); 20 EXPECT_EQ(actual_offset->in_dst, expected_in_dst); 21} 22 23#if ENABLE_TIME_ZONE_DATA 24 25# include <LibTimeZone/TimeZoneData.h> 26 27class TimeZoneGuard { 28public: 29 explicit TimeZoneGuard(char const* tz) 30 : m_tz(getenv("TZ")) 31 { 32 setenv("TZ", tz, 1); 33 } 34 35 ~TimeZoneGuard() 36 { 37 if (m_tz) 38 setenv("TZ", m_tz, 1); 39 else 40 unsetenv("TZ"); 41 } 42 43private: 44 char const* m_tz { nullptr }; 45}; 46 47TEST_CASE(time_zone_from_string) 48{ 49 EXPECT_EQ(TimeZone::time_zone_from_string("America/New_York"sv), TimeZone::TimeZone::America_New_York); 50 EXPECT_EQ(TimeZone::time_zone_from_string("Europe/Paris"sv), TimeZone::TimeZone::Europe_Paris); 51 EXPECT_EQ(TimeZone::time_zone_from_string("Etc/GMT+2"sv), TimeZone::TimeZone::Etc_GMT_Ahead_2); 52 EXPECT_EQ(TimeZone::time_zone_from_string("Etc/GMT-5"sv), TimeZone::TimeZone::Etc_GMT_Behind_5); 53 54 EXPECT(!TimeZone::time_zone_from_string("I don't exist"sv).has_value()); 55} 56 57TEST_CASE(time_zone_from_string_link) 58{ 59 auto test_link = [](auto tz1, auto tz2) { 60 auto result1 = TimeZone::time_zone_from_string(tz1); 61 EXPECT(result1.has_value()); 62 63 auto result2 = TimeZone::time_zone_from_string(tz2); 64 EXPECT(result2.has_value()); 65 66 EXPECT_EQ(*result1, *result2); 67 }; 68 69 test_link("America/New_York"sv, "US/Eastern"sv); 70 71 test_link("Etc/GMT"sv, "GMT"sv); 72 test_link("Etc/GMT+0"sv, "GMT"sv); 73 test_link("Etc/GMT-0"sv, "GMT"sv); 74 75 test_link("Etc/UTC"sv, "UTC"sv); 76 test_link("Etc/Universal"sv, "UTC"sv); 77 test_link("Universal"sv, "UTC"sv); 78} 79 80TEST_CASE(case_insensitive_time_zone_from_string) 81{ 82 EXPECT_EQ(TimeZone::time_zone_from_string("UTC"sv), TimeZone::TimeZone::UTC); 83 EXPECT_EQ(TimeZone::time_zone_from_string("utc"sv), TimeZone::TimeZone::UTC); 84 EXPECT_EQ(TimeZone::time_zone_from_string("uTc"sv), TimeZone::TimeZone::UTC); 85} 86 87TEST_CASE(time_zone_to_string) 88{ 89 EXPECT_EQ(TimeZone::time_zone_to_string(TimeZone::TimeZone::America_New_York), "America/New_York"sv); 90 EXPECT_EQ(TimeZone::time_zone_to_string(TimeZone::TimeZone::Europe_Paris), "Europe/Paris"sv); 91 EXPECT_EQ(TimeZone::time_zone_to_string(TimeZone::TimeZone::Etc_GMT_Ahead_2), "Etc/GMT+2"sv); 92 EXPECT_EQ(TimeZone::time_zone_to_string(TimeZone::TimeZone::Etc_GMT_Behind_5), "Etc/GMT-5"sv); 93} 94 95TEST_CASE(time_zone_to_string_link) 96{ 97 EXPECT_EQ(TimeZone::time_zone_to_string(TimeZone::TimeZone::Etc_UTC), "Etc/UTC"sv); 98 EXPECT_EQ(TimeZone::time_zone_to_string(TimeZone::TimeZone::UTC), "Etc/UTC"sv); 99 EXPECT_EQ(TimeZone::time_zone_to_string(TimeZone::TimeZone::Universal), "Etc/UTC"sv); 100 EXPECT_EQ(TimeZone::time_zone_to_string(TimeZone::TimeZone::Etc_Universal), "Etc/UTC"sv); 101} 102 103TEST_CASE(canonicalize_time_zone) 104{ 105 EXPECT_EQ(TimeZone::canonicalize_time_zone("America/New_York"sv), "America/New_York"sv); 106 EXPECT_EQ(TimeZone::canonicalize_time_zone("AmErIcA/NeW_YoRk"sv), "America/New_York"sv); 107 108 EXPECT_EQ(TimeZone::canonicalize_time_zone("UTC"sv), "UTC"sv); 109 EXPECT_EQ(TimeZone::canonicalize_time_zone("GMT"sv), "UTC"sv); 110 EXPECT_EQ(TimeZone::canonicalize_time_zone("GMT+0"sv), "UTC"sv); 111 EXPECT_EQ(TimeZone::canonicalize_time_zone("GMT-0"sv), "UTC"sv); 112 EXPECT_EQ(TimeZone::canonicalize_time_zone("Etc/UTC"sv), "UTC"sv); 113 EXPECT_EQ(TimeZone::canonicalize_time_zone("Etc/GMT"sv), "UTC"sv); 114 115 EXPECT(!TimeZone::canonicalize_time_zone("I don't exist"sv).has_value()); 116} 117 118TEST_CASE(invalid_time_zone) 119{ 120 TimeZoneGuard guard { "ladybird" }; 121 EXPECT_EQ(TimeZone::current_time_zone(), "UTC"sv); 122} 123 124static i64 offset(i64 sign, i64 hours, i64 minutes, i64 seconds) 125{ 126 return sign * ((hours * 3600) + (minutes * 60) + seconds); 127} 128 129TEST_CASE(get_time_zone_offset) 130{ 131 test_offset("America/Chicago"sv, -2717647201, offset(-1, 5, 50, 36), No); // Sunday, November 18, 1883 5:59:59 PM 132 test_offset("America/Chicago"sv, -2717647200, offset(-1, 6, 00, 00), No); // Sunday, November 18, 1883 6:00:00 PM 133 test_offset("America/Chicago"sv, -1067810460, offset(-1, 6, 00, 00), No); // Sunday, March 1, 1936 1:59:00 AM 134 test_offset("America/Chicago"sv, -1067810400, offset(-1, 5, 00, 00), No); // Sunday, March 1, 1936 2:00:00 AM 135 test_offset("America/Chicago"sv, -1045432860, offset(-1, 5, 00, 00), No); // Sunday, November 15, 1936 1:59:00 AM 136 test_offset("America/Chicago"sv, -1045432800, offset(-1, 6, 00, 00), No); // Sunday, November 15, 1936 2:00:00 AM 137 138 test_offset("Europe/London"sv, -3852662401, offset(-1, 0, 01, 15), No); // Tuesday, November 30, 1847 11:59:59 PM 139 test_offset("Europe/London"sv, -3852662400, offset(+1, 0, 00, 00), No); // Wednesday, December 1, 1847 12:00:00 AM 140 test_offset("Europe/London"sv, -37238401, offset(+1, 0, 00, 00), No); // Saturday, October 26, 1968 11:59:59 PM 141 test_offset("Europe/London"sv, -37238400, offset(+1, 1, 00, 00), No); // Sunday, October 27, 1968 12:00:00 AM 142 test_offset("Europe/London"sv, 57722399, offset(+1, 1, 00, 00), No); // Sunday, October 31, 1971 1:59:59 AM 143 test_offset("Europe/London"sv, 57722400, offset(+1, 0, 00, 00), No); // Sunday, October 31, 1971 2:00:00 AM 144 145 test_offset("UTC"sv, -1641846268, offset(+1, 0, 00, 00), No); 146 test_offset("UTC"sv, 0, offset(+1, 0, 00, 00), No); 147 test_offset("UTC"sv, 1641846268, offset(+1, 0, 00, 00), No); 148 149 test_offset("Etc/GMT+4"sv, -1641846268, offset(-1, 4, 00, 00), No); 150 test_offset("Etc/GMT+5"sv, 0, offset(-1, 5, 00, 00), No); 151 test_offset("Etc/GMT+6"sv, 1641846268, offset(-1, 6, 00, 00), No); 152 153 test_offset("Etc/GMT-12"sv, -1641846268, offset(+1, 12, 00, 00), No); 154 test_offset("Etc/GMT-13"sv, 0, offset(+1, 13, 00, 00), No); 155 test_offset("Etc/GMT-14"sv, 1641846268, offset(+1, 14, 00, 00), No); 156 157 EXPECT(!TimeZone::get_time_zone_offset("I don't exist"sv, {}).has_value()); 158} 159 160TEST_CASE(get_time_zone_offset_with_dst) 161{ 162 test_offset("America/New_York"sv, 1642558528, offset(-1, 5, 00, 00), No); // Wednesday, January 19, 2022 2:15:28 AM 163 test_offset("America/New_York"sv, 1663553728, offset(-1, 4, 00, 00), Yes); // Monday, September 19, 2022 2:15:28 AM 164 test_offset("America/New_York"sv, 1671453238, offset(-1, 5, 00, 00), No); // Monday, December 19, 2022 12:33:58 PM 165 166 // Phoenix does not observe DST. 167 test_offset("America/Phoenix"sv, 1642558528, offset(-1, 7, 00, 00), No); // Wednesday, January 19, 2022 2:15:28 AM 168 test_offset("America/Phoenix"sv, 1663553728, offset(-1, 7, 00, 00), No); // Monday, September 19, 2022 2:15:28 AM 169 test_offset("America/Phoenix"sv, 1671453238, offset(-1, 7, 00, 00), No); // Monday, December 19, 2022 12:33:58 PM 170 171 // Moscow's observed DST changed several times in 1919. 172 test_offset("Europe/Moscow"sv, -1609459200, offset(+1, 2, 31, 19), No); // Wednesday, January 1, 1919 12:00:00 AM 173 test_offset("Europe/Moscow"sv, -1596412800, offset(+1, 4, 31, 19), Yes); // Sunday, June 1, 1919 12:00:00 AM 174 test_offset("Europe/Moscow"sv, -1592611200, offset(+1, 4, 00, 00), Yes); // Tuesday, July 15, 1919 12:00:00 AM 175 test_offset("Europe/Moscow"sv, -1589068800, offset(+1, 3, 00, 00), No); // Monday, August 25, 1919 12:00:00 AM 176 177 // Paraguay begins the year in DST. 178 test_offset("America/Asuncion"sv, 1642558528, offset(-1, 3, 00, 00), Yes); // Wednesday, January 19, 2022 2:15:28 AM 179 test_offset("America/Asuncion"sv, 1663553728, offset(-1, 4, 00, 00), No); // Monday, September 19, 2022 2:15:28 AM 180 test_offset("America/Asuncion"sv, 1671453238, offset(-1, 3, 00, 00), Yes); // Monday, December 19, 2022 12:33:58 PM 181} 182 183TEST_CASE(get_named_time_zone_offsets) 184{ 185 auto test_named_offsets = [](auto time_zone, i64 time, i64 expected_standard_offset, i64 expected_daylight_offset, auto expected_standard_name, auto expected_daylight_name) { 186 auto actual_offsets = TimeZone::get_named_time_zone_offsets(time_zone, AK::Time::from_seconds(time)); 187 VERIFY(actual_offsets.has_value()); 188 189 EXPECT_EQ(actual_offsets->at(0).seconds, expected_standard_offset); 190 EXPECT_EQ(actual_offsets->at(1).seconds, expected_daylight_offset); 191 EXPECT_EQ(actual_offsets->at(0).name, expected_standard_name); 192 EXPECT_EQ(actual_offsets->at(1).name, expected_daylight_name); 193 }; 194 195 test_named_offsets("America/New_York"sv, 1642558528, offset(-1, 5, 00, 00), offset(-1, 4, 00, 00), "EST"sv, "EDT"sv); // Wednesday, January 19, 2022 2:15:28 AM 196 test_named_offsets("UTC"sv, 1642558528, offset(+1, 0, 00, 00), offset(+1, 0, 00, 00), "UTC"sv, "UTC"sv); // Wednesday, January 19, 2022 2:15:28 AM 197 test_named_offsets("GMT"sv, 1642558528, offset(+1, 0, 00, 00), offset(+1, 0, 00, 00), "GMT"sv, "GMT"sv); // Wednesday, January 19, 2022 2:15:28 AM 198 199 // Phoenix does not observe DST. 200 test_named_offsets("America/Phoenix"sv, 1642558528, offset(-1, 7, 00, 00), offset(-1, 7, 00, 00), "MST"sv, "MST"sv); // Wednesday, January 19, 2022 2:15:28 AM 201 202 // Moscow's observed DST changed several times in 1919. 203 test_named_offsets("Europe/Moscow"sv, -1609459200, offset(+1, 2, 31, 19), offset(+1, 3, 31, 19), "MSK"sv, "MSD"sv); // Wednesday, January 1, 1919 12:00:00 AM 204 test_named_offsets("Europe/Moscow"sv, -1596412800, offset(+1, 2, 31, 19), offset(+1, 4, 31, 19), "MSK"sv, "MDST"sv); // Sunday, June 1, 1919 12:00:00 AM 205 test_named_offsets("Europe/Moscow"sv, -1589068800, offset(+1, 3, 00, 00), offset(+1, 4, 00, 00), "MSK"sv, "MSD"sv); // Monday, August 25, 1919 12:00:00 AM 206 207 // Shanghai's DST rules end in 1991. 208 test_named_offsets("Asia/Shanghai"sv, 694223999, offset(+1, 8, 00, 00), offset(+1, 9, 00, 00), "CST"sv, "CDT"sv); // Tuesday, December 31, 1991 11:59:59 PM 209 test_named_offsets("Asia/Shanghai"sv, 694224000, offset(+1, 8, 00, 00), offset(+1, 8, 00, 00), "CST"sv, "CST"sv); // Wednesday, January 1, 1992 12:00:00 AM 210} 211 212#else 213 214TEST_CASE(time_zone_from_string) 215{ 216 EXPECT(TimeZone::time_zone_from_string("UTC"sv).has_value()); 217 218 EXPECT(!TimeZone::time_zone_from_string("Europe/Paris"sv).has_value()); 219 EXPECT(!TimeZone::time_zone_from_string("Etc/UTC"sv).has_value()); 220 EXPECT(!TimeZone::time_zone_from_string("I don't exist"sv).has_value()); 221} 222 223TEST_CASE(get_time_zone_offset) 224{ 225 test_offset("UTC"sv, 123456, 0, No); 226 227 EXPECT(!TimeZone::get_time_zone_offset("Europe/Paris"sv, {}).has_value()); 228 EXPECT(!TimeZone::get_time_zone_offset("Etc/UTC"sv, {}).has_value()); 229 EXPECT(!TimeZone::get_time_zone_offset("I don't exist"sv, {}).has_value()); 230} 231 232#endif