@@ -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