iso8601

Build Status

Coverage

Tag

Erlang Version

Downloads

An ISO 8601 date formatting and parsing library for Erlang

iso8601 project logo

Contents

About Back to contents

The erlang_iso8601 library was originally created by Sean Sawyer in 2011. In 2016, Sean handed off maintenance of the library to the erlsci Github org at which point the project (and repo) was renamed to simply iso8601, matching its Erlang app name:

Thanks to Github's forwarding for project renames and moves, the following still work:

New in 1.4 Back to contents

Usage Back to contents

Add it to your rebar.config deps:

{iso8601, "~> 1.4"}

Format a timestamp or calendar datetime tuple. Formatting labels the supplied tuple as UTC; it does not convert a local timezone. Timestamp conversion drops microseconds, while float seconds in a datetime are rounded to microseconds:

1> iso8601:format({0, 0, 0}).
<<"1970-01-01T00:00:00Z">>
2> iso8601:format({{2012, 2, 16}, {1, 6, 48}}).
<<"2012-02-16T01:06:48Z">>

Parse a date string or binary:

3> iso8601:parse(<<"2012-02-16T01:06:48Z">>).
{{2012,2,16},{1,6,48}}
4> iso8601:parse("2012-02-16T01:06:48Z").
{{2012,2,16},{1,6,48}}

The date, interval and duration parsers accept byte iodata, so strings, binaries, nested byte fragments and valid improper byte-iodata tails can be combined directly. Non-byte or otherwise invalid iodata raises badarg.

4a> iso8601:parse([<<"2012-">>, ["02", <<"-03">>]]).
{{2012,2,3},{0,0,0}}

Add 1 hour, 2 minutes and 3 seconds to a datetime tuple:

5> Datetime = iso8601:parse(<<"2012-02-16T01:06:48Z">>).
{{2012,2,16},{1,6,48}}
6> iso8601:add_time(Datetime, 1, 2, 3).
{{2012,2,16},{2,8,51}}

Subtract 1 hour, 2 minutes and 3 seconds from a datetime tuple:

5> Datetime = iso8601:parse(<<"2012-02-16T01:06:48Z">>).
{{2012,2,16},{1,6,48}}
6> iso8601:subtract_time(Datetime, 1, 2, 3).
{{2012,2,16},{0,4,45}}

Fractional times:

7> iso8601:parse("20120203T040506.50").
{{2012,2,3},{4,5,6}}
8> iso8601:parse_exact("20120203T040506.50").
{{2012,2,3},{4,5,6.50}}

Parse durations from nested byte iodata. Duration fields have fixed order and zero defaults; fractional seconds are accepted then truncated, while fractional hours/minutes and week units are unsupported:

9> iso8601:parse_duration("+P6Y3M1DT1H1M1.1S").
[{sign, "+"}, {years, 6}, {months, 3}, {days, 1}, {hours, 1}, {minutes, 1}, {seconds, 1}]
10> iso8601:parse_duration([<<"PT">>, ["6", <<"M">>]]).
[{sign, []}, {years, 0}, {months, 0}, {days, 0},{hours, 0}, {minutes, 6}, {seconds, 0}]

Calendar helpers add integer days, months and years. Month/year changes clamp to the last valid day of the target month and preserve time/fractional seconds:

iso8601:add_days({{2017, 1, 31}, {12, 0, 0.25}}, 1).
{{2017, 2, 1}, {12, 0, 0.25}}
iso8601:add_months({{2017, 1, 31}, {12, 0, 0.25}}, 1).
{{2017, 2, 28}, {12, 0, 0.25}}
iso8601:add_years({{2016, 2, 29}, {12, 0, 0.25}}, 1).
{{2017, 2, 28}, {12, 0, 0.25}}

Apply a duration with its sign and fields in years/months/days/time order. Each calendar step clamps independently; intermediate results below year zero raise badarg:

iso8601:apply_duration({{2017, 1, 31}, {12, 0, 0.25}}, <<"P1M">>).
{{2017, 2, 28}, {12, 0, 0.25}}

Parse a time interval. parse_interval/1 preserves which of the four forms was given, tagging the result so it stays matchable:

11> iso8601:parse_interval("2007-03-01T13:00:00Z/2008-05-11T15:30:00Z").
{interval,start_end,{{2007,3,1},{13,0,0}},{{2008,5,11},{15,30,0}}}
12> iso8601:parse_interval("2007-03-01T13:00:00Z/P1Y2M10DT2H30M").
{interval,start_duration,{{2007,3,1},{13,0,0}},
          [{sign,[]},{years,1},{months,2},{days,10},
           {hours,2},{minutes,30},{seconds,0}]}
13> iso8601:parse_interval("P1Y2M10DT2H30M/2008-05-11T15:30:00Z").
{interval,duration_end,
          [{sign,[]},{years,1},{months,2},{days,10},
           {hours,2},{minutes,30},{seconds,0}],
          {{2008,5,11},{15,30,0}}}
14> iso8601:parse_interval("P1Y2M10DT2H30M").
{interval,duration,
          [{sign,[]},{years,1},{months,2},{days,10},
           {hours,2},{minutes,30},{seconds,0}]}

An abbreviated end inherits the missing leading components from the start, and the -- separator is accepted as well as /:

15> iso8601:parse_interval("2007-11-13/15").
{interval,start_end,{{2007,11,13},{0,0,0}},{{2007,11,15},{0,0,0}}}
16> iso8601:parse_interval("2000--2002").
{interval,start_end,{{2000,1,1},{0,0,0}},{{2002,1,1},{0,0,0}}}

An explicit positive expanded year is retained when an abbreviated end inherits its year or month. For an abbreviated end without its own timezone, the start's timezone is also inherited after the end's local date and time have been completed. A complete endpoint remains independent, and an explicit end timezone takes precedence:

17> iso8601:parse_interval("+2007-11-13/15").
{interval,start_end,{{2007,11,13},{0,0,0}},{{2007,11,15},{0,0,0}}}
18> iso8601:interval_bounds(iso8601:parse_interval("2007-12-14T13:30:00+02:00/15:30:00")).
{{{2007,12,14},{11,30,0}},{{2007,12,14},{13,30,0}}}
19> iso8601:interval_bounds(iso8601:parse_interval("2007-12-14T13:30:00+02:00/2007-12-15T15:30:00")).
{{{2007,12,14},{11,30,0}},{{2007,12,15},{15,30,0}}}

Resolve an interval to concrete {Start, End} datetimes with interval_bounds/1 (the start/duration and duration/end forms are computed from the duration; a duration-only interval has no anchor and raises badarg):

20> iso8601:interval_bounds(iso8601:parse_interval("2007-03-01T13:00:00Z/P1Y2M10DT2H30M")).
{{{2007,3,1},{13,0,0}},{{2008,5,11},{15,30,0}}}

Format an interval back to a binary with format_interval/1:

21> iso8601:format_interval(iso8601:parse_interval("2007-03-01T13:00:00Z/P1Y2M10DT2H30M")).
<<"2007-03-01T13:00:00Z/P1Y2M10DT2H30M">>

Parse an expanded-representation year. Positive expanded years (an explicit + and any number of digits, e.g. years beyond 9999) work with parse/1, which stays calendar-compatible:

22> iso8601:parse("+0002007-01-01").
{{2007,1,1},{0,0,0}}
23> iso8601:parse("+10000-01-01").
{{10000,1,1},{0,0,0}}

Negative (astronomical) years — where year 0 is 1 BCE, -1 is 2 BCE, and so on — use parse_expanded/1 / format_expanded/1, which return an expanded_datetime() with an integer() year. parse/1 rejects negative years so its calendar-compatible contract is preserved:

24> iso8601:parse_expanded("-0001-01-01").
{{-1,1,1},{0,0,0.0}}
25> iso8601:format_expanded({{-44,3,15},{0,0,0}}).
<<"-0044-03-15T00:00:00Z">>

parse_expanded/1 preserves fractional seconds (like parse_exact/1), so the seconds field comes back as a float. Negative years are supported in UTC (Z) or offset-free form; combining a negative year with a non-zero UTC offset or a week-date raises badarg. At year zero or below, nonzero rounded time adjustments raise badarg; an adjustment that rounds to zero remains allowed.

Known Deficiencies Back to contents

See the open issues for more info.

Donating

A donation account for supporting development on this project has been set up on Liberapay here:

You can learn more about Liberapay on its Wikipedia entry or on the service's "About" page.

License Back to contents

The MIT License (MIT)

Copyright © 2011-2014, Sean Sawyer
Copyright © 2012, Tristan Sloughter
Copyright © 2016-2026, Erlang-Aided Enrichment Center