Skip to content

Commit

Permalink
Deserialize handles multiple events in ics (#36)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericdude4 authored and lpil committed Sep 5, 2019
1 parent 4799665 commit b7fdbb2
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 10 deletions.
25 changes: 24 additions & 1 deletion lib/icalendar/deserialize.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,29 @@ defimpl ICalendar.Deserialize, for: BitString do
|> String.trim()
|> String.split("\n")
|> Enum.map(&String.trim_trailing/1)
|> Deserialize.build_event()
|> get_events()
end

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

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

"END:VEVENT" ->
# finish collecting event
event = Deserialize.build_event(temp_collector ++ [head])
get_events(calendar_data, [event] ++ event_collector, [])

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

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

defp get_events([], event_collector, _temp_collector), do: event_collector
end
6 changes: 5 additions & 1 deletion lib/icalendar/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ defmodule ICalendar.Event do
categories: nil,
class: nil,
comment: nil,
geo: nil
geo: nil,
modified: nil,
organizer: nil,
sequence: nil,
attendees: []
end

defimpl ICalendar.Serialize, for: ICalendar.Event do
Expand Down
62 changes: 60 additions & 2 deletions lib/icalendar/util/deserialize.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@ defmodule ICalendar.Util.Deserialize do
"""
def retrieve_kvs(line) do
# Split Line up into key and value
[key, value] = String.split(line, ":", parts: 2, trim: true)
[key, params] = retrieve_params(key)
{key, value, params} =
case String.split(line, ":", parts: 2, trim: true) do
[key, value] ->
[key, params] = retrieve_params(key)
{key, value, params}

[key] ->
{key, nil, %{}}
end

%Property{key: String.upcase(key), value: value, params: params}
end
Expand Down Expand Up @@ -53,6 +60,8 @@ defmodule ICalendar.Util.Deserialize do
[key, params]
end

def parse_attr(%Property{key: _, value: nil}, acc), do: acc

def parse_attr(
%Property{key: "DESCRIPTION", value: description},
acc
Expand Down Expand Up @@ -125,6 +134,42 @@ defmodule ICalendar.Util.Deserialize do
%{acc | geo: to_geo(geo)}
end

def parse_attr(
%Property{key: "UID", value: uid},
acc
) do
%{acc | uid: uid}
end

def parse_attr(
%Property{key: "LAST-MODIFIED", value: modified},
acc
) do
{:ok, timestamp} = to_date(modified)
%{acc | modified: timestamp}
end

def parse_attr(
%Property{key: "ORGANIZER", params: _params, value: organizer},
acc
) do
%{acc | organizer: organizer}
end

def parse_attr(
%Property{key: "ATTENDEE", params: params, value: value},
acc
) do
%{acc | attendees: [Map.put(params, :original_value, value)] ++ acc.attendees}
end

def parse_attr(
%Property{key: "SEQUENCE", value: sequence},
acc
) do
%{acc | sequence: sequence}
end

def parse_attr(
%Property{key: "URL", value: url},
acc
Expand Down Expand Up @@ -162,6 +207,15 @@ defmodule ICalendar.Util.Deserialize do
[{{1998, 1, 19}, {2, 0, 0}}, "America/Chicago"]
"""
def to_date(date_string, %{"TZID" => timezone}) do
# Microsoft Outlook calendar .ICS files report times in Greenwich Standard Time (UTC +0)
# so just convert this to UTC
timezone =
if Regex.match?(~r/\//, timezone) do
timezone
else
Timex.Timezone.Utils.to_olson(timezone)
end

date_string =
case String.last(date_string) do
"Z" -> date_string
Expand All @@ -171,6 +225,10 @@ defmodule ICalendar.Util.Deserialize do
Timex.parse(date_string <> timezone, "{YYYY}{0M}{0D}T{h24}{m}{s}Z{Zname}")
end

def to_date(date_string, %{"VALUE" => "DATE"}) do
to_date(date_string <> "T000000Z")
end

def to_date(date_string, %{}) do
to_date(date_string, %{"TZID" => "Etc/UTC"})
end
Expand Down
15 changes: 15 additions & 0 deletions lib/icalendar/util/kv.ex
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ defmodule ICalendar.Util.KV do
"#{key}:#{lat};#{lon}\n"
end

def build("ATTENDEES", attendees) do
Enum.map(attendees, fn attendee ->
params =
for {key, val} <- attendee do
unless key == :original_value do
";#{key}=#{val}"
end
end
|> Enum.join()

"ATTENDEE#{params}:#{attendee.original_value}"
end)
|> Enum.join("\n")
end

def build(key, date = %DateTime{time_zone: "Etc/UTC"}) do
"#{key}:#{Value.to_ics(date)}Z\n"
end
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule ICalendar.Mixfile do
use Mix.Project

@version "0.8.1"
@version "1.0.0"

def project do
[
Expand Down
9 changes: 5 additions & 4 deletions test/icalendar/deserialize_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ defmodule ICalendar.DeserializeTest do
END:VEVENT
"""

event = ICalendar.from_ics(ics)
[event] = ICalendar.from_ics(ics)

assert event == %Event{
dtstart: Timex.to_datetime({{2015, 12, 24}, {8, 30, 0}}),
Expand All @@ -44,19 +44,20 @@ defmodule ICalendar.DeserializeTest do
END:VEVENT
"""

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

test "with CR+LF line endings" do
ics = """
BEGIN:VEVENT
DESCRIPTION:CR+LF line endings\r\nSUMMARY:Going fishing\r
DTEND:20151224T084500Z\r\nDTSTART:20151224T083000Z\r
END:VEVENT
"""

event = ICalendar.from_ics(ics)
[event] = ICalendar.from_ics(ics)
assert event.description == "CR+LF line endings"
end

Expand All @@ -77,7 +78,7 @@ defmodule ICalendar.DeserializeTest do
END:VEVENT
"""

event = ICalendar.from_ics(ics)
[event] = ICalendar.from_ics(ics)
assert event.url == "http://google.com"
end
end
Expand Down
72 changes: 72 additions & 0 deletions test/icalendar/util/deserialize_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,76 @@ defmodule ICalendar.Util.DeserializeTest do
description: nil
}
end

test "Handles date strings" do
event =
"""
BEGIN:VEVENT
DTSTART;VALUE=DATE:20190624
DTEND;VALUE=DATE:20190625
END:VEVENT
"""
|> String.trim()
|> String.split("\n")
|> Deserialize.build_event()

assert %Event{} = event
end

test "Handle empty keys" do
event =
"""
BEGIN:VEVENT
DESCRIPTION:
END:VEVENT
"""
|> String.trim()
|> String.split("\n")
|> Deserialize.build_event()

assert %Event{} = event
end

test "Include ORGANIZER and ATTENDEEs in event" do
event =
"""
BEGIN:VEVENT
DTSTART:20190711T130000Z
DTEND:20190711T150000Z
DTSTAMP:20190719T195201Z
ORGANIZER;[email protected]:mailto:[email protected]
UID:7E212264-C604-4071-892B-E0A28048F1BA
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;[email protected];X-NUM-GUESTS=0:mailto:[email protected]
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;[email protected];X-NUM-GUESTS=0:mailto:[email protected]
ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=James SM;X-NUM-GUESTS=0:mailto:[email protected]
CREATED:20190709T192336Z
DESCRIPTION:
LAST-MODIFIED:20190711T130843Z
LOCATION:In-person at Volta and https://zoom.us/j/12345678
SEQUENCE:0
STATUS:CONFIRMED
SUMMARY:Design session
END:VEVENT
"""
|> String.trim()
|> String.split("\n")
|> Deserialize.build_event()

assert %Event{} = event
end

test "Convert other time zone formats to UTC" do
event =
"""
BEGIN:VEVENT
DTSTART;TZID=Greenwich Standard Time:20190726T190000
DTEND;TZID=Greenwich Standard Time:20190726T213000
END:VEVENT
"""
|> String.trim()
|> String.split("\n")
|> Deserialize.build_event()

assert %Event{} = event
end
end
2 changes: 1 addition & 1 deletion test/icalendar_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ defmodule ICalendarTest do
}
]

new_event =
[new_event] =
%ICalendar{events: events}
|> ICalendar.to_ics(vendor: @vendor)
|> ICalendar.from_ics()
Expand Down

0 comments on commit b7fdbb2

Please sign in to comment.