Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/icalendar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule ICalendar do

defstruct events: []
defdelegate to_ics(events, options \\ []), to: ICalendar.Serialize
defdelegate from_ics(events), to: ICalendar.Deserialize
defdelegate from_ics(events, options \\ []), to: ICalendar.Deserialize

@doc """
To create a Phoenix/Plug controller and view that output ics format:
Expand Down
33 changes: 22 additions & 11 deletions lib/icalendar/deserialize.ex
Original file line number Diff line number Diff line change
@@ -1,40 +1,51 @@
defprotocol ICalendar.Deserialize do
def from_ics(ics)
def from_ics(ics, opts \\ [])
end

alias ICalendar.Deserialize

defimpl ICalendar.Deserialize, for: BitString do
alias ICalendar.Util.Deserialize
require Logger

def from_ics(ics) do
def from_ics(ics, opts \\ []) do
ics
|> String.trim()
|> String.split("\n")
|> Enum.map(&String.trim_trailing/1)
|> get_events()
|> get_events([], [], opts)
end

defp get_events(calendar_data, event_collector \\ [], temp_collector \\ [])
defp get_events(calendar_data, event_collector, temp_collector, opts)

defp get_events([head | calendar_data], event_collector, temp_collector) do
defp get_events([head | calendar_data], event_collector, temp_collector, opts) do
case head do
"BEGIN:VEVENT" ->
# start collecting event
get_events(calendar_data, event_collector, [head])
get_events(calendar_data, event_collector, [head], opts)

"END:VEVENT" ->
# finish collecting event
event = Deserialize.build_event(temp_collector ++ [head])
get_events(calendar_data, [event] ++ event_collector, [])
try do
event = Deserialize.build_event(temp_collector ++ [head])
get_events(calendar_data, [event] ++ event_collector, [], opts)
rescue
e ->
if Keyword.get(opts, :ignore_errors) do
Logger.info("Error parsing: #{inspect(e)}")
get_events(calendar_data, event_collector, [], opts)
else
Kernel.reraise(e, __STACKTRACE__)
end
end

event_property when temp_collector != [] ->
get_events(calendar_data, event_collector, temp_collector ++ [event_property])
get_events(calendar_data, event_collector, temp_collector ++ [event_property], opts)

_unimportant_stuff ->
get_events(calendar_data, event_collector, temp_collector)
get_events(calendar_data, event_collector, temp_collector, opts)
end
end

defp get_events([], event_collector, _temp_collector), do: event_collector
defp get_events([], event_collector, _temp_collector, _opts), do: event_collector
end
22 changes: 17 additions & 5 deletions lib/icalendar/util/deserialize.ex
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,19 @@ defmodule ICalendar.Util.Deserialize do
end

def parse_attr(
%Property{key: "EXDATE", value: exdate, params: params},
%Property{key: "EXDATE", value: new_exdates, params: params},
acc
) do
exdates = Map.get(acc, :exdates, [])
{:ok, timestamp} = to_date(exdate, params)
%{acc | exdates: [timestamp | exdates]}

new_dates =
String.split(new_exdates, ",")
|> Enum.map(fn exdate ->
{:ok, timestamp} = to_date(exdate, params)
timestamp
end)

%{acc | exdates: Enum.concat(new_dates, exdates)}
end

def parse_attr(
Expand Down Expand Up @@ -238,8 +245,13 @@ defmodule ICalendar.Util.Deserialize do
if Regex.match?(~r/\//, timezone) do
timezone
else
Timex.Timezone.Utils.to_olson(timezone)
end
try do
Timex.Timezone.Utils.to_olson(timezone)
rescue
# probably a custom timezone defined in this file
_e -> nil
end
end || "UTC"

date_string =
case String.last(date_string) do
Expand Down
35 changes: 35 additions & 0 deletions test/icalendar/deserialize_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,41 @@ defmodule ICalendar.DeserializeTest do
assert event.dtend.time_zone == "America/Chicago"
end

test "with custom Timezone won't crash" do
ics = """
BEGIN:VTIMEZONE
TZID:Eastern Standard Time 1
BEGIN:STANDARD
DTSTART:16010101T000000
TZOFFSETFROM:-0500
TZOFFSETTO:-0500
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTEND;TZID=Eastern Standard Time 1:22221224T084500
DTSTART;TZID=Eastern Standard Time 1:22221224T083000
END:VEVENT
"""

[event] = ICalendar.from_ics(ics)
assert event.dtstart.time_zone == "Etc/UTC"
assert event.dtend.time_zone == "Etc/UTC"
end

test "with rrule exceptions" do
ics = """
BEGIN:VEVENT
RRULE:FREQ=WEEKLY
EXDATE;TZID=Eastern Standard Time:20201126T091500,20201127T091500
DTSTART;TZID=Eastern Standard Time:20201023T091500
DTEND;TZID=Eastern Standard Time:20201023T093000
END:VEVENT
"""

[event] = ICalendar.from_ics(ics)
assert length(event.exdates) == 2
end

test "with CR+LF line endings" do
ics = """
BEGIN:VEVENT
Expand Down