Skip to content

Commit

Permalink
Few performance optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieuprog committed Apr 6, 2020
1 parent adcd192 commit 3dc5467
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 116 deletions.
92 changes: 64 additions & 28 deletions lib/iana_file_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ defmodule Tz.IanaFileParser do
end

for year <- Range.new(from_year, to_year) do
{year, month, day} = parse_day_string(year, month, rule.day)
parsed_day = parse_day_string(rule.day)
{year, month, day} = parsed_day_to_date(year, month, parsed_day)

naive_date_time = new_naive_date_time(year, month, day, hour, minute, second)

Expand All @@ -159,11 +160,38 @@ defmodule Tz.IanaFileParser do
name: rule.name,
local_offset_from_std_time: local_offset,
letter: if(rule.letter == "-", do: "", else: rule.letter),
raw: rule
__datetime_data: %{
date: {year, month, parsed_day},
time: {hour, minute, second, time_modifier}
}
}
end
end

def change_rule_year(rule, year, ongoing_switch \\ false)

def change_rule_year(%{to: _} = rule, year, ongoing_switch) do
rule
|> Map.put(:ongoing_switch, ongoing_switch)
|> Map.delete(:to)
|> change_rule_year(year, ongoing_switch)
end

def change_rule_year(%{} = rule, year, ongoing_switch) do
%{
date: {_, month, parsed_day},
time: {hour, minute, second, time_modifier}
} = rule.__datetime_data

{year, month, day} = parsed_day_to_date(year, month, parsed_day)
naive_date_time = new_naive_date_time(year, month, day, hour, minute, second)

%{rule |
from: {naive_date_time, time_modifier},
ongoing_switch: ongoing_switch
}
end

defp new_naive_date_time(year, month, day, 24, minute, second) do
{:ok, naive_date_time} = NaiveDateTime.new(year, month, day, 0, minute, second)
NaiveDateTime.add(naive_date_time, 86400)
Expand All @@ -179,30 +207,43 @@ defmodule Tz.IanaFileParser do
naive_date_time
end

defp parse_day_string(year, month, day_string) do
defp parse_day_string(day_string) do
cond do
String.contains?(day_string, "last") ->
"last" <> day_of_week_string = day_string
day_of_week = day_of_week_string_to_integer(day_of_week_string)
day = day_at_last_given_day_of_week_of_month(year, month, day_of_week)
{year, month, day}
{:last_dow, day_of_week}
String.contains?(day_string, "<=") ->
[day_of_week_string, on_or_before_day] = String.split(day_string, "<=", trim: true)
day_of_week = day_of_week_string_to_integer(day_of_week_string)
on_or_before_day = String.to_integer(on_or_before_day)
day_at_given_day_of_week_of_month(year, month, day_of_week, :on_or_before_day, on_or_before_day)
{:dow_equal_or_before_day, day_of_week, on_or_before_day}
String.contains?(day_string, ">=") ->
[day_of_week_string, on_or_after_day] = String.split(day_string, ">=", trim: true)
day_of_week = day_of_week_string_to_integer(day_of_week_string)
on_or_after_day = String.to_integer(on_or_after_day)
day_at_given_day_of_week_of_month(year, month, day_of_week, :on_or_after_day, on_or_after_day)
{:dow_equal_or_after_day, day_of_week, on_or_after_day}
String.match?(day_string, ~r/[0-9]+/) ->
{year, month, String.to_integer(day_string)}
{:day, String.to_integer(day_string)}
true ->
raise "could not parse day from rule (day to parse is \"#{day_string}\")"
end
end

defp parsed_day_to_date(year, month, parsed_day) do
case parsed_day do
{:last_dow, day_of_week} ->
day = day_at_last_given_day_of_week_of_month(year, month, day_of_week)
{year, month, day}
{:dow_equal_or_before_day, day_of_week, on_or_before_day} ->
day_at_given_day_of_week_of_month(year, month, day_of_week, :on_or_before_day, on_or_before_day)
{:dow_equal_or_after_day, day_of_week, on_or_after_day} ->
day_at_given_day_of_week_of_month(year, month, day_of_week, :on_or_after_day, on_or_after_day)
{:day, day} ->
{year, month, day}
end
end

defp parse_to_field_string(:min), do: :min
defp parse_to_field_string(:max), do: :max
defp parse_to_field_string(to_field_string) do
Expand All @@ -211,14 +252,16 @@ defmodule Tz.IanaFileParser do
[year, month, day, time] ->
year = String.to_integer(year)
month = month_string_to_integer(month)
{year, month, day} = parse_day_string(year, month, day)
parsed_day = parse_day_string(day)
{year, month, day} = parsed_day_to_date(year, month, parsed_day)

{hour, minute, second, time_modifier} = parse_time_string(time)
{year, month, day, hour, minute, second, time_modifier}
[year, month, day] ->
year = String.to_integer(year)
month = month_string_to_integer(month)
{year, month, day} = parse_day_string(year, month, day)
parsed_day = parse_day_string(day)
{year, month, day} = parsed_day_to_date(year, month, parsed_day)

{year, month, day, 0, 0, 0, :wall}
[year, month] ->
Expand Down Expand Up @@ -385,32 +428,25 @@ defmodule Tz.IanaFileParser do
ongoing_switch_rules = Enum.filter(rules, & &1.ongoing_switch)

rules =
case length(ongoing_switch_rules) do
0 ->
case ongoing_switch_rules do
[] ->
rules
2 ->
[rule1, rule2] = ongoing_switch_rules
[rule1, rule2] ->
last_year = Enum.max([
build_periods_with_ongoing_dst_changes_until_year,
elem(rule1.from, 0).year,
elem(rule2.from, 0).year
])

Enum.filter(rules, & !&1.ongoing_switch)
++ (rule1.raw
|> Map.put(:to_year, "#{last_year}")
|> transform_rule())
++ (rule1.raw
|> Map.put(:from_year, "#{last_year + 1}")
|> Map.put(:to_year, "max")
|> transform_rule())
++ (rule2.raw
|> Map.put(:to_year, "#{last_year}")
|> transform_rule())
++ (rule2.raw
|> Map.put(:from_year, "#{last_year + 1}")
|> Map.put(:to_year, "max")
|> transform_rule())
++ for year <- Range.new(elem(rule1.from, 0).year, last_year) do
change_rule_year(rule1, year)
end
++ [change_rule_year(rule1, last_year + 1, true)]
++ for year <- Range.new(elem(rule2.from, 0).year, last_year) do
change_rule_year(rule2, year)
end
++ [change_rule_year(rule2, last_year + 1, true)]
_ ->
raise "unexpected number of rules to \"max\", rules: \"#{inspect rules}\""
end
Expand Down
33 changes: 14 additions & 19 deletions lib/periods_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ defmodule Tz.PeriodsBuilder do

def build_periods(zone_lines, rule_records, prev_period \\ nil, periods \\ [])

def build_periods([], _rule_records, _prev_period, periods), do: periods
def build_periods([], _rule_records, _prev_period, periods), do: Enum.reverse(periods)

def build_periods([zone_line | rest_zone_lines], rule_records, prev_period, periods) do
rules = Map.get(rule_records, zone_line.rules, zone_line.rules)

new_periods = build_periods_for_zone_line(zone_line, rules, prev_period)

build_periods(rest_zone_lines, rule_records, List.last(new_periods), periods ++ new_periods)
build_periods(rest_zone_lines, rule_records, hd(new_periods), new_periods ++ periods)
end

defp offset_diff_from_prev_period(_zone_line, _local_offset, nil), do: 0
Expand All @@ -20,8 +20,8 @@ defmodule Tz.PeriodsBuilder do
total_offset - prev_total_offset
end

defp maybe_build_gap_or_overlap_period(_zone_line, _local_offset, %{to: :max}, _period), do: nil
defp maybe_build_gap_or_overlap_period(zone_line, local_offset, prev_period, period) do
defp maybe_build_gap_period(_zone_line, _local_offset, %{to: :max}, _period), do: nil
defp maybe_build_gap_period(zone_line, local_offset, prev_period, period) do
offset_diff = offset_diff_from_prev_period(zone_line, local_offset, prev_period)

if offset_diff > 0 do
Expand All @@ -40,11 +40,6 @@ defmodule Tz.PeriodsBuilder do
if period.from.utc_gregorian_seconds != prev_period.to.utc_gregorian_seconds do
raise "logic error"
end

%{
from: period.from,
to: prev_period.to
}
end
end
end
Expand Down Expand Up @@ -74,9 +69,9 @@ defmodule Tz.PeriodsBuilder do
zone_abbr: zone_abbr(zone_line, offset)
}

maybe_gap_or_overlap_period = maybe_build_gap_or_overlap_period(zone_line, offset, prev_period, period)
maybe_build_gap_period = maybe_build_gap_period(zone_line, offset, prev_period, period)

if maybe_gap_or_overlap_period, do: [maybe_gap_or_overlap_period, period], else: [period]
if maybe_build_gap_period, do: [period, maybe_build_gap_period], else: [period]
end

defp build_periods_for_zone_line(zone_line, rules, prev_period) when is_list(rules) do
Expand All @@ -102,7 +97,7 @@ defmodule Tz.PeriodsBuilder do
end

defp filter_rules_for_zone_line(zone_line, rules, prev_period, prev_local_offset_from_std_time, filtered_rules \\ [])
defp filter_rules_for_zone_line(_zone_line, [], _, _, filtered_rules), do: filtered_rules
defp filter_rules_for_zone_line(_zone_line, [], _, _, filtered_rules), do: Enum.reverse(filtered_rules)
defp filter_rules_for_zone_line(zone_line, [rule | rest_rules], prev_period, prev_local_offset_from_std_time, filtered_rules) do
is_rule_included =
cond do
Expand Down Expand Up @@ -131,7 +126,7 @@ defmodule Tz.PeriodsBuilder do
end

if is_rule_included do
filter_rules_for_zone_line(zone_line, rest_rules, prev_period, rule.local_offset_from_std_time, filtered_rules ++ [rule])
filter_rules_for_zone_line(zone_line, rest_rules, prev_period, rule.local_offset_from_std_time, [rule | filtered_rules])
else
filter_rules_for_zone_line(zone_line, rest_rules, prev_period, prev_local_offset_from_std_time, filtered_rules)
end
Expand All @@ -151,8 +146,8 @@ defmodule Tz.PeriodsBuilder do
last_rule = List.last(rules)

if rule_ends_after_zone_line_range?(zone_line, last_rule) do
rules_without_last = Enum.reverse(rules) |> tl() |> Enum.reverse()
rules_without_last ++ [%{last_rule | to: zone_line.to}]
[%{last_rule | to: zone_line.to} | (Enum.reverse(rules) |> tl())]
|> Enum.reverse()
else
rules
end
Expand Down Expand Up @@ -246,17 +241,17 @@ defmodule Tz.PeriodsBuilder do
period =
if period_to == :max do
period
|> Map.put(:raw_rule, rule.raw)
|> Map.put(:rule, rule)
|> Map.put(:zone_line, zone_line)
else
period
end

maybe_gap_or_overlap_period = maybe_build_gap_or_overlap_period(zone_line, rule.local_offset_from_std_time, prev_period, period)
maybe_build_gap_period = maybe_build_gap_period(zone_line, rule.local_offset_from_std_time, prev_period, period)

periods = if maybe_gap_or_overlap_period, do: periods ++ [maybe_gap_or_overlap_period], else: periods
periods = if maybe_build_gap_period, do: [maybe_build_gap_period | periods], else: periods

periods = periods ++ [period]
periods = [period | periods]

do_build_periods_for_zone_line(zone_line, rest_rules, period, periods)
end
Expand Down
Loading

0 comments on commit 3dc5467

Please sign in to comment.