Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

httpc #129

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
import Config

config :logger, utc_log: true
config :tzdata, :autoupdate, :enabled
Expand Down
8 changes: 4 additions & 4 deletions lib/tzdata/data_loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule Tzdata.DataLoader do
extract(target_filename, new_dir_name)
release_version = release_version_for_dir(new_dir_name)
Logger.debug("Tzdata data downloaded. Release version #{release_version}.")
{:ok, content_length, release_version, new_dir_name, last_modified}
{:ok, content_length, release_version, new_dir_name, last_modified |> List.to_string()}
end

defp extract(filename, target_dir) do
Expand Down Expand Up @@ -90,14 +90,14 @@ defmodule Tzdata.DataLoader do
end

defp content_length_from_headers(headers) do
case value_from_headers(headers, "Content-Length") do
{:ok, content_length} -> {:ok, content_length |> String.to_integer()}
case value_from_headers(headers, 'content-length') do
{:ok, content_length} -> {:ok, content_length |> List.to_integer()}
{:error, reason} -> {:error, reason}
end
end

defp last_modified_from_headers(headers) do
value_from_headers(headers, "Last-Modified")
value_from_headers(headers, 'last-modified')
end

defp value_from_headers(headers, key) do
Expand Down
59 changes: 59 additions & 0 deletions lib/tzdata/http_client/httpc.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule Tzdata.HttpClient.Httpc do
require Logger

@behaviour Tzdata.HTTPClient

@impl true
def get(url, _headers, _options) when is_binary(url) do
String.to_charlist(url) |> get([], [])
end

def get(url, _headers, _options) when is_list(url) do
request = {url, []}

{:ok, {{_, response, _}, headers, body}} = :httpc.request(:get, request, http_options(), [])

{:ok, {response, headers, :erlang.list_to_binary(body)}}
end

@impl true
def head(url, _headers, _options) when is_binary(url) do
String.to_charlist(url) |> head([],[])
end

def head(url, _headers, _options) when is_list(url) do
request = {url, []}

{:ok, {{_, response, _}, headers, []}} = :httpc.request(:head, request, http_options(), [])

{:ok, {response, headers}}
end

defp http_options() do
[{:ssl, ssl_options()}]
end

defp ssl_options() do
local_storage = CAStore.file_path() |> String.to_charlist()

[{:verify, :verify_peer},
{:cacertfile, local_storage},
{:depth, 2},
{:customize_hostname_check, [
{:match_fun, :public_key.pkix_verify_hostname_match_fun(:https)}
]}
]
end

##
# uses cacert file maintained elsewhere on the system
##
defp custom_cacert(cacert) when is_binary(cacert) do
cacert |> String.to_charlist()
end

defp custom_cacert(cacert) when is_list(cacert) do
cacert
end

end
91 changes: 52 additions & 39 deletions lib/tzdata/period_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,17 @@ defmodule Tzdata.PeriodBuilder do
def h_calc_next_zone_line(_btz_data, period, _, zone_line_tl, _) when zone_line_tl == [] do
case period do
nil -> []
_ -> [ period ]
_ -> [period]
end
end

# If there is a zone line tail, we recursively add to the list of periods with that zone line tail
def h_calc_next_zone_line(btz_data, period, until_utc, zone_line_tl, letter) do
tail = calc_periods(btz_data, zone_line_tl, until_utc, hd(zone_line_tl).rules, letter)

case period do
nil -> tail
_ -> [ period | tail ]
_ -> [period | tail]
end
end

Expand Down Expand Up @@ -194,8 +195,12 @@ defmodule Tzdata.PeriodBuilder do
letter
) do
until_utc = datetime_to_utc(Map.get(zone_line, :until), utc_off, std_off)
tail = calc_periods(btz_data, zone_line_tl, until_utc, Map.get(hd(zone_line_tl), :rules), letter)
if from == until_utc do # empty period may happen when 'until' of zone line coincides with end of rule

tail =
calc_periods(btz_data, zone_line_tl, until_utc, Map.get(hd(zone_line_tl), :rules), letter)

# empty period may happen when 'until' of zone line coincides with end of rule
if from == until_utc do
tail
else
from_standard_time = standard_time_from_utc(from, utc_off)
Expand All @@ -211,7 +216,7 @@ defmodule Tzdata.PeriodBuilder do
zone_abbr: TzUtil.period_abbrevation(zone_line.format, std_off, letter)
}

[ period | tail ]
[period | tail]
end
end

Expand Down Expand Up @@ -286,9 +291,14 @@ defmodule Tzdata.PeriodBuilder do

until_utc = datetime_to_utc(TzUtil.time_for_rule(rule, year), utc_off, std_off)
# truncate end of period to within time range of zone line
until_before_lower_limit = is_integer(lower_limit) && is_integer(until_utc) && lower_limit > until_utc
until_before_lower_limit =
is_integer(lower_limit) && is_integer(until_utc) && lower_limit > until_utc

until_utc = if until_before_lower_limit, do: lower_limit, else: until_utc
last_included_rule = is_integer(upper_limit) && is_integer(until_utc) && upper_limit <= until_utc

last_included_rule =
is_integer(upper_limit) && is_integer(until_utc) && upper_limit <= until_utc

until_utc = if last_included_rule, do: upper_limit, else: until_utc
# derive standard and wall time for 'until'
until_standard_time = standard_time_from_utc(until_utc, utc_off)
Expand All @@ -313,38 +323,41 @@ defmodule Tzdata.PeriodBuilder do

# If we've hit the upper time boundary of this zone line, we do not need to examine any more
# rules for this rule set OR there are no more years to consider for this rule set
if last_included_rule || no_more_years && no_more_rules do
if last_included_rule || (no_more_years && no_more_rules) do
h_calc_next_zone_line(btz_data, period, until_utc, zone_line_tl, letter)
else
tail = cond do
# If there are no more rules for the year, continue with the next year
no_more_rules ->
calc_rule_periods(
btz_data,
[zone_line | zone_line_tl],
until_utc,
utc_off,
rule.save,
years |> tl,
zone_rules,
rule.letter
)
# Else continue with those rules
true ->
calc_periods_for_year(
btz_data,
[zone_line | zone_line_tl],
until_utc,
utc_off,
rule.save,
years,
zone_rules,
rules_tail,
rule.letter,
lower_limit
)
end
if period == nil, do: tail, else: [ period | tail ]
tail =
cond do
# If there are no more rules for the year, continue with the next year
no_more_rules ->
calc_rule_periods(
btz_data,
[zone_line | zone_line_tl],
until_utc,
utc_off,
rule.save,
years |> tl,
zone_rules,
rule.letter
)

# Else continue with those rules
true ->
calc_periods_for_year(
btz_data,
[zone_line | zone_line_tl],
until_utc,
utc_off,
rule.save,
years,
zone_rules,
rules_tail,
rule.letter,
lower_limit
)
end

if period == nil, do: tail, else: [period | tail]
end
end

Expand All @@ -353,9 +366,9 @@ defmodule Tzdata.PeriodBuilder do
def sort_rules_by_time(rules, year) do
# n.b., we can have many rules per month - such as time changes for religious festivals
rules
|> Enum.map(&({&1, TzUtil.tz_day_to_date(year, &1.in, &1.on)}))
|> Enum.map(&{&1, TzUtil.tz_day_to_date(year, &1.in, &1.on)})
|> Enum.sort(&(elem(&1, 1) < elem(&2, 1)))
|> Enum.map(&(elem(&1, 0)))
|> Enum.map(&elem(&1, 0))
end

@doc """
Expand Down
8 changes: 4 additions & 4 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ defmodule Tzdata.Mixfile do

def application do
[
extra_applications: [:logger],
extra_applications: [:logger, :inets, :public_key],
env: env(),
mod: {Tzdata.App, []}
]
end

defp deps do
[
{:hackney, "~> 1.17"},
{:ex_doc, "~> 0.21", only: :dev, runtime: false}
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
{:castore, "~> 0.1.17"}
]
end

Expand All @@ -44,7 +44,7 @@ defmodule Tzdata.Mixfile do
[
autoupdate: :enabled,
data_dir: nil,
http_client: Tzdata.HTTPClient.Hackney
http_client: Tzdata.HTTPClient.Httpc
]
end

Expand Down
20 changes: 7 additions & 13 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
%{
"certifi": {:hex, :certifi, "2.5.3", "70bdd7e7188c804f3a30ee0e7c99655bc35d8ac41c23e12325f36ab449b70651", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "ed516acb3929b101208a9d700062d520f3953da3b6b918d866106ffa980e1c10"},
"earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"},
"ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"},
"hackney": {:hex, :hackney, "1.17.0", "717ea195fd2f898d9fe9f1ce0afcc2621a41ecfe137fae57e7fe6e9484b9aa99", [:rebar3], [{:certifi, "~>2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "64c22225f1ea8855f584720c0e5b3cd14095703af1c9fbc845ba042811dc671c"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"castore": {:hex, :castore, "0.1.17", "ba672681de4e51ed8ec1f74ed624d104c0db72742ea1a5e74edbc770c815182f", [:mix], [], "hexpm", "d9844227ed52d26e7519224525cb6868650c272d4a3d327ce3ca5570c12163f9"},
"earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"},
"ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"},
}
66 changes: 40 additions & 26 deletions test/tz_period_builder_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -33,37 +33,48 @@ defmodule Tzdata.PeriodBuilderTest do

def convert(utc) do
case utc do
atom when is_atom(utc) -> atom
atom when is_atom(utc) ->
atom

utc ->
:calendar.gregorian_seconds_to_datetime(utc)
|> NaiveDateTime.from_erl!()
end
end

def test_for_overlaps(map, location) do
result = calc_periods(map, location)
|> Enum.reduce_while(nil, fn period, last ->
%{from: %{utc: from_utc}, until: %{utc: until_utc}, zone_abbr: zone_abbr} = period
# preconditions
assert from_utc != :max # period can't start at :max
assert until_utc != :min # period can't finish at :min
assert from_utc == :min || until_utc == :max || from_utc < until_utc,
"#{location}: #{convert(from_utc)}UTC >= #{convert(until_utc)}UTC" # 'from' must precede 'until' time
case last do
nil -> {:cont, {:ok, period}}
{:ok, last} ->
# check if this period overlaps with prior period
if last.until.utc != from_utc do
{:halt, {:error,
"Location #{location}: #{convert(last.from.utc)}UTC..#{convert(last.until.utc)}UTC #{last.zone_abbr}"
<> "... is non-sequential with ..."
<> "#{convert(from_utc)}UTC..#{convert(until_utc)}UTC #{zone_abbr}"
}}
else
result =
calc_periods(map, location)
|> Enum.reduce_while(nil, fn period, last ->
%{from: %{utc: from_utc}, until: %{utc: until_utc}, zone_abbr: zone_abbr} = period
# preconditions
# period can't start at :max
assert from_utc != :max
# period can't finish at :min
assert until_utc != :min

assert from_utc == :min || until_utc == :max || from_utc < until_utc,
# 'from' must precede 'until' time
"#{location}: #{convert(from_utc)}UTC >= #{convert(until_utc)}UTC"

case last do
nil ->
{:cont, {:ok, period}}
end
end
end)

{:ok, last} ->
# check if this period overlaps with prior period
if last.until.utc != from_utc do
{:halt,
{:error,
"Location #{location}: #{convert(last.from.utc)}UTC..#{convert(last.until.utc)}UTC #{last.zone_abbr}" <>
"... is non-sequential with ..." <>
"#{convert(from_utc)}UTC..#{convert(until_utc)}UTC #{zone_abbr}"}}
else
{:cont, {:ok, period}}
end
end
end)

assert {:ok, _last} = result
end

Expand All @@ -72,7 +83,10 @@ defmodule Tzdata.PeriodBuilderTest do
{:ok, map} = Tzdata.BasicDataMap.from_single_file_in_dir(@fixtures_dir, "rule_overlap")
{:ok, %{map: map}}
end
test "will handle coincidence of a rule time change with a subsequent time change", %{map: map} do

test "will handle coincidence of a rule time change with a subsequent time change", %{
map: map
} do
[
"America/Whitehorse",
"America/Santiago",
Expand All @@ -83,13 +97,13 @@ defmodule Tzdata.PeriodBuilderTest do
"Africa/Cairo",
"America/Argentina/Buenos_Aires"
]
|> Enum.each(&(test_for_overlaps(map, &1)))
|> Enum.each(&test_for_overlaps(map, &1))
end
end

test "source data has no time period overlaps", %{map: map} do
map.zone_list
|> Enum.each(&(test_for_overlaps(map, &1)))
|> Enum.each(&test_for_overlaps(map, &1))
end

test "can calculate for zones with one line", %{map: map} do
Expand Down