Skip to content

Commit e38be07

Browse files
author
José Valim
committed
Add DateTime.from_iso8601/1
Thanks to Lau Taarnskov for suggestions and code review. Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
1 parent 28824e8 commit e38be07

File tree

1 file changed

+81
-3
lines changed

1 file changed

+81
-3
lines changed

lib/elixir/lib/calendar.ex

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,9 +1598,7 @@ defmodule DateTime do
15981598
15991599
WARNING: the ISO 8601 datetime format does not contain the time zone nor
16001600
its abbreviation, which means information is lost when converting to such
1601-
format. This is also why this module does not provide a `from_iso8601/1`
1602-
function, as it is impossible to build a proper `DateTime` from only the
1603-
information in the ISO 8601 string.
1601+
format.
16041602
16051603
### Examples
16061604
@@ -1630,6 +1628,86 @@ defmodule DateTime do
16301628
time_zone, zone_abbr, utc_offset, std_offset)
16311629
end
16321630

1631+
@doc """
1632+
Parses the extended "Date and time of day" format described by
1633+
[ISO 8601:2004](https://en.wikipedia.org/wiki/ISO_8601).
1634+
1635+
Since ISO8601 does not include the proper time zone, the given
1636+
string will be converted to UTC and its offset in seconds will be
1637+
returned as part of this function. Therefore offset information
1638+
must be present in the string.
1639+
1640+
As specified in the standard, the separator "T" may be omitted if
1641+
desired as there is no ambiguity within this function.
1642+
1643+
Time representations with reduced accuracy are not supported.
1644+
1645+
## Examples
1646+
1647+
iex> DateTime.from_iso8601("2015-01-23T23:50:07Z")
1648+
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 23, microsecond: {0, 0}, minute: 50, month: 1, second: 7, std_offset: 0,
1649+
time_zone: "Etc/UTC", utc_offset: 0, year: 2015, zone_abbr: "UTC"}, 0}
1650+
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123+02:30")
1651+
{:ok, %DateTime{calendar: Calendar.ISO, day: 23, hour: 21, microsecond: {123000, 3}, minute: 20, month: 1, second: 7, std_offset: 0,
1652+
time_zone: "Etc/UTC", utc_offset: 0, year: 2015, zone_abbr: "UTC"}, 9000}
1653+
1654+
iex> DateTime.from_iso8601("2015-01-23P23:50:07")
1655+
{:error, :invalid_format}
1656+
iex> DateTime.from_iso8601("2015-01-23 23:50:07A")
1657+
{:error, :invalid_format}
1658+
iex> DateTime.from_iso8601("2015-01-23T23:50:07")
1659+
{:error, :missing_offset}
1660+
iex> DateTime.from_iso8601("2015-01-23 23:50:61")
1661+
{:error, :invalid_time}
1662+
iex> DateTime.from_iso8601("2015-01-32 23:50:07")
1663+
{:error, :invalid_date}
1664+
1665+
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123-00:00")
1666+
{:error, :invalid_format}
1667+
iex> DateTime.from_iso8601("2015-01-23T23:50:07.123-00:60")
1668+
{:error, :invalid_format}
1669+
1670+
"""
1671+
@spec from_iso8601(String.t) :: {:ok, t, Calendar.utc_offset} | {:error, atom}
1672+
def from_iso8601(<<year::4-bytes, ?-, month::2-bytes, ?-, day::2-bytes, sep,
1673+
hour::2-bytes, ?:, min::2-bytes, ?:, sec::2-bytes, rest::binary>>) when sep in [?\s, ?T] do
1674+
with {year, ""} <- Integer.parse(year),
1675+
{month, ""} <- Integer.parse(month),
1676+
{day, ""} <- Integer.parse(day),
1677+
{hour, ""} <- Integer.parse(hour),
1678+
{min, ""} <- Integer.parse(min),
1679+
{sec, ""} <- Integer.parse(sec),
1680+
{microsec, rest} <- Calendar.ISO.parse_microsecond(rest),
1681+
{:ok, date} <- Calendar.ISO.date(year, month, day),
1682+
{:ok, time} <- Time.new(hour, min, sec, microsec),
1683+
{:ok, offset} <- parse_offset(rest) do
1684+
%{year: year, month: month, day: day} = date
1685+
%{hour: hour, minute: minute, second: second, microsecond: microsecond} = time
1686+
1687+
erl = {{year, month, day}, {hour, minute, second}}
1688+
seconds = :calendar.datetime_to_gregorian_seconds(erl)
1689+
{{year, month, day}, {hour, minute, second}} =
1690+
:calendar.gregorian_seconds_to_datetime(seconds - offset)
1691+
1692+
{:ok, %DateTime{year: year, month: month, day: day,
1693+
hour: hour, minute: minute, second: second, microsecond: microsecond,
1694+
std_offset: 0, utc_offset: 0, zone_abbr: "UTC", time_zone: "Etc/UTC"}, offset}
1695+
else
1696+
{:error, reason} -> {:error, reason}
1697+
_ -> {:error, :invalid_format}
1698+
end
1699+
end
1700+
def from_iso8601(_) do
1701+
{:error, :invalid_format}
1702+
end
1703+
defp parse_offset(rest) do
1704+
case Calendar.ISO.parse_offset(rest) do
1705+
{offset, ""} when is_integer(offset) -> {:ok, offset}
1706+
{nil, ""} -> {:error, :missing_offset}
1707+
_ -> {:error, :invalid_format}
1708+
end
1709+
end
1710+
16331711
@doc """
16341712
Converts the given datetime to a string according to its calendar.
16351713

0 commit comments

Comments
 (0)