fazibear / ex_ical Goto Github PK
View Code? Open in Web Editor NEWICalendar parser for Elixir.
License: MIT License
ICalendar parser for Elixir.
License: MIT License
ex_ical
is locked on to an old release of timex
({:timex, "~> 1.0"}
, they're currently at 3.x).
This means that in order to use ex_ical
in a project (assuming that the project isn't using timex ~> 1.0
, a developer needs to not only include an extra library/version, but they will also need to write their own adapter from the old Timex datetime formats into whatever library they're using in their application.
This library should be able to work with current DateTime libraries.
Since Date
and DateTime
were only just added to Elixir (end even then, they aren't a complete implementation), there are a number of libraries available (timex
, calendar
, good_times
, etc.) Ideally, using ex_ical
shouldn't lock you into having to use a specific version of a specific library–it should be able to work with a number of options. Just upgrading to timex ~> 3.0
makes the situation better, but it won't solve the full problem. I think that using an adapter pattern and letting the user specify their library/adapter is the best way to handle this.
Ideally, something like the following interface would work. It should be able to handle a default case with minimal boilerplate required; common cases with a small amount of configuration; and "unsupported" cases with a bit more configuration.
# mix.exs
def applications do
[applications: [:timex, ...]]
end
def deps do
[
{:timex, "~> 3.0"},
{:ex_ical, "~> 1.0"},
...
]
end
calendar
, another popular DateTime library# mix.exs
def applications do
[applications: [:calendar, ...]]
end
def deps do
[
{:calendar, "~> 1.0"},
{:ex_ical, "~> 1.0"},
....
]
end
# config/config.exs
config :ex_ical,
datetime_library: :calendar
# mix.exs
def applications do
[applications: [...]]
end
def deps do
[{:ex_ical, "~> 1.0"}, ...]
end
# config/config.exs
config :ex_ical,
datetime_library: :bespoke_datetime,
datetime_adapter: BespokeDateTime.Adapter # the user writes this module
Trying to parse an ical file with a TZID (either set calendar-wide or for a specific date) blows up with an ArgumentError on :ets.lookup/2
(called from Tzdata
).
iex> calendar_wide_tzid = "BEGIN:VEVENT\nTZID=America/New_York\nDTSTART:19690620"
iex> date_specific_tzid = "BEGIN:VEVENT\nDTSTART;TZID=America/New_York:19690620"
iex> ExIcal.parse calendar_wide_tzid
** (ArgumentError) argument error
(stdlib) :ets.lookup(:tzdata_current_release, :release_version)
lib/tzdata/release_reader.ex:41: Tzdata.ReleaseReader.current_release_from_table/0
lib/tzdata/release_reader.ex:13: Tzdata.ReleaseReader.simple_lookup/1
lib/tzdata/release_reader.ex:7: Tzdata.ReleaseReader.zone_and_link_list/0
lib/tzdata.ex:61: Tzdata.zone_exists?/1
lib/timezone/timezone.ex:130: Timex.Timezone.get/2
(ex_ical) lib/ex_ical/date_parser.ex:70: ExIcal.DateParser.datetime_from_params/3
(ex_ical) lib/ex_ical/parser.ex:15: ExIcal.Parser.parse_line/2
iex> ExIcal.parse date_specific_tzid
** (ArgumentError) argument error
(stdlib) :ets.lookup(:tzdata_current_release, :release_version)
...
This can be seen more directly with the DateParser:
iex> alias ExIcal.DateParser
iex> tzid = "America/New_York"
iex> date = "19690620"
iex> DateParser.parse(date, tzid)
** (ArgumentError) argument error
(stdlib) :ets.lookup(:tzdata_current_release, :release_version)
...
The parser should create a new event given a valid time zone:
iex> ical = "BEGIN:VEVENT\nDTSTART;TZID=America/New_York:19690620"
iex> ExIcal.parse ical
[%ExIcal.Event{description: nil, end: nil, rrule: nil, stamp: nil,
start: %Timex.DateTime{calendar: :gregorian, day: 20, hour: 0, minute: 0,
month: 6, ms: 0, second: 0,
timezone: %Timex.TimezoneInfo{abbreviation: "EDT",
from: {:sunday, {{2016, 3, 13}, {2, 0, 0}}}, full_name: "America/New_York",
offset_std: 60, offset_utc: -300,
until: {:sunday, {{2016, 11, 6}, {1, 0, 0}}}}, year: 1969}, summary: nil}]
ExIcal: observed on 0.0.3
Elixir: 1.3.2
Erlang/OTP: 19
OS: Mac OS X 10.11.5
DTSTART;TZID=W. Europe Standard Time:20181227T130000 or
DTSTART;TZID="W. Europe Standard Time":20181227T130000
leads to
start: {:error, {:invalid_timezone, "W. Europe Standard Time"}},
Reason seems to be the missing lookup:
https://github.com/bitwalker/timex/blob/master/priv/standard_to_olson.exs
I´m not sure where and how to do it, but there needs be something like this:
Timex.Timezone.Utils.to_olson("W. Europe Standard ") which replaces it with "Europe/Berlin"
BEGIN:VEVENT .......
\r\nDESCRIPTION:Bodytext
.....
BEGIN:VALARM\r\nDESCRIPTION:REMINDER
....
leads to the description "REMINDER" instead of "Bodytext"
Please publish a 0.2 release with latest changes. :)
I tried to use this tool to parse a Facebook birthday .ics file, but it failed with an argument error. Here is the format of the iCal file:
BEGIN:VCALENDAR
... (irrelevant header stuff)
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
BEGIN:VEVENT
DTSTART:20160611
SUMMARY:Foo Bar's birthday
RRULE:FREQ=YEARLY
DURATION:P1D
UID:[email protected]
END:VEVENT
...
END:VCALENDAR
This is the full text of the error:
** (ArgumentError) argument error
(stdlib) :ets.lookup(:tzdata_current_release, :release_version)
lib/tzdata/release_reader.ex:41: Tzdata.ReleaseReader.current_release_from_table/0
lib/tzdata/release_reader.ex:13: Tzdata.ReleaseReader.simple_lookup/1
lib/tzdata/release_reader.ex:7: Tzdata.ReleaseReader.zone_and_link_list/0
lib/tzdata.ex:61: Tzdata.zone_exists?/1
lib/timezone/timezone.ex:130: Timex.Timezone.get/2
(ex_ical) lib/ex_ical/date_parser.ex:23: ExIcal.DateParser.parse/2
(ex_ical) lib/ex_ical/parser.ex:12: ExIcal.Parser.parse_line/2
Following the stack trace, it looks like there's no pattern for the YYYYMMDD
input string with a timezone of nil
. When DateParser
tried to parse DTSTART
, it tried to pass nil
to Timezone.get/1
, throwing an argument error.
This looks like it won't be a hard fix; I'll open a pull request with the bugfix/tests.
Some of the valid date/date-time formats (as defined in RFC 2445) are not parsable.
For example, when trying to parse a DTSTART
using a datetime with the local timezone:
iex> ical = """
BEGIN:VEVENT
DTSTART:19690620T201804
END:VEVENT
""" # local time
iex> ExIcal.parse ical
** (FunctionClauseError) no function clause matching in ExIcal.DateParser.parse/2
(ex_ical) lib/ex_ical/date_parser.ex:7: ExIcal.DateParser.parse("19690620T201804", nil)
(ex_ical) lib/ex_ical/parser.ex:15: ExIcal.Parser.parse_line/2
(elixir) lib/enum.ex:1623: Enum."-reduce/3-lists^foldl/2-0-"/3
(ex_ical) lib/ex_ical/parser.ex:8: ExIcal.Parser.parse/1
From what I can tell, Form 1 and Form 3 (below) are not currently handled.
I'd expect all formats defined in the specs to be parsable by this library.
There are four valid input forms:
19690620T201804
)19690620T201804Z
)TZID=America/Chicago:19690620T201804
)19690620
)Also, these inline time zones need to interact in a reasonable way with the global calendar time zones–inline time zones (Forms 2 & 3) should take precedence over global zones.
Here are some possible rules:
# | Example Line | When no global TZID is set |
When TZID:Europe/Berlin |
---|---|---|---|
1 | DTSTART:19690620 |
No timezone (date) | No timezone (date) |
2* | DTSTART:19690620Z |
No timezone (date) | No timezone (date) |
3 | DTSTART:19690620T201804 |
No timezone (local) | Europe/Berlin |
4 | DTSTART:19690620T201804Z |
UTD |
UTD (priority to zulu time marker) |
5 | DTSTART;TZID=America/New_York:19690620T201804 |
America/New_York |
America/New_York (priority to inline tzid) |
6* | DTSTART;TZID=America/New_York:19690620T201804Z |
UDT (priority to zulu time marker) |
UDT (priority to zulu time marker) |
*L2 & L6 are not in the spec, but I included them here for error tolerance
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.