From 66c880663eea259c5f0590dd380620f26acc87dd Mon Sep 17 00:00:00 2001 From: Eric Hostalery Date: Thu, 25 Nov 2021 17:07:43 +0100 Subject: [PATCH 1/2] handle an EXDATE with multiple comma separated datetimes --- lib/cocktail/parser/i_calendar.ex | 43 ++++++++++++++++++++++-- test/cocktail/parser/i_calendar_test.exs | 24 +++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/lib/cocktail/parser/i_calendar.ex b/lib/cocktail/parser/i_calendar.ex index 4872fea..3a38fb4 100644 --- a/lib/cocktail/parser/i_calendar.ex +++ b/lib/cocktail/parser/i_calendar.ex @@ -104,6 +104,33 @@ defmodule Cocktail.Parser.ICalendar do end end + @spec parse_datetimes_list(String.t()) :: {:ok, [Cocktail.time()]} | {:error, term} + defp parse_datetimes_list(time_string) do + case String.split(time_string, ":") do + [tzid, datetimes] -> + datetimes + |> String.split(",") + |> Enum.map(&("#{tzid}:#{&1}")) + |> parse_datetimes_values([]) + + [datetimes] -> + datetimes + |> String.split(",") + |> parse_datetimes_values([]) + + _ -> + {:error, :invalid_datetimes_list_format} + end + end + + defp parse_datetimes_values([], datetimes_list), do: {:ok, datetimes_list} + + defp parse_datetimes_values([head | rest], datetimes_list) do + with {:ok, datetime} <- parse_datetime(head) do + parse_datetimes_values(rest, [datetime | datetimes_list]) + end + end + @spec parse_naive_datetime(String.t()) :: {:ok, NaiveDateTime.t()} | {:error, term} defp parse_naive_datetime(time_string), do: Timex.parse(time_string, @datetime_format) @@ -470,9 +497,19 @@ defmodule Cocktail.Parser.ICalendar do @spec parse_exdate(String.t(), Schedule.t(), non_neg_integer) :: {:ok, Schedule.t()} | {:error, term} defp parse_exdate(time_string, schedule, index) do - case parse_datetime(time_string) do - {:ok, datetime} -> {:ok, Schedule.add_exception_time(schedule, datetime)} - {:error, term} -> {:error, {term, index}} + case parse_datetimes_list(time_string) do + {:ok, datetimes} -> + schedule = Enum.reduce( + datetimes, + schedule, + fn datetime, acc -> + Schedule.add_exception_time(acc, datetime) + end + ) + {:ok, schedule} + + {:error, term} -> + {:error, {term, index}} end end end diff --git a/test/cocktail/parser/i_calendar_test.exs b/test/cocktail/parser/i_calendar_test.exs index 945dbc1..ee3a7b8 100644 --- a/test/cocktail/parser/i_calendar_test.exs +++ b/test/cocktail/parser/i_calendar_test.exs @@ -139,6 +139,30 @@ defmodule Cocktail.Parser.ICalendarTest do assert datetime == ~N[2017-01-01 06:00:00] end + test "parses a schedule with an EXDATE list" do + schedule_string = """ + DTSTART:20170101T060000 + EXDATE:20170101T060000,20170102T060000 + """ + + assert {:ok, schedule} = parse(schedule_string) + assert [%NaiveDateTime{} = first_datetime, %NaiveDateTime{} = second_datetime] = schedule.exception_times + assert first_datetime == ~N[2017-01-01 06:00:00] + assert second_datetime == ~N[2017-01-02 06:00:00] + end + + test "parses a schedule with an EXDATE list and a TZID" do + schedule_string = """ + DTSTART;TZID=America/Los_Angeles:20170101T060000 + EXDATE;TZID=America/Los_Angeles:20170101T060000,20170102T060000 + """ + + assert {:ok, schedule} = parse(schedule_string) + assert [%DateTime{} = first_datetime, %DateTime{} = second_datetime] = schedule.exception_times + assert first_datetime == ~Y[2017-01-01 06:00:00 America/Los_Angeles] + assert second_datetime == ~Y[2017-01-02 06:00:00 America/Los_Angeles] + end + ########## # Errors # ########## From aa3677a474cd810d85a016dc0f2baec2c6f1ff55 Mon Sep 17 00:00:00 2001 From: Eric Hostalery Date: Fri, 26 Nov 2021 16:17:33 +0100 Subject: [PATCH 2/2] improve formatting --- lib/cocktail/parser/i_calendar.ex | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/lib/cocktail/parser/i_calendar.ex b/lib/cocktail/parser/i_calendar.ex index 3a38fb4..668e0e7 100644 --- a/lib/cocktail/parser/i_calendar.ex +++ b/lib/cocktail/parser/i_calendar.ex @@ -106,23 +106,18 @@ defmodule Cocktail.Parser.ICalendar do @spec parse_datetimes_list(String.t()) :: {:ok, [Cocktail.time()]} | {:error, term} defp parse_datetimes_list(time_string) do - case String.split(time_string, ":") do - [tzid, datetimes] -> - datetimes - |> String.split(",") - |> Enum.map(&("#{tzid}:#{&1}")) - |> parse_datetimes_values([]) - - [datetimes] -> - datetimes - |> String.split(",") - |> parse_datetimes_values([]) - + with [tzid, datetimes] <- String.split(time_string, ":") do + datetimes + |> String.split(",") + |> Enum.map(&("#{tzid}:#{&1}")) + |> parse_datetimes_values([]) + else _ -> - {:error, :invalid_datetimes_list_format} + {:error, :invalid_time_format} end end + @spec parse_datetimes_values([String.t()], [Cocktail.time()]) :: {:ok, [Cocktail.time()]} | {:error, term} defp parse_datetimes_values([], datetimes_list), do: {:ok, datetimes_list} defp parse_datetimes_values([head | rest], datetimes_list) do @@ -499,14 +494,7 @@ defmodule Cocktail.Parser.ICalendar do defp parse_exdate(time_string, schedule, index) do case parse_datetimes_list(time_string) do {:ok, datetimes} -> - schedule = Enum.reduce( - datetimes, - schedule, - fn datetime, acc -> - Schedule.add_exception_time(acc, datetime) - end - ) - {:ok, schedule} + {:ok, Enum.reduce(datetimes, schedule, &(Schedule.add_exception_time(&2, &1)))} {:error, term} -> {:error, {term, index}}