Skip to content

Commit 04c45b0

Browse files
josevalimJosé Valim
authored andcommitted
Use quote fragments to remove duplication and optimize calendar parsing (#7951)
Signed-off-by: José Valim <[email protected]>
1 parent d410466 commit 04c45b0

File tree

5 files changed

+100
-67
lines changed

5 files changed

+100
-67
lines changed

lib/elixir/lib/calendar/date.ex

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,13 @@ defmodule Date do
263263
@spec from_iso8601(String.t(), Calendar.calendar()) :: {:ok, t} | {:error, atom}
264264
def from_iso8601(string, calendar \\ Calendar.ISO)
265265

266-
def from_iso8601(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes>>, calendar) do
267-
with {year, ""} <- Integer.parse(year),
268-
{month, ""} <- Integer.parse(month),
269-
{day, ""} <- Integer.parse(day) do
270-
with {:ok, date} <- new(year, month, day, Calendar.ISO), do: convert(date, calendar)
271-
else
272-
_ -> {:error, :invalid_format}
266+
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
267+
268+
def from_iso8601(unquote(match_date), calendar) when unquote(guard_date) do
269+
{year, month, day} = unquote(read_date)
270+
271+
with {:ok, date} <- new(year, month, day, Calendar.ISO) do
272+
convert(date, calendar)
273273
end
274274
end
275275

lib/elixir/lib/calendar/datetime.ex

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -505,48 +505,48 @@ defmodule DateTime do
505505
@doc since: "1.4.0"
506506
@spec from_iso8601(String.t(), Calendar.calendar()) ::
507507
{:ok, t, Calendar.utc_offset()} | {:error, atom}
508+
509+
@sep [?\s, ?T]
510+
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
511+
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
512+
508513
def from_iso8601(string, calendar \\ Calendar.ISO) when is_binary(string) do
509-
with <<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, rest::binary>> <- string,
510-
true <- sep in [?\s, ?T],
511-
<<hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>> <- rest,
512-
{year, ""} <- Integer.parse(year),
513-
{month, ""} <- Integer.parse(month),
514-
{day, ""} <- Integer.parse(day),
515-
{hour, ""} <- Integer.parse(hour),
516-
{minute, ""} <- Integer.parse(min),
517-
{second, ""} <- Integer.parse(sec),
514+
with <<unquote(match_date), sep, unquote(match_time), rest::binary>>
515+
when unquote(guard_date) and sep in @sep and unquote(guard_time) <- string,
518516
{microsecond, rest} <- Calendar.ISO.parse_microsecond(rest),
519-
{:ok, date} <- Date.new(year, month, day),
520-
{:ok, time} <- Time.new(hour, minute, second, microsecond),
521-
{:ok, offset} <- parse_offset(rest) do
522-
%{year: year, month: month, day: day} = date
523-
%{hour: hour, minute: minute, second: second, microsecond: microsecond} = time
524-
{_, precision} = microsecond
525-
526-
datetime =
527-
Calendar.ISO.naive_datetime_to_iso_days(
528-
year,
529-
month,
530-
day,
531-
hour,
532-
minute,
533-
second,
534-
microsecond
535-
)
536-
|> apply_tz_offset(offset)
537-
|> from_iso_days("Etc/UTC", "UTC", 0, 0, calendar, precision)
538-
539-
{:ok, %{datetime | microsecond: microsecond}, offset}
517+
{offset, ""} <- Calendar.ISO.parse_offset(rest) do
518+
{year, month, day} = unquote(read_date)
519+
{hour, minute, second} = unquote(read_time)
520+
521+
cond do
522+
not calendar.valid_date?(year, month, day) ->
523+
{:error, :invalid_date}
524+
525+
not calendar.valid_time?(hour, minute, second, microsecond) ->
526+
{:error, :invalid_time}
527+
528+
is_nil(offset) ->
529+
{:error, :missing_offset}
530+
531+
true ->
532+
{_, precision} = microsecond
533+
534+
datetime =
535+
Calendar.ISO.naive_datetime_to_iso_days(
536+
year,
537+
month,
538+
day,
539+
hour,
540+
minute,
541+
second,
542+
microsecond
543+
)
544+
|> apply_tz_offset(offset)
545+
|> from_iso_days("Etc/UTC", "UTC", 0, 0, calendar, precision)
546+
547+
{:ok, %{datetime | microsecond: microsecond}, offset}
548+
end
540549
else
541-
{:error, reason} -> {:error, reason}
542-
_ -> {:error, :invalid_format}
543-
end
544-
end
545-
546-
defp parse_offset(rest) do
547-
case Calendar.ISO.parse_offset(rest) do
548-
{offset, ""} when is_integer(offset) -> {:ok, offset}
549-
{nil, ""} -> {:error, :missing_offset}
550550
_ -> {:error, :invalid_format}
551551
end
552552
end

lib/elixir/lib/calendar/iso.ex

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,39 @@ defmodule Calendar.ISO do
3636

3737
@months_in_year 12
3838

39+
@doc false
40+
def __match_date__ do
41+
quote do
42+
[
43+
<<y1, y2, y3, y4, ?-, m1, m2, ?-, d1, d2>>,
44+
y1 >= ?0 and y1 <= ?9 and y2 >= ?0 and y2 <= ?9 and y3 >= ?0 and y3 <= ?9 and y4 >= ?0 and
45+
y4 <= ?9 and m1 >= ?0 and m1 <= ?9 and m2 >= ?0 and m2 <= ?9 and d1 >= ?0 and d1 <= ?9 and
46+
d2 >= ?0 and d2 <= ?9,
47+
{
48+
(y1 - ?0) * 1000 + (y2 - ?0) * 100 + (y3 - ?0) * 10 + (y4 - ?0),
49+
(m1 - ?0) * 10 + (m2 - ?0),
50+
(d1 - ?0) * 10 + (d2 - ?0)
51+
}
52+
]
53+
end
54+
end
55+
56+
@doc false
57+
def __match_time__ do
58+
quote do
59+
[
60+
<<h1, h2, ?:, i1, i2, ?:, s1, s2>>,
61+
h1 >= ?0 and h1 <= ?9 and h2 >= ?0 and h2 <= ?9 and i1 >= ?0 and i1 <= ?9 and i2 >= ?0 and
62+
i2 <= ?9 and s1 >= ?0 and s1 <= ?9 and s2 >= ?0 and s2 <= ?9,
63+
{
64+
(h1 - ?0) * 10 + (h2 - ?0),
65+
(i1 - ?0) * 10 + (i2 - ?0),
66+
(s1 - ?0) * 10 + (s2 - ?0)
67+
}
68+
]
69+
end
70+
end
71+
3972
@doc """
4073
Returns the `t:Calendar.iso_days/0` format of the specified date.
4174

lib/elixir/lib/calendar/naive_datetime.ex

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -476,20 +476,21 @@ defmodule NaiveDateTime do
476476
end
477477
end
478478

479+
@sep [?\s, ?T]
480+
[match_date, guard_date, read_date] = Calendar.ISO.__match_date__()
481+
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
482+
479483
def from_iso8601(string, calendar) when is_binary(string) do
480-
with <<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep, rest::binary>> <- string,
481-
true <- sep in [?\s, ?T],
482-
<<hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>> <- rest,
483-
{year, ""} <- Integer.parse(year),
484-
{month, ""} <- Integer.parse(month),
485-
{day, ""} <- Integer.parse(day),
486-
{hour, ""} <- Integer.parse(hour),
487-
{min, ""} <- Integer.parse(min),
488-
{sec, ""} <- Integer.parse(sec),
484+
with <<unquote(match_date), sep, unquote(match_time), rest::binary>>
485+
when unquote(guard_date) and sep in @sep and unquote(guard_time) <- string,
489486
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
490487
{_offset, ""} <- Calendar.ISO.parse_offset(rest) do
491-
with {:ok, utc_date} <- new(year, month, day, hour, min, sec, microsec, Calendar.ISO),
492-
do: convert(utc_date, calendar)
488+
{year, month, day} = unquote(read_date)
489+
{hour, min, sec} = unquote(read_time)
490+
491+
with {:ok, utc_date} <- new(year, month, day, hour, min, sec, microsec, Calendar.ISO) do
492+
convert(utc_date, calendar)
493+
end
493494
else
494495
_ -> {:error, :invalid_format}
495496
end

lib/elixir/lib/calendar/time.ex

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -221,23 +221,22 @@ defmodule Time do
221221
from_iso8601(<<h, rest::binary>>, calendar)
222222
end
223223

224-
def from_iso8601(<<hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>>, calendar) do
225-
with {hour, ""} <- Integer.parse(hour),
226-
{min, ""} <- Integer.parse(min),
227-
{sec, ""} <- Integer.parse(sec),
224+
[match_time, guard_time, read_time] = Calendar.ISO.__match_time__()
225+
226+
def from_iso8601(string, calendar) do
227+
with <<unquote(match_time), rest::binary>> when unquote(guard_time) <- string,
228228
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
229229
{_offset, ""} <- Calendar.ISO.parse_offset(rest) do
230-
with {:ok, utc_time} <- new(hour, min, sec, microsec, Calendar.ISO),
231-
do: convert(utc_time, calendar)
230+
{hour, min, sec} = unquote(read_time)
231+
232+
with {:ok, utc_time} <- new(hour, min, sec, microsec, Calendar.ISO) do
233+
convert(utc_time, calendar)
234+
end
232235
else
233236
_ -> {:error, :invalid_format}
234237
end
235238
end
236239

237-
def from_iso8601(<<_::binary>>, _calendar) do
238-
{:error, :invalid_format}
239-
end
240-
241240
@doc """
242241
Parses the extended "Local time" format described by
243242
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).

0 commit comments

Comments
 (0)