From 47b39f3f8e99f17d864bd60c1970cea2cbe685ed Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sun, 14 Jun 2026 19:21:50 -0400 Subject: [PATCH 1/4] allow for a configurable time zone database --- lib/open_hours/offset.ex | 8 ++++---- lib/open_hours/schedule.ex | 2 +- lib/open_hours/time_slot.ex | 12 ++++++------ lib/open_hours/time_zone_database.ex | 19 +++++++++++++++++++ mix.exs | 5 +++-- mix.lock | 1 + 6 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 lib/open_hours/time_zone_database.ex diff --git a/lib/open_hours/offset.ex b/lib/open_hours/offset.ex index 3736ef8..9f1892f 100644 --- a/lib/open_hours/offset.ex +++ b/lib/open_hours/offset.ex @@ -98,7 +98,7 @@ defmodule OpenHours.Offset do duration ) when schedule_tz != dt_tz do - {:ok, shifted} = DateTime.shift_zone(dt, schedule_tz, Tzdata.TimeZoneDatabase) + {:ok, shifted} = DateTime.shift_zone(dt, schedule_tz, OpenHours.TimeZoneDatabase.database()) shift(schedule, shifted, duration) end @@ -142,7 +142,7 @@ defmodule OpenHours.Offset do available = DateTime.diff(slot.ends_at, position) if available >= remaining_seconds do - DateTime.add(position, remaining_seconds, :second, Tzdata.TimeZoneDatabase) + DateTime.add(position, remaining_seconds, :second, OpenHours.TimeZoneDatabase.database()) else shift_forward(schedule, slot.ends_at, remaining_seconds - available) end @@ -160,7 +160,7 @@ defmodule OpenHours.Offset do available = DateTime.diff(position, slot.starts_at) if available >= remaining_seconds do - DateTime.add(position, -remaining_seconds, :second, Tzdata.TimeZoneDatabase) + DateTime.add(position, -remaining_seconds, :second, OpenHours.TimeZoneDatabase.database()) else shift_backward(schedule, slot.starts_at, remaining_seconds - available) end @@ -206,7 +206,7 @@ defmodule OpenHours.Offset do end defp snap_to_business_time(schedule, date, time, direction) do - dt = DateTime.new!(date, time, schedule.time_zone, Tzdata.TimeZoneDatabase) + dt = DateTime.new!(date, time, schedule.time_zone, OpenHours.TimeZoneDatabase.database()) if Schedule.in_hours?(schedule, dt) do dt diff --git a/lib/open_hours/schedule.ex b/lib/open_hours/schedule.ex index b9c9603..f64d375 100644 --- a/lib/open_hours/schedule.ex +++ b/lib/open_hours/schedule.ex @@ -94,7 +94,7 @@ defmodule OpenHours.Schedule do @spec in_hours?(OpenHours.Schedule.t(), DateTime.t()) :: boolean() def in_hours?(%Schedule{time_zone: schedule_tz} = schedule, %DateTime{time_zone: date_tz} = at) when schedule_tz != date_tz do - {:ok, shifted_at} = DateTime.shift_zone(at, schedule_tz, Tzdata.TimeZoneDatabase) + {:ok, shifted_at} = DateTime.shift_zone(at, schedule_tz, OpenHours.TimeZoneDatabase.database()) in_hours?(schedule, shifted_at) end diff --git a/lib/open_hours/time_slot.ex b/lib/open_hours/time_slot.ex index 8a87071..64b754d 100644 --- a/lib/open_hours/time_slot.ex +++ b/lib/open_hours/time_slot.ex @@ -25,7 +25,7 @@ defmodule OpenHours.TimeSlot do %DateTime{} = ends_at ) when schedule_tz != start_tz do - {:ok, shifted_starts_at} = DateTime.shift_zone(starts_at, schedule_tz, Tzdata.TimeZoneDatabase) + {:ok, shifted_starts_at} = DateTime.shift_zone(starts_at, schedule_tz, OpenHours.TimeZoneDatabase.database()) between(schedule, shifted_starts_at, ends_at) end @@ -35,7 +35,7 @@ defmodule OpenHours.TimeSlot do %DateTime{time_zone: end_tz} = ends_at ) when schedule_tz != end_tz do - {:ok, shifted_ends_at} = DateTime.shift_zone(ends_at, schedule_tz, Tzdata.TimeZoneDatabase) + {:ok, shifted_ends_at} = DateTime.shift_zone(ends_at, schedule_tz, OpenHours.TimeZoneDatabase.database()) between(schedule, starts_at, shifted_ends_at) end @@ -110,7 +110,7 @@ defmodule OpenHours.TimeSlot do def stream_next(%Schedule{time_zone: schedule_tz} = schedule, %DateTime{time_zone: dt_tz} = at, opts) when schedule_tz != dt_tz do - {:ok, shifted} = DateTime.shift_zone(at, schedule_tz, Tzdata.TimeZoneDatabase) + {:ok, shifted} = DateTime.shift_zone(at, schedule_tz, OpenHours.TimeZoneDatabase.database()) stream_next(schedule, shifted, opts) end @@ -158,7 +158,7 @@ defmodule OpenHours.TimeSlot do def stream_previous(%Schedule{time_zone: schedule_tz} = schedule, %DateTime{time_zone: dt_tz} = at, opts) when schedule_tz != dt_tz do - {:ok, shifted} = DateTime.shift_zone(at, schedule_tz, Tzdata.TimeZoneDatabase) + {:ok, shifted} = DateTime.shift_zone(at, schedule_tz, OpenHours.TimeZoneDatabase.database()) stream_previous(schedule, shifted, opts) end @@ -210,8 +210,8 @@ defmodule OpenHours.TimeSlot do |> get_intervals_for(day) |> Enum.map(fn {interval_start, interval_end} -> %TimeSlot{ - starts_at: DateTime.new!(day, interval_start, schedule.time_zone, Tzdata.TimeZoneDatabase), - ends_at: DateTime.new!(day, interval_end, schedule.time_zone, Tzdata.TimeZoneDatabase) + starts_at: DateTime.new!(day, interval_start, schedule.time_zone, OpenHours.TimeZoneDatabase.database()), + ends_at: DateTime.new!(day, interval_end, schedule.time_zone, OpenHours.TimeZoneDatabase.database()) } end) end diff --git a/lib/open_hours/time_zone_database.ex b/lib/open_hours/time_zone_database.ex new file mode 100644 index 0000000..272db81 --- /dev/null +++ b/lib/open_hours/time_zone_database.ex @@ -0,0 +1,19 @@ +defmodule OpenHours.TimeZoneDatabase do + @moduledoc """ + Resolves the configured `Calendar.TimeZoneDatabase` implementation. + + By default, `Tzdata.TimeZoneDatabase` is used. You can override this by setting: + + config :open_hours, :time_zone_database, Tz.TimeZoneDatabase + + Any module that implements the `Calendar.TimeZoneDatabase` behaviour can be used. + """ + + @doc """ + Returns the configured time zone database module. + """ + @spec database() :: module() + def database do + Application.get_env(:open_hours, :time_zone_database, Tzdata.TimeZoneDatabase) + end +end diff --git a/mix.exs b/mix.exs index c380bb4..0cccb1d 100644 --- a/mix.exs +++ b/mix.exs @@ -19,14 +19,15 @@ defmodule OpenHours.MixProject do # Run "mix help compile.app" to learn about applications. def application do [ - extra_applications: [:logger, :tzdata] + extra_applications: [:logger] ] end # Run "mix help deps" to learn about dependencies. defp deps do [ - {:tzdata, "~> 1.1"}, + {:tzdata, "~> 1.1", optional: true}, + {:tz, "~> 0.28", optional: true}, {:ex_doc, "~> 0.31", only: :dev, runtime: false} ] end diff --git a/mix.lock b/mix.lock index 066b733..7d442ce 100644 --- a/mix.lock +++ b/mix.lock @@ -14,6 +14,7 @@ "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "rangex": {:git, "https://github.com/openapi-ro/rangex.git", "a92b03f8f075d73fef1bbbf4b1c088feaff8724b", [tag: "master"]}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "tz": {:hex, :tz, "0.28.2", "6c47f3d1a8ee5c33a1d8f0ba49e5a851b0a30c408a587d907aff6e71228b3b32", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:mint, "~> 1.6", [hex: :mint, repo: "hexpm", optional: true]}], "hexpm", "a6bf7355a33f0a7511602ab4566432ac0901d8abff81e94e455bca19708a0c87"}, "tzdata": {:hex, :tzdata, "1.1.3", "b1cef7bb6de1de90d4ddc25d33892b32830f907e7fc2fccd1e7e22778ab7dfbc", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "d4ca85575a064d29d4e94253ee95912edfb165938743dbf002acdf0dcecb0c28"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } From 5797620c8fadbe6340c984d9185f4e9860a23016 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Sun, 14 Jun 2026 19:43:48 -0400 Subject: [PATCH 2/4] default to the Elixir one if one is not set --- lib/open_hours/time_zone_database.ex | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/open_hours/time_zone_database.ex b/lib/open_hours/time_zone_database.ex index 272db81..133daaa 100644 --- a/lib/open_hours/time_zone_database.ex +++ b/lib/open_hours/time_zone_database.ex @@ -2,7 +2,9 @@ defmodule OpenHours.TimeZoneDatabase do @moduledoc """ Resolves the configured `Calendar.TimeZoneDatabase` implementation. - By default, `Tzdata.TimeZoneDatabase` is used. You can override this by setting: + By default, the Elixir global time zone database is used (set via + `Calendar.put_time_zone_database/1`). You can override this for OpenHours + specifically by setting: config :open_hours, :time_zone_database, Tz.TimeZoneDatabase @@ -14,6 +16,6 @@ defmodule OpenHours.TimeZoneDatabase do """ @spec database() :: module() def database do - Application.get_env(:open_hours, :time_zone_database, Tzdata.TimeZoneDatabase) + Application.get_env(:open_hours, :time_zone_database, Calendar.get_time_zone_database()) end end From 47ea0a4b9a84a3fa7eab3672372f91eedb3ae98f Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Mon, 15 Jun 2026 18:41:49 -0400 Subject: [PATCH 3/4] changelog and version notes --- CHANGELOG.md | 4 ++++ README.md | 5 ++++- mix.exs | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85de142..737044d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [0.4.0](https://github.com/hopsor/open_hours/compare/v0.2.0...v0.4.0) (TBD) + +* Potentialy breaking chaneg: Make the timezone database configurable ([#41](https://github.com/hopsor/open_hours/pull/41)) + ## [0.3.0](https://github.com/hopsor/open_hours/compare/v0.2.0...v0.3.0) (2026-03-27) diff --git a/README.md b/README.md index d7e4d36..d9f7944 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,14 @@ The package can be installed by adding `open_hours` to your list of dependencies ```elixir def deps do [ - {:open_hours, "~> 0.2.0"} + {:open_hours, "~> 0.4.0"} ] end ``` +If you do not already have a timezone database (eg tz or tzdata) configured for your Elixir application +you will also need to do that. We recommend [TZ](https://tz.hexdocs.pm/readme.html) + ## Usage In order to use OpenHours functions you first need a `Schedule` config: diff --git a/mix.exs b/mix.exs index 0cccb1d..e61e95a 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule OpenHours.MixProject do use Mix.Project - @version "0.3.0" + @version "0.4.0" def project do [ From 2ab12da478136077a622e8de0322a0cc1a51cc69 Mon Sep 17 00:00:00 2001 From: Ben Wilson Date: Wed, 17 Jun 2026 14:00:59 -0400 Subject: [PATCH 4/4] set a timezone database --- test/test_helper.exs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..b9497e1 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,3 @@ +Application.put_env(:elixir, :time_zone_database, Tz.TimeZoneDatabase) + ExUnit.start()