From 904d4c7a7dcf9fc230ab61308673a53440bd7b09 Mon Sep 17 00:00:00 2001 From: Mathieu Decaffmeyer <5883963+mathieuprog@users.noreply.github.com> Date: Mon, 29 Jul 2024 11:51:56 +0800 Subject: [PATCH] Add clock_shift/2 --- README.md | 3 ++- lib/compiler.ex | 20 ++++++++++++++++++-- test/tz_extra_test.exs | 41 +++++++++++++++++++++++++++++++---------- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 42d8dea..0055028 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ * `TzExtra.new_resolved_datetime!/4` * `TzExtra.utc_datetime_range/3` * `TzExtra.round_datetime/3` -* `TzExtra.advances_clock?/1` +* `TzExtra.shifts_clock?/1` +* `TzExtra.clock_shift/2` * `TzExtra.next_period_start_in_year_span/1` ### `TzExtra.countries_time_zones/0` diff --git a/lib/compiler.ex b/lib/compiler.ex index ea00fb9..dd49754 100644 --- a/lib/compiler.ex +++ b/lib/compiler.ex @@ -237,14 +237,15 @@ defmodule TzExtra.Compiler do Enum.map(start_unix_time..end_unix_time//step_in_seconds, &DateTime.from_unix!(&1)) end - def advances_clock?(time_zone_id) when time_zone_id != nil do + def shifts_clock?(time_zone_id) when time_zone_id != nil do case Tz.PeriodsProvider.periods(time_zone_id) do {:error, :time_zone_not_found} -> raise "invalid time zone #{time_zone_id}" {:ok, [{utc_secs, _, _, nil} | _]} -> hardcoded_dst_future_periods? = - DateTime.from_gregorian_seconds(utc_secs).year > Tz.PeriodsProvider.compiled_at().year + 20 + DateTime.from_gregorian_seconds(utc_secs).year > + Tz.PeriodsProvider.compiled_at().year + 20 hardcoded_dst_future_periods? @@ -259,6 +260,21 @@ defmodule TzExtra.Compiler do DateTime.from_gregorian_seconds(from) |> DateTime.shift_zone!(datetime.time_zone, Tz.TimeZoneDatabase) end + + def clock_shift(datetime1, datetime2) do + if DateTime.compare(datetime1, datetime2) == :gt do + raise "first datetime must be earlier than or equal to second datetime" + end + + offset1 = datetime1.utc_offset + datetime1.std_offset + offset2 = datetime2.utc_offset + datetime2.std_offset + + cond do + offset1 < offset2 -> :forward + offset1 > offset2 -> :backward + offset1 == offset2 -> :no_shift + end + end end, for %{code: country_code} <- countries do {:ok, time_zones_for_country} = diff --git a/test/tz_extra_test.exs b/test/tz_extra_test.exs index 8a7fbb0..3c1a04a 100644 --- a/test/tz_extra_test.exs +++ b/test/tz_extra_test.exs @@ -62,22 +62,43 @@ defmodule TzExtraTest do assert TzExtra.iana_version() == Tz.iana_version() end - test "advances_clock?/1" do - assert TzExtra.advances_clock?("Europe/Brussels") - assert TzExtra.advances_clock?("Africa/Casablanca") + test "shifts_clock?/1" do + assert TzExtra.shifts_clock?("Europe/Brussels") + assert TzExtra.shifts_clock?("Africa/Casablanca") - refute TzExtra.advances_clock?("Asia/Manila") - refute TzExtra.advances_clock?("Asia/Tokyo") + refute TzExtra.shifts_clock?("Asia/Manila") + refute TzExtra.shifts_clock?("Asia/Tokyo") end - test "next_period_start_in_year_span/1" do - {:ambiguous, first_dt, second_dt} = DateTime.new(~D[2018-10-28], ~T[02:00:00], "Europe/Copenhagen", Tz.TimeZoneDatabase) - dt = TzExtra.next_period_start_in_year_span(DateTime.add(first_dt, -1, :day, Tz.TimeZoneDatabase)) + test "next_period_start_in_year_span/1 and clock_shift/2" do + {:ambiguous, first_dt, second_dt} = + DateTime.new(~D[2018-10-28], ~T[02:00:00], "Europe/Copenhagen", Tz.TimeZoneDatabase) + + dt = + TzExtra.next_period_start_in_year_span( + DateTime.add(first_dt, -1, :day, Tz.TimeZoneDatabase) + ) + assert DateTime.compare(dt, second_dt) == :eq - {:gap, dt_just_before, dt_just_after} = DateTime.new(~D[2019-03-31], ~T[02:30:00], "Europe/Copenhagen", Tz.TimeZoneDatabase) - dt = TzExtra.next_period_start_in_year_span(DateTime.add(dt_just_before, -1, :day, Tz.TimeZoneDatabase)) + assert TzExtra.clock_shift(first_dt, second_dt) == :backward + assert TzExtra.clock_shift(first_dt, first_dt) == :no_shift + + {:gap, dt_just_before, dt_just_after} = + DateTime.new(~D[2019-03-31], ~T[02:30:00], "Europe/Copenhagen", Tz.TimeZoneDatabase) + + dt = + TzExtra.next_period_start_in_year_span( + DateTime.add(dt_just_before, -1, :day, Tz.TimeZoneDatabase) + ) + assert DateTime.compare(dt, dt_just_after) == :eq + + assert TzExtra.clock_shift(dt_just_before, dt_just_after) == :forward + + assert_raise RuntimeError, fn -> + assert TzExtra.clock_shift(dt_just_after, dt_just_before) == :backward + end end test "time_zone_id_exists?/1" do