summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp58
-rw-r--r--Tests/LibTimeZone/TestTimeZone.cpp38
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getOffsetStringFor.js10
3 files changed, 90 insertions, 16 deletions
diff --git a/Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp b/Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp
index 46efbe016a..64e05485d3 100644
--- a/Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp
+++ b/Meta/Lagom/Tools/CodeGenerators/LibTimeZone/GenerateTimeZoneData.cpp
@@ -450,9 +450,55 @@ static constexpr Array<@type@, @size@> @name@ { {
append_string_conversions("DaylightSavingsRule"sv, "daylight_savings_rule"sv, time_zone_data.dst_offset_names);
generator.append(R"~~~(
+static i64 get_dst_offset(TimeZoneOffset const& time_zone_offset, AK::Time time)
+{
+ auto const& dst_rules = s_dst_offsets[time_zone_offset.dst_rule];
+
+ DaylightSavingsOffset const* standard_offset = nullptr;
+ DaylightSavingsOffset const* daylight_offset = nullptr;
+
+ auto time_in_effect_for_rule = [&](auto const& dst_rule) {
+ auto in_effect = dst_rule.in_effect;
+ in_effect.year = seconds_since_epoch_to_year(time.to_seconds());
+
+ return in_effect.time_since_epoch();
+ };
+
+ auto preferred_rule = [&](auto* current_offset, auto& new_offset) {
+ if (!current_offset)
+ return &new_offset;
+
+ auto new_time_in_effect = time_in_effect_for_rule(new_offset);
+ return (time >= new_time_in_effect) ? &new_offset : current_offset;
+ };
+
+ for (size_t index = 0; (index < dst_rules.size()) && (!standard_offset || !daylight_offset); ++index) {
+ auto const& dst_rule = dst_rules[index];
+
+ auto year_from = AK::Time::from_timestamp(dst_rule.year_from, 1, 1, 0, 0, 0, 0);
+ auto year_to = AK::Time::from_timestamp(dst_rule.year_to + 1, 1, 1, 0, 0, 0, 0);
+ if ((time < year_from) || (time >= year_to))
+ continue;
+
+ if (dst_rule.offset == 0)
+ standard_offset = preferred_rule(standard_offset, dst_rule);
+ else
+ daylight_offset = preferred_rule(daylight_offset, dst_rule);
+ }
+
+ if (!standard_offset || !daylight_offset)
+ return 0;
+
+ auto standard_time_in_effect = time_in_effect_for_rule(*standard_offset);
+ auto daylight_time_in_effect = time_in_effect_for_rule(*daylight_offset);
+
+ if ((time < daylight_time_in_effect) || (time >= standard_time_in_effect))
+ return standard_offset->offset;
+ return daylight_offset->offset;
+}
+
Optional<i64> get_time_zone_offset(TimeZone time_zone, AK::Time time)
{
- // FIXME: This implementation completely ignores DST.
auto const& time_zone_offsets = s_time_zone_offsets[to_underlying(time_zone)];
size_t index = 0;
@@ -464,7 +510,15 @@ Optional<i64> get_time_zone_offset(TimeZone time_zone, AK::Time time)
}
VERIFY(index < time_zone_offsets.size());
- return time_zone_offsets[index].offset;
+ auto const& time_zone_offset = time_zone_offsets[index];
+
+ i64 dst_offset = 0;
+ if (time_zone_offset.dst_rule != -1)
+ dst_offset = get_dst_offset(time_zone_offset, time);
+ else
+ dst_offset = time_zone_offset.dst_offset;
+
+ return time_zone_offset.offset + dst_offset;
}
}
diff --git a/Tests/LibTimeZone/TestTimeZone.cpp b/Tests/LibTimeZone/TestTimeZone.cpp
index 50efd28a25..19cfa5fc42 100644
--- a/Tests/LibTimeZone/TestTimeZone.cpp
+++ b/Tests/LibTimeZone/TestTimeZone.cpp
@@ -85,18 +85,20 @@ TEST_CASE(canonicalize_time_zone)
EXPECT(!TimeZone::canonicalize_time_zone("I don't exist"sv).has_value());
}
-TEST_CASE(get_time_zone_offset)
+static i64 offset(i64 sign, i64 hours, i64 minutes, i64 seconds)
{
- auto offset = [](i64 sign, i64 hours, i64 minutes, i64 seconds) {
- return sign * ((hours * 3600) + (minutes * 60) + seconds);
- };
+ return sign * ((hours * 3600) + (minutes * 60) + seconds);
+}
- auto test_offset = [](auto time_zone, i64 time, i64 expected_offset) {
- auto actual_offset = TimeZone::get_time_zone_offset(time_zone, AK::Time::from_seconds(time));
- VERIFY(actual_offset.has_value());
- EXPECT_EQ(*actual_offset, expected_offset);
- };
+static void test_offset(StringView time_zone, i64 time, i64 expected_offset)
+{
+ auto actual_offset = TimeZone::get_time_zone_offset(time_zone, AK::Time::from_seconds(time));
+ VERIFY(actual_offset.has_value());
+ EXPECT_EQ(*actual_offset, expected_offset);
+}
+TEST_CASE(get_time_zone_offset)
+{
test_offset("America/Chicago"sv, -2717668237, offset(-1, 5, 50, 36)); // Sunday, November 18, 1883 12:09:23 PM
test_offset("America/Chicago"sv, -2717668236, offset(-1, 6, 00, 00)); // Sunday, November 18, 1883 12:09:24 PM
test_offset("America/Chicago"sv, -1067810460, offset(-1, 6, 00, 00)); // Sunday, March 1, 1936 1:59:00 AM
@@ -126,6 +128,24 @@ TEST_CASE(get_time_zone_offset)
EXPECT(!TimeZone::get_time_zone_offset("I don't exist"sv, {}).has_value());
}
+TEST_CASE(get_time_zone_offset_with_dst)
+{
+ test_offset("America/New_York"sv, 1642558528, offset(-1, 5, 00, 00)); // Wednesday, January 19, 2022 2:15:28 AM
+ test_offset("America/New_York"sv, 1663553728, offset(-1, 4, 00, 00)); // Monday, September 19, 2022 2:15:28 AM
+ test_offset("America/New_York"sv, 1671453238, offset(-1, 5, 00, 00)); // Monday, December 19, 2022 12:33:58 PM
+
+ // Phoenix does not observe DST.
+ test_offset("America/Phoenix"sv, 1642558528, offset(-1, 7, 00, 00)); // Wednesday, January 19, 2022 2:15:28 AM
+ test_offset("America/Phoenix"sv, 1663553728, offset(-1, 7, 00, 00)); // Monday, September 19, 2022 2:15:28 AM
+ test_offset("America/Phoenix"sv, 1671453238, offset(-1, 7, 00, 00)); // Monday, December 19, 2022 12:33:58 PM
+
+ // Moscow's observed DST changed several times in 1919.
+ test_offset("Europe/Moscow"sv, -1609459200, offset(+1, 2, 31, 19)); // Wednesday, January 1, 1919 12:00:00 AM
+ test_offset("Europe/Moscow"sv, -1596412800, offset(+1, 4, 31, 19)); // Sunday, June 1, 1919 12:00:00 AM
+ test_offset("Europe/Moscow"sv, -1592611200, offset(+1, 4, 00, 00)); // Tuesday, July 15, 1919 12:00:00 AM
+ test_offset("Europe/Moscow"sv, -1589068800, offset(+1, 3, 00, 00)); // Monday, August 25, 1919 12:00:00 AM
+}
+
#else
TEST_CASE(time_zone_from_string)
diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getOffsetStringFor.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getOffsetStringFor.js
index 5c926b3133..0458c71fcc 100644
--- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getOffsetStringFor.js
+++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getOffsetStringFor.js
@@ -9,15 +9,15 @@ describe("correct behavior", () => {
["GMT", "+00:00"],
["Etc/GMT+12", "-12:00"],
["Etc/GMT-12", "+12:00"],
- ["Europe/London", "+00:00"],
- ["Europe/Berlin", "+01:00"],
- ["America/New_York", "-05:00"],
- ["America/Los_Angeles", "-08:00"],
+ ["Europe/London", "+01:00"],
+ ["Europe/Berlin", "+02:00"],
+ ["America/New_York", "-04:00"],
+ ["America/Los_Angeles", "-07:00"],
["+00:00", "+00:00"],
["+01:30", "+01:30"],
];
for (const [arg, expected] of values) {
- const instant = new Temporal.Instant(1600000000000000000n);
+ const instant = new Temporal.Instant(1600000000000000000n); // Sunday, September 13, 2020 12:26:40 PM
expect(new Temporal.TimeZone(arg).getOffsetStringFor(instant)).toBe(expected);
}
});