Skip to content

Commit

Permalink
Add support for returning metadata about time zones
Browse files Browse the repository at this point in the history
If a system's time zone database has limited information, this makes it
easier to debug.
  • Loading branch information
fhunleth committed Mar 11, 2021
1 parent 553677e commit 8d28469
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 0 deletions.
13 changes: 13 additions & 0 deletions lib/zoneinfo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ defmodule Zoneinfo do
end
end

@doc """
Return Zoneinfo metadata on a time zone
The returned metadata is limited to what's available in the source TZif data
file for the time zone. It's mostly useful for verifying that time zone
information is available for dates used in your application. Note that proper
time zone calculations depend on many things and it's possible that they'll
work outside of the returned ranged. However, it's also possible that a time
zone database was built and then a law changed which invalidates a record.
"""
@spec get_metadata(String.t()) :: {:ok, Zoneinfo.Meta.t()} | {:error, atom()}
defdelegate get_metadata(time_zone), to: Zoneinfo.Cache, as: :meta

defp contains_tzif?(path) do
case File.open(path, [:read], &contains_tzif_helper/1) do
{:ok, result} -> result
Expand Down
10 changes: 10 additions & 0 deletions lib/zoneinfo/cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ defmodule Zoneinfo.Cache do
load_time_zone(time_zone)
end

@doc """
Return Zoneinfo metadata on a time zone
"""
@spec meta(String.t()) :: {:ok, Zoneinfo.Meta.t()} | {:error, atom()}
def meta(time_zone) do
with {:ok, tzif} <- get(time_zone) do
{:ok, Zoneinfo.Meta.to_meta(time_zone, tzif)}
end
end

@impl GenServer
def init(_args) do
@table = :ets.new(@table, [:set, :protected, :named_table])
Expand Down
46 changes: 46 additions & 0 deletions lib/zoneinfo/meta.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
defmodule Zoneinfo.Meta do
alias Zoneinfo.TZif

@moduledoc """
Metadata derived from TZif information
The metadata here is mostly useful for checking the quality of the TZif files that
were loaded.
"""
defstruct [:time_zone, :tz_string, :earliest_record_utc, :latest_record_utc, :record_count]

@typedoc """
Zoneinfo.Meta contains information about one time zone
* `:time_zone` - the name of the time zone
* `:tz_string` - if a POSIX TZ string is available, this is it
* `:earliest_record_utc` - the UTC time of the earliest time zone record
* `:latest_record_utc` - the UTC time of the latest time zone record
* `:record_count` -- the number of records
"""
@type t() :: %__MODULE__{
time_zone: String.t(),
tz_string: String.t() | nil,
earliest_record_utc: NaiveDateTime.t(),
latest_record_utc: NaiveDateTime.t(),
record_count: non_neg_integer()
}

@doc false
@spec to_meta(String.t(), TZif.t()) :: t()
def to_meta(time_zone, tzif) do
%__MODULE__{
time_zone: time_zone,
tz_string: tzif.tz_string,
earliest_record_utc: ndt(Enum.at(tzif.periods, -2)),
latest_record_utc: ndt(List.first(tzif.periods)),
# The last record is the default for times before the first known one, so
# it doesn't really count
record_count: length(tzif.periods)
}
end

defp ndt({gregorian_seconds, _utc_offset, _std_offset, _zone_abbr}) do
Zoneinfo.Utils.gregorian_seconds_to_naive_datetime(gregorian_seconds)
end
end
25 changes: 25 additions & 0 deletions test/zoneinfo/meta_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Zoneinfo.MetaTest do
use ExUnit.Case

alias Zoneinfo.Meta

@fixture_path Path.join(__DIR__, "../fixture")

defp parse_file(name) do
Path.join(@fixture_path, name)
|> File.read!()
|> Zoneinfo.TZif.parse()
end

test "to_meta/2" do
{:ok, tzif} = parse_file("Honolulu_v2")
meta = Meta.to_meta("America/Honolulu", tzif)

assert meta.time_zone == "America/Honolulu"
assert meta.tz_string == "HST10"
assert meta.earliest_record_utc == ~N[1896-01-13 22:31:26]
# Yes, this looks strange. The file was shortened on purpose
assert meta.latest_record_utc == ~N[1947-06-08 12:30:00]
assert meta.record_count == 8
end
end

0 comments on commit 8d28469

Please sign in to comment.