lib/ex_ical/date_parser.ex
defmodule ExIcal.DateParser do
@moduledoc """
Responsible for parsing datestrings in predefined formats with `parse/1` and
`parse/2`.
"""
@doc """
Responsible for parsing datestrings in predefined formats into %DateTime{}
structs. Valid formats are defined by the "Internet Calendaring and Scheduling
Core Object Specification" (RFC 2445).
- **Full text:** http://www.ietf.org/rfc/rfc2445.txt
- **DateTime spec:** http://www.kanzaki.com/docs/ical/dateTime.html
- **Date spec:** http://www.kanzaki.com/docs/ical/date.html
## Valid Formats
The format is based on the [ISO 8601] complete representation, basic format
for a calendar date and time of day. The text format is a concatenation of
the "date", followed by the LATIN CAPITAL LETTER T character (US-ASCII
decimal 84) time designator, followed by the "time" format.
1. **<YYYYMMDD>T<HHMMSS>** -
The date with local time form is simply a date-time value that does not
contain the UTC designator nor does it reference a time zone. For
example, the following represents Janurary 18, 1998, at 11 PM:
19980118T230000
2. **<YYYYMMDD>T<HHMMSS>Z** -
The date with UTC time, or absolute time, is identified by a LATIN
CAPITAL LETTER Z suffix character (US-ASCII decimal 90), the UTC
designator, appended to the time value. For example, the following
represents January 19, 1998, at 0700 UTC:
19980119T070000Z
The format for the date value type is expressed as the [ISO 8601] complete
representation, basic format for a calendar date. The textual format
specifies a four-digit year, two-digit month, and two-digit day of the
month. There are no separator characters between the year, month and day
component text.
3. **<YYYYMMDD>** -
The following represents July 14, 1997:
19970714
4. **<YYYYMMDD>Z** -
A basic date in absolute time. The following represents July 14, 1997 UTC:
19970714Z
## Resulting Timezone
If the datestring has a Zulu time indicator (ending in "Z"), then the
returned %DateTime{} will be in UTC, regardless of the inputted tzid.
If the tzid is a valid tzid (ex. "America/New_York"), `parse/2` will return
a %DateTime{} with the given timezone.
Otherwise, if `parse/1` is used or `parse/2` is used with a `nil` tzid,
the returned %DateTime{} will be in the local timezone.
"""
@type valid_timezone :: String.t | :utc | :local
@spec parse(String.t, valid_timezone | nil) :: %DateTime{}
def parse(data, tzid \\ nil)
# Date Format: "19690620T201804Z", Timezone: *
def parse(<< year :: binary-size(4), month :: binary-size(2), day :: binary-size(2), "T",
hour :: binary-size(2), minutes :: binary-size(2), seconds :: binary-size(2), "Z" >>,
_timezone) do
date = {year, month, day}
time = {hour, minutes, seconds}
{to_integers(date), to_integers(time)}
|> NaiveDateTime.from_erl!()
|> DateTime.from_naive!("Etc/UTC")
end
# Date Format: "19690620T201804", Timezone: nil
def parse(<< year :: binary-size(4), month :: binary-size(2), day :: binary-size(2), "T",
hour :: binary-size(2), minutes :: binary-size(2), seconds :: binary-size(2) >>,
nil) do
date = {year, month, day}
time = {hour, minutes, seconds}
{to_integers(date), to_integers(time)}
|> NaiveDateTime.from_erl!()
|> DateTime.from_naive!("Etc/UTC")
end
# Date Format: "19690620T201804", Timezone: *
def parse(<< year :: binary-size(4), month :: binary-size(2), day :: binary-size(2), "T",
hour :: binary-size(2), minutes :: binary-size(2), seconds :: binary-size(2) >>,
timezone) do
date = {year, month, day}
time = {hour, minutes, seconds}
{to_integers(date), to_integers(time)}
|> Timex.to_datetime(timezone)
end
# Date Format: "19690620Z", Timezone: *
def parse(<< year :: binary-size(4), month :: binary-size(2), day :: binary-size(2), "Z" >>, _timezone) do
{to_integers({year, month, day}), {0, 0, 0}}
|> Timex.to_datetime()
end
# Date Format: "19690620", Timezone: *
def parse(<< year :: binary-size(4), month :: binary-size(2), day :: binary-size(2) >>, _timezone) do
{to_integers({year, month, day}), {0, 0, 0}}
|> Timex.to_datetime()
end
@spec to_integers({String.t, String.t, String.t}) :: {integer, integer, integer}
defp to_integers({str1, str2, str3}) do
{
String.to_integer(str1),
String.to_integer(str2),
String.to_integer(str3)
}
end
end