Current section

Files

Jump to
calendar lib calendar date_time format.ex
Raw

lib/calendar/date_time/format.ex

defmodule Calendar.DateTime.Format do
alias Calendar.Strftime
@secs_between_year_0_and_unix_epoch 719528*24*3600 # From erlang calendar docs: there are 719528 days between Jan 1, 0 and Jan 1, 1970. Does not include leap seconds
@doc """
Format a DateTime as an RFC 2822 timestamp.
## Examples
iex> Calendar.DateTime.from_erl!({{2010, 3, 13}, {11, 23, 03}}, "America/Los_Angeles") |> rfc2822
"Sat, 13 Mar 2010 11:23:03 -0800"
iex> Calendar.DateTime.from_erl!({{2010, 3, 13}, {11, 23, 03}}, "Etc/UTC") |> rfc2822
"Sat, 13 Mar 2010 11:23:03 +0000"
"""
def rfc2822(dt) do
dt
|> contained_date_time
|> Strftime.strftime!("%a, %d %b %Y %T %z")
end
@doc """
Format a DateTime as an RFC 822 timestamp.
Note that this format is old and uses only 2 digits to denote the year!
## Examples
iex> Calendar.DateTime.from_erl!({{2010, 3, 13}, {11, 23, 03}}, "America/Los_Angeles") |> rfc822
"Sat, 13 Mar 10 11:23:03 -0800"
iex> Calendar.DateTime.from_erl!({{2010, 3, 13}, {11, 23, 03}}, "Etc/UTC") |> rfc822
"Sat, 13 Mar 10 11:23:03 +0000"
"""
def rfc822(dt) do
dt
|> contained_date_time
|> Strftime.strftime!("%a, %d %b %y %T %z")
end
@doc """
Format a DateTime as an RFC 850 timestamp.
Note that this format is old and uses only 2 digits to denote the year!
## Examples
iex> Calendar.DateTime.from_erl!({{2010, 3, 13}, {11, 23, 03}}, "America/Los_Angeles") |> rfc850
"Sat, 13-Mar-10 11:23:03 PST"
"""
def rfc850(dt) do
dt
|> contained_date_time
|> Strftime.strftime!("%a, %d-%b-%y %T %Z")
end
@doc """
Format as ISO 8601 extended (alias for rfc3339/1)
"""
def iso8601(dt), do: rfc3339(dt)
@doc """
Format as ISO 8601 Basic
# Examples
iex> Calendar.DateTime.from_erl!({{2014, 9, 26}, {20, 10, 20}}, "Etc/UTC",5) |> Calendar.DateTime.Format.iso8601_basic
"20140926T201020Z"
iex> Calendar.DateTime.from_erl!({{2014, 9, 26}, {17, 10, 20}}, "America/Montevideo",5) |> Calendar.DateTime.Format.iso8601_basic
"20140926T171020-0300"
"""
def iso8601_basic(dt) do
dt = dt |> contained_date_time
offset_part = rfc3339_offset_part(dt, dt.time_zone)
|> String.replace(":", "")
Strftime.strftime!(dt, "%Y%m%dT%H%M%S")<>offset_part
end
@doc """
Takes a DateTime.
Returns a string with the time in RFC3339 (a profile of ISO 8601)
## Examples
Without microseconds
iex> Calendar.DateTime.from_erl!({{2014, 9, 26}, {17, 10, 20}}, "America/Montevideo") |> Calendar.DateTime.Format.rfc3339
"2014-09-26T17:10:20-03:00"
With microseconds
iex> Calendar.DateTime.from_erl!({{2014, 9, 26}, {17, 10, 20, 5}}, "America/Montevideo") |> Calendar.DateTime.Format.rfc3339
"2014-09-26T17:10:20.000005-03:00"
"""
def rfc3339(%DateTime{time_zone: time_zone, year: year, month: month, day: day, hour: hour, minute: minute, second: second, microsecond: microsecond} = dt) do
[pad(year, 4), "-", pad(month), "-", pad(day), "T", pad(hour), ":", pad(minute), ":", pad(second),
rfc3330_microsecond_part(microsecond, nil),
rfc3339_offset_part(dt, time_zone)]
|> IO.iodata_to_binary
end
def rfc3339(dt), do: dt |> contained_date_time |> rfc3339
defp rfc3339_offset_part(_, time_zone) when time_zone == "UTC" or time_zone == "Etc/UTC", do: "Z"
defp rfc3339_offset_part(dt, _) do
Strftime.strftime!(dt, "%z")
total_off = dt.utc_offset + dt.std_offset
sign = sign_for_offset(total_off)
offset_amount_string = total_off |> secs_to_hours_mins_string
sign<>offset_amount_string
end
defp sign_for_offset(offset) when offset < 0, do: "-"
defp sign_for_offset(_), do: "+"
defp secs_to_hours_mins_string(secs) do
secs = abs(secs)
hours = secs/3600.0 |> Float.floor |> trunc
mins = rem(secs, 3600)/60.0 |> Float.floor |> trunc
"#{hours|>pad(2)}:#{mins|>pad(2)}"
end
defp rfc3330_microsecond_part(_, 0), do: ""
defp rfc3330_microsecond_part({microsecond, precision}, nil), do: rfc3330_microsecond_part({microsecond, precision}, precision)
defp rfc3330_microsecond_part({microsecond, _}, 6) do
"." <> pad(microsecond, 6)
end
defp rfc3330_microsecond_part({microsecond, _inherent_precision}, precision) when precision >= 1 and precision <=6 do
".#{microsecond |> pad(6)}" |> String.slice(0..precision)
end
defp pad(subject, len\\2, char\\?0)
defp pad(subject, 2, _char) when is_integer(subject) and subject >= 10 and subject <= 99 do
subject |> Integer.to_string
end
defp pad(subject, len, char) when is_integer(subject) do
subject |> Integer.to_string |> pad(len, char)
end
defp pad(subject, len, char) when is_binary(subject) do
String.rjust(subject, len, char)
end
@doc """
Takes a DateTime and a integer for number of decimals.
Returns a string with the time in RFC3339 (a profile of ISO 8601)
The decimal_count integer defines the number fractional second digits.
The decimal_count must be between 0 and 6.
Fractional seconds are not rounded up, but rather trucated.
## Examples
DateTime does not have microseconds, but 3 digits of fractional seconds
requested. We assume 0 microseconds and display three zeroes.
iex> Calendar.DateTime.from_erl!({{2014, 9, 26}, {17, 10, 20}}, "America/Montevideo") |> Calendar.DateTime.Format.rfc3339(3)
"2014-09-26T17:10:20.000-03:00"
DateTime has microseconds and decimal count set to 6
iex> Calendar.DateTime.from_erl!({{2014, 9, 26}, {17, 10, 20}}, "America/Montevideo",{5, 6}) |> Calendar.DateTime.Format.rfc3339(6)
"2014-09-26T17:10:20.000005-03:00"
DateTime has microseconds and decimal count set to 5
iex> Calendar.DateTime.from_erl!({{2014, 9, 26}, {17, 10, 20}}, "America/Montevideo",{5, 6}) |> Calendar.DateTime.Format.rfc3339(5)
"2014-09-26T17:10:20.00000-03:00"
DateTime has microseconds and decimal count set to 0
iex> Calendar.DateTime.from_erl!({{2014, 9, 26}, {17, 10, 20}}, "America/Montevideo",{5, 6}) |> Calendar.DateTime.Format.rfc3339(0)
"2014-09-26T17:10:20-03:00"
"""
def rfc3339(%DateTime{} = dt, decimal_count) when decimal_count >= 0 and decimal_count <=6 do
Strftime.strftime!(dt, "%Y-%m-%dT%H:%M:%S")<>
rfc3330_microsecond_part(dt.microsecond, decimal_count)<>
rfc3339_offset_part(dt, dt.time_zone)
end
def rfc3339(dt, decimal_count) do
dt |> contained_date_time |> rfc3339(decimal_count)
end
@doc """
Takes a DateTime.
Returns a string with the date-time in RFC 2616 format. This format is used in
the HTTP protocol. Note that the date-time will always be "shifted" to UTC.
## Example
# The time is 6:09 in the morning in Montevideo, but 9:09 GMT/UTC.
iex> Calendar.DateTime.from_erl!({{2014, 9, 6}, {6, 9, 8}}, "America/Montevideo") |> Calendar.DateTime.Format.httpdate
"Sat, 06 Sep 2014 09:09:08 GMT"
"""
def httpdate(%DateTime{time_zone: "Etc/UTC"} = dt) do
dt |> Strftime.strftime!("%a, %d %b %Y %H:%M:%S GMT")
end
def httpdate(dt) do
dt
|> contained_date_time
|> Calendar.DateTime.shift_zone!("Etc/UTC")
|> httpdate
end
@doc """
Unix time. Unix time is defined as seconds since 1970-01-01 00:00:00 UTC without leap seconds.
## Examples
iex> Calendar.DateTime.from_erl!({{2001,09,09},{03,46,40}}, "Europe/Copenhagen", {55, 2}) |> Calendar.DateTime.Format.unix
1_000_000_000
"""
def unix(%DateTime{time_zone: "Etc/UTC"} = dt) do
dt
|> Calendar.DateTime.gregorian_seconds
|> Kernel.-(@secs_between_year_0_and_unix_epoch)
end
def unix(dt) do
dt
|> contained_date_time
|> Calendar.DateTime.shift_zone!("Etc/UTC")
|> unix
end
@doc """
Like unix_time but returns a float with fractional seconds. If the microsecond of the DateTime
is nil, the fractional seconds will be treated as 0.0 as seen in the second example below:
## Examples
iex> Calendar.DateTime.from_erl!({{2001,09,09},{03,46,40}}, "Europe/Copenhagen", {985085, 6}) |> Calendar.DateTime.Format.unix_micro
1_000_000_000.985085
iex> Calendar.DateTime.from_erl!({{2001,09,09},{03,46,40}}, "Europe/Copenhagen") |> Calendar.DateTime.Format.unix_micro
1_000_000_000.0
"""
def unix_micro(%DateTime{microsecond: {microsecond, _}} = date_time) when microsecond == 0 do
date_time |> unix |> Kernel.+(0.0)
end
def unix_micro(%DateTime{} = date_time) do
{microsecond, _} = date_time.microsecond
date_time
|> unix
|> Kernel.+(microsecond/1_000_000)
end
def unix_micro(date_time) do
date_time |> contained_date_time |> unix_micro
end
@doc """
Takes datetime and returns UTC timestamp in JavaScript format. That is milliseconds since 1970 unix epoch.
These millisecond numbers can be used to create new Javascript Dates in JavaScript like so: new Date(1000000000985)
The UTC value of the javascript date will be the same as that of the Elixir Calendar.DateTime.
## Examples
iex> Calendar.DateTime.from_erl!({{2001,09,09},{03,46,40}}, "Europe/Copenhagen", 985085) |> Calendar.DateTime.Format.js_ms
1000000000985
iex> Calendar.DateTime.from_erl!({{2001,09,09},{03,46,40}}, "Europe/Copenhagen", 98508) |> Calendar.DateTime.Format.js_ms
1000000000098
"""
def js_ms(date_time) do
date_time
|> contained_date_time
|> DateTime.to_unix(:milliseconds)
end
defp contained_date_time(dt_container) do
Calendar.ContainsDateTime.dt_struct(dt_container)
end
end