Skip to content

Commit 3dc5467

Browse files
committed
Few performance optimizations
1 parent adcd192 commit 3dc5467

File tree

4 files changed

+121
-116
lines changed

4 files changed

+121
-116
lines changed

lib/iana_file_parser.ex

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ defmodule Tz.IanaFileParser do
146146
end
147147

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

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

@@ -159,11 +160,38 @@ defmodule Tz.IanaFileParser do
159160
name: rule.name,
160161
local_offset_from_std_time: local_offset,
161162
letter: if(rule.letter == "-", do: "", else: rule.letter),
162-
raw: rule
163+
__datetime_data: %{
164+
date: {year, month, parsed_day},
165+
time: {hour, minute, second, time_modifier}
166+
}
163167
}
164168
end
165169
end
166170

171+
def change_rule_year(rule, year, ongoing_switch \\ false)
172+
173+
def change_rule_year(%{to: _} = rule, year, ongoing_switch) do
174+
rule
175+
|> Map.put(:ongoing_switch, ongoing_switch)
176+
|> Map.delete(:to)
177+
|> change_rule_year(year, ongoing_switch)
178+
end
179+
180+
def change_rule_year(%{} = rule, year, ongoing_switch) do
181+
%{
182+
date: {_, month, parsed_day},
183+
time: {hour, minute, second, time_modifier}
184+
} = rule.__datetime_data
185+
186+
{year, month, day} = parsed_day_to_date(year, month, parsed_day)
187+
naive_date_time = new_naive_date_time(year, month, day, hour, minute, second)
188+
189+
%{rule |
190+
from: {naive_date_time, time_modifier},
191+
ongoing_switch: ongoing_switch
192+
}
193+
end
194+
167195
defp new_naive_date_time(year, month, day, 24, minute, second) do
168196
{:ok, naive_date_time} = NaiveDateTime.new(year, month, day, 0, minute, second)
169197
NaiveDateTime.add(naive_date_time, 86400)
@@ -179,30 +207,43 @@ defmodule Tz.IanaFileParser do
179207
naive_date_time
180208
end
181209

182-
defp parse_day_string(year, month, day_string) do
210+
defp parse_day_string(day_string) do
183211
cond do
184212
String.contains?(day_string, "last") ->
185213
"last" <> day_of_week_string = day_string
186214
day_of_week = day_of_week_string_to_integer(day_of_week_string)
187-
day = day_at_last_given_day_of_week_of_month(year, month, day_of_week)
188-
{year, month, day}
215+
{:last_dow, day_of_week}
189216
String.contains?(day_string, "<=") ->
190217
[day_of_week_string, on_or_before_day] = String.split(day_string, "<=", trim: true)
191218
day_of_week = day_of_week_string_to_integer(day_of_week_string)
192219
on_or_before_day = String.to_integer(on_or_before_day)
193-
day_at_given_day_of_week_of_month(year, month, day_of_week, :on_or_before_day, on_or_before_day)
220+
{:dow_equal_or_before_day, day_of_week, on_or_before_day}
194221
String.contains?(day_string, ">=") ->
195222
[day_of_week_string, on_or_after_day] = String.split(day_string, ">=", trim: true)
196223
day_of_week = day_of_week_string_to_integer(day_of_week_string)
197224
on_or_after_day = String.to_integer(on_or_after_day)
198-
day_at_given_day_of_week_of_month(year, month, day_of_week, :on_or_after_day, on_or_after_day)
225+
{:dow_equal_or_after_day, day_of_week, on_or_after_day}
199226
String.match?(day_string, ~r/[0-9]+/) ->
200-
{year, month, String.to_integer(day_string)}
227+
{:day, String.to_integer(day_string)}
201228
true ->
202229
raise "could not parse day from rule (day to parse is \"#{day_string}\")"
203230
end
204231
end
205232

233+
defp parsed_day_to_date(year, month, parsed_day) do
234+
case parsed_day do
235+
{:last_dow, day_of_week} ->
236+
day = day_at_last_given_day_of_week_of_month(year, month, day_of_week)
237+
{year, month, day}
238+
{:dow_equal_or_before_day, day_of_week, on_or_before_day} ->
239+
day_at_given_day_of_week_of_month(year, month, day_of_week, :on_or_before_day, on_or_before_day)
240+
{:dow_equal_or_after_day, day_of_week, on_or_after_day} ->
241+
day_at_given_day_of_week_of_month(year, month, day_of_week, :on_or_after_day, on_or_after_day)
242+
{:day, day} ->
243+
{year, month, day}
244+
end
245+
end
246+
206247
defp parse_to_field_string(:min), do: :min
207248
defp parse_to_field_string(:max), do: :max
208249
defp parse_to_field_string(to_field_string) do
@@ -211,14 +252,16 @@ defmodule Tz.IanaFileParser do
211252
[year, month, day, time] ->
212253
year = String.to_integer(year)
213254
month = month_string_to_integer(month)
214-
{year, month, day} = parse_day_string(year, month, day)
255+
parsed_day = parse_day_string(day)
256+
{year, month, day} = parsed_day_to_date(year, month, parsed_day)
215257

216258
{hour, minute, second, time_modifier} = parse_time_string(time)
217259
{year, month, day, hour, minute, second, time_modifier}
218260
[year, month, day] ->
219261
year = String.to_integer(year)
220262
month = month_string_to_integer(month)
221-
{year, month, day} = parse_day_string(year, month, day)
263+
parsed_day = parse_day_string(day)
264+
{year, month, day} = parsed_day_to_date(year, month, parsed_day)
222265

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

387430
rules =
388-
case length(ongoing_switch_rules) do
389-
0 ->
431+
case ongoing_switch_rules do
432+
[] ->
390433
rules
391-
2 ->
392-
[rule1, rule2] = ongoing_switch_rules
434+
[rule1, rule2] ->
393435
last_year = Enum.max([
394436
build_periods_with_ongoing_dst_changes_until_year,
395437
elem(rule1.from, 0).year,
396438
elem(rule2.from, 0).year
397439
])
398440

399441
Enum.filter(rules, & !&1.ongoing_switch)
400-
++ (rule1.raw
401-
|> Map.put(:to_year, "#{last_year}")
402-
|> transform_rule())
403-
++ (rule1.raw
404-
|> Map.put(:from_year, "#{last_year + 1}")
405-
|> Map.put(:to_year, "max")
406-
|> transform_rule())
407-
++ (rule2.raw
408-
|> Map.put(:to_year, "#{last_year}")
409-
|> transform_rule())
410-
++ (rule2.raw
411-
|> Map.put(:from_year, "#{last_year + 1}")
412-
|> Map.put(:to_year, "max")
413-
|> transform_rule())
442+
++ for year <- Range.new(elem(rule1.from, 0).year, last_year) do
443+
change_rule_year(rule1, year)
444+
end
445+
++ [change_rule_year(rule1, last_year + 1, true)]
446+
++ for year <- Range.new(elem(rule2.from, 0).year, last_year) do
447+
change_rule_year(rule2, year)
448+
end
449+
++ [change_rule_year(rule2, last_year + 1, true)]
414450
_ ->
415451
raise "unexpected number of rules to \"max\", rules: \"#{inspect rules}\""
416452
end

lib/periods_builder.ex

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ defmodule Tz.PeriodsBuilder do
33

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

6-
def build_periods([], _rule_records, _prev_period, periods), do: periods
6+
def build_periods([], _rule_records, _prev_period, periods), do: Enum.reverse(periods)
77

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

1111
new_periods = build_periods_for_zone_line(zone_line, rules, prev_period)
1212

13-
build_periods(rest_zone_lines, rule_records, List.last(new_periods), periods ++ new_periods)
13+
build_periods(rest_zone_lines, rule_records, hd(new_periods), new_periods ++ periods)
1414
end
1515

1616
defp offset_diff_from_prev_period(_zone_line, _local_offset, nil), do: 0
@@ -20,8 +20,8 @@ defmodule Tz.PeriodsBuilder do
2020
total_offset - prev_total_offset
2121
end
2222

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

2727
if offset_diff > 0 do
@@ -40,11 +40,6 @@ defmodule Tz.PeriodsBuilder do
4040
if period.from.utc_gregorian_seconds != prev_period.to.utc_gregorian_seconds do
4141
raise "logic error"
4242
end
43-
44-
%{
45-
from: period.from,
46-
to: prev_period.to
47-
}
4843
end
4944
end
5045
end
@@ -74,9 +69,9 @@ defmodule Tz.PeriodsBuilder do
7469
zone_abbr: zone_abbr(zone_line, offset)
7570
}
7671

77-
maybe_gap_or_overlap_period = maybe_build_gap_or_overlap_period(zone_line, offset, prev_period, period)
72+
maybe_build_gap_period = maybe_build_gap_period(zone_line, offset, prev_period, period)
7873

79-
if maybe_gap_or_overlap_period, do: [maybe_gap_or_overlap_period, period], else: [period]
74+
if maybe_build_gap_period, do: [period, maybe_build_gap_period], else: [period]
8075
end
8176

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

10499
defp filter_rules_for_zone_line(zone_line, rules, prev_period, prev_local_offset_from_std_time, filtered_rules \\ [])
105-
defp filter_rules_for_zone_line(_zone_line, [], _, _, filtered_rules), do: filtered_rules
100+
defp filter_rules_for_zone_line(_zone_line, [], _, _, filtered_rules), do: Enum.reverse(filtered_rules)
106101
defp filter_rules_for_zone_line(zone_line, [rule | rest_rules], prev_period, prev_local_offset_from_std_time, filtered_rules) do
107102
is_rule_included =
108103
cond do
@@ -131,7 +126,7 @@ defmodule Tz.PeriodsBuilder do
131126
end
132127

133128
if is_rule_included do
134-
filter_rules_for_zone_line(zone_line, rest_rules, prev_period, rule.local_offset_from_std_time, filtered_rules ++ [rule])
129+
filter_rules_for_zone_line(zone_line, rest_rules, prev_period, rule.local_offset_from_std_time, [rule | filtered_rules])
135130
else
136131
filter_rules_for_zone_line(zone_line, rest_rules, prev_period, prev_local_offset_from_std_time, filtered_rules)
137132
end
@@ -151,8 +146,8 @@ defmodule Tz.PeriodsBuilder do
151146
last_rule = List.last(rules)
152147

153148
if rule_ends_after_zone_line_range?(zone_line, last_rule) do
154-
rules_without_last = Enum.reverse(rules) |> tl() |> Enum.reverse()
155-
rules_without_last ++ [%{last_rule | to: zone_line.to}]
149+
[%{last_rule | to: zone_line.to} | (Enum.reverse(rules) |> tl())]
150+
|> Enum.reverse()
156151
else
157152
rules
158153
end
@@ -246,17 +241,17 @@ defmodule Tz.PeriodsBuilder do
246241
period =
247242
if period_to == :max do
248243
period
249-
|> Map.put(:raw_rule, rule.raw)
244+
|> Map.put(:rule, rule)
250245
|> Map.put(:zone_line, zone_line)
251246
else
252247
period
253248
end
254249

255-
maybe_gap_or_overlap_period = maybe_build_gap_or_overlap_period(zone_line, rule.local_offset_from_std_time, prev_period, period)
250+
maybe_build_gap_period = maybe_build_gap_period(zone_line, rule.local_offset_from_std_time, prev_period, period)
256251

257-
periods = if maybe_gap_or_overlap_period, do: periods ++ [maybe_gap_or_overlap_period], else: periods
252+
periods = if maybe_build_gap_period, do: [maybe_build_gap_period | periods], else: periods
258253

259-
periods = periods ++ [period]
254+
periods = [period | periods]
260255

261256
do_build_periods_for_zone_line(zone_line, rest_rules, period, periods)
262257
end

0 commit comments

Comments
 (0)