iso8601 (iso8601 v1.4.1)

View Source

Parse and format ISO 8601 dates, times, durations and intervals.

The ordinary APIs use calendar-style {Date, Time} tuples and return binaries from formatters. Date-like input is byte iodata. The legacy parser truncates explicit fractional seconds; parse_exact/1 and the expanded parser retain floating seconds. Arithmetic preserves an anchor fraction, clamps month/year results, and observes the library's year-zero boundary. Expanded dates support selected astronomical-year forms, while interval parsing supports implemented endpoint and abbreviation forms rather than every ISO 8601 grammar production.

  {{2012, 2, 3}, {0, 0, 0}} = iso8601:parse("2012-02-03").

Summary

Types

Calendar-style day component; validity depends on the selected month/year.

Fixed-order duration fields: sign, years, months, days, hours, minutes and seconds. Values default to zero; sign is [], "+" or "-".

Expanded calendar datetime; seconds may be floating point to retain a fractional part.

Astronomical year integer, including negative years; year 0 is 1 BCE and year -1 is 2 BCE.

Calendar-style hour component.

One of four parsed interval shapes: concrete start/end, start/duration, duration/end, or unanchored duration. Bounds require a concrete anchor.

Calendar-style minute component.

Calendar-style month component.

Calendar-style whole-second component. Datetime tuples separately permit floating-point seconds for fractional precision.

calendar:now_to_datetime/1-style timestamp. Conversion to calendar datetime drops the timestamp's microsecond component.

Calendar-style year component; expanded negative years use expanded_year().

Functions

Add an integer number of days to a calendar datetime or timestamp. Time and fractional seconds are preserved; a result before year zero raises badarg.

Add an integer number of months to a calendar datetime or timestamp. The day is clamped to the last valid day of the target month, with time and fractional seconds preserved. Results before year zero raise badarg.

Add numeric hours, minutes and seconds to a calendar datetime or timestamp. The combined shift is rounded to a whole second; the anchor's fractional second is preserved. Year-zero inputs reject a nonzero rounded shift, and results before year zero raise badarg.

Add an integer number of years to a calendar datetime or timestamp. The day is clamped to the last valid day of the target month; year zero is a valid result and time/fractional seconds are preserved. A target before year zero raises badarg.

Apply byte-iodata duration text to a calendar datetime. A negative sign subtracts the duration. Years, months, days and then rounded time are applied in order, preserving the anchor's fractional second. Month/year stages clamp; a below-floor intermediate result raises badarg even if later fields could move forward.

Format a calendar datetime or timestamp as a binary labeled UTC with a trailing Z; a local tuple is labeled, not timezone-converted. Timestamp conversion uses calendar:now_to_datetime/1 and drops microseconds. Integer seconds omit decimals; float seconds round to six microsecond digits and carry into the next second/date when needed. Negative years need format_expanded/1.

Format an expanded datetime as a binary with an explicit year sign and at least four year digits, labeled UTC with Z. Float seconds are rounded to six microsecond digits with carry. Negative astronomical years are supported.

Format a structured interval as binary text. Concrete endpoints are formatted from their normalized datetime values; zero duration fields are omitted and a zero duration can format as bare P. Original spelling, abbreviation and zone text are not retained.

Resolve an anchored interval to concrete {Start, End} datetimes. A duration-only interval has no anchor and raises badarg.

Parse byte iodata in the implemented calendar, ordinal, week and reduced-date/time forms, returning a calendar-style datetime. Recognized offsets normalize to UTC; missing calendar fields default to month/day 1 and time 0. Legacy parsing rounds fractional hours/minutes to whole seconds but truncates explicit fractional seconds. Date/week fields are validated, though this is not a complete ISO grammar or strict clock validator. Invalid byte iodata and specific invalid date/week values raise badarg. Explicit positive expanded years with a leading + are accepted; negative years raise badarg.

Parse a duration from byte iodata, including strings, binaries, nested byte fragments and valid improper byte-iodata tails. The supported order is optional sign then Y/M/D and T H/M/S fields. The result is a fixed-order plist with zero defaults. Fractional seconds are accepted but truncated to their integer part; fractional hours/minutes and week units are unsupported. Bare P and PT are accepted as zero durations. Invalid or non-byte iodata raises badarg.

Parse the same implemented date/time forms as parse/1, preserving fractional seconds as floating point rather than truncating explicit fractions. This is binary floating-point precision, not exact decimal arithmetic; results normally use float seconds even when the fraction is zero. Offset-free input and Z need no shift, while recognized numeric offsets normalize to UTC. Explicit positive expanded years are accepted; negative years are rejected by this positive-year parser.

Parse implemented calendar, ordinal, week and reduced forms from byte iodata into an expanded datetime. Negative astronomical years are allowed for calendar/ordinal and offset-free or UTC forms; negative week dates and nonzero rounded adjustments at year zero or below raise badarg. Recognized offsets normalize to UTC. Fractional seconds are retained as floating point. Positive explicitly signed expanded years are also accepted by parse/1 and parse_exact/1; those positive-year APIs reject negative years.

Parse byte iodata into one of four interval shapes: start/end, start/duration, duration/end, or duration-only. / and -- separators, signed durations and implemented abbreviated endpoints are supported. Complete endpoints are independent; omitted end-zone inheritance completes the end's raw local date/time before applying the start zone; an explicit end zone takes precedence. Negative endpoints, repeating intervals and unsupported/ambiguous fragments are rejected. No general endpoint-order validation is promised.

Subtract numeric hours, minutes and seconds from a calendar datetime. The combined shift is rounded to a whole second, retaining the anchor's fractional second. The API does not accept timestamp tuples; a result before year zero raises badarg. At year zero a nonzero rounded shift also raises badarg; components that cancel or round to zero keep the zero-shift result.

Types

datetime/0

-type datetime() :: {calendar:date(), {hour(), minute(), second() | float()}}.

datetime_plist/0

-type datetime_plist() :: [{atom(), integer() | string()}].

day/0

-type day() :: 1..31.

Calendar-style day component; validity depends on the selected month/year.

duration/0

-type duration() :: datetime_plist().

Fixed-order duration fields: sign, years, months, days, hours, minutes and seconds. Values default to zero; sign is [], "+" or "-".

expanded_datetime/0

-type expanded_datetime() :: {{expanded_year(), month(), day()}, {hour(), minute(), second() | float()}}.

Expanded calendar datetime; seconds may be floating point to retain a fractional part.

expanded_year/0

-type expanded_year() :: integer().

Astronomical year integer, including negative years; year 0 is 1 BCE and year -1 is 2 BCE.

hour/0

-type hour() :: 0..23.

Calendar-style hour component.

interval/0

-type interval() ::
          {interval, start_end, datetime(), datetime()} |
          {interval, start_duration, datetime(), duration()} |
          {interval, duration_end, duration(), datetime()} |
          {interval, duration, duration()}.

One of four parsed interval shapes: concrete start/end, start/duration, duration/end, or unanchored duration. Bounds require a concrete anchor.

minute/0

-type minute() :: 0..59.

Calendar-style minute component.

month/0

-type month() :: 1..12.

Calendar-style month component.

parser_accumulator/0

-type parser_accumulator() :: [{atom(), integer() | float() | string() | parser_mode()}].

parser_mode/0

-type parser_mode() :: legacy | exact.

second/0

-type second() :: 0..59.

Calendar-style whole-second component. Datetime tuples separately permit floating-point seconds for fractional precision.

timestamp/0

-type timestamp() :: {MegaSecs :: integer(), Secs :: integer(), MicroSecs :: integer() | float()}.

calendar:now_to_datetime/1-style timestamp. Conversion to calendar datetime drops the timestamp's microsecond component.

undefined_or/1

-type undefined_or(A) :: undefined | A.

year/0

-type year() :: non_neg_integer().

Calendar-style year component; expanded negative years use expanded_year().

Functions

add_days(Timestamp, D)

-spec add_days(datetime() | timestamp(), integer()) -> datetime().

Add an integer number of days to a calendar datetime or timestamp. Time and fractional seconds are preserved; a result before year zero raises badarg.

  {{2017, 2, 1}, {0, 0, 0}} = iso8601:add_days({{2017, 1, 31}, {0, 0, 0}}, 1).

add_months(Timestamp, M)

-spec add_months(datetime() | timestamp(), integer()) -> datetime().

Add an integer number of months to a calendar datetime or timestamp. The day is clamped to the last valid day of the target month, with time and fractional seconds preserved. Results before year zero raise badarg.

  {{2017, 2, 28}, {12, 0, 0.25}} =
      iso8601:add_months({{2017, 1, 31}, {12, 0, 0.25}}, 1).

add_time(Timestamp, H, M, S)

-spec add_time(datetime() | timestamp(), number(), number(), number()) -> datetime().

Add numeric hours, minutes and seconds to a calendar datetime or timestamp. The combined shift is rounded to a whole second; the anchor's fractional second is preserved. Year-zero inputs reject a nonzero rounded shift, and results before year zero raise badarg.

  {{1970, 1, 1}, {1, 0, 0}} = iso8601:add_time({0, 0, 0}, 1, 0, 0).
  {{2017, 1, 1}, {0, 0, 1}} =
      iso8601:add_time({{2017, 1, 1}, {0, 0, 0}}, 0, 0, 0.5).

add_years(Timestamp, Y)

-spec add_years(datetime() | timestamp(), integer()) -> datetime().

Add an integer number of years to a calendar datetime or timestamp. The day is clamped to the last valid day of the target month; year zero is a valid result and time/fractional seconds are preserved. A target before year zero raises badarg.

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

apply_duration(Datetime, Duration)

-spec apply_duration(datetime(), iodata()) -> datetime().

Apply byte-iodata duration text to a calendar datetime. A negative sign subtracts the duration. Years, months, days and then rounded time are applied in order, preserving the anchor's fractional second. Month/year stages clamp; a below-floor intermediate result raises badarg even if later fields could move forward.

  {{2017, 2, 28}, {12, 0, 0.25}} =
      iso8601:apply_duration({{2017, 1, 31}, {12, 0, 0.25}}, [$P, $1 | <<"M">>]).

format(Timestamp)

-spec format(datetime() | timestamp()) -> binary().

Format a calendar datetime or timestamp as a binary labeled UTC with a trailing Z; a local tuple is labeled, not timezone-converted. Timestamp conversion uses calendar:now_to_datetime/1 and drops microseconds. Integer seconds omit decimals; float seconds round to six microsecond digits and carry into the next second/date when needed. Negative years need format_expanded/1.

  <<"1970-01-01T00:00:00Z">> = iso8601:format({0, 0, 0}).

format_expanded(_)

-spec format_expanded(expanded_datetime()) -> binary().

Format an expanded datetime as a binary with an explicit year sign and at least four year digits, labeled UTC with Z. Float seconds are rounded to six microsecond digits with carry. Negative astronomical years are supported.

  <<"-0044-03-15T00:00:00Z">> =
      iso8601:format_expanded({{-44, 3, 15}, {0, 0, 0}}).

format_interval(_)

-spec format_interval(interval()) -> binary().

Format a structured interval as binary text. Concrete endpoints are formatted from their normalized datetime values; zero duration fields are omitted and a zero duration can format as bare P. Original spelling, abbreviation and zone text are not retained.

  Parsed = iso8601:parse_interval("2017-01-01/P1D"),
  <<"2017-01-01T00:00:00Z/P1D">> = iso8601:format_interval(Parsed).

interval_bounds(_)

-spec interval_bounds(interval()) -> {datetime(), datetime()}.

Resolve an anchored interval to concrete {Start, End} datetimes. A duration-only interval has no anchor and raises badarg.

  {{{2006, 12, 31}, {22, 30, 0}}, {{2006, 12, 31}, {22, 45, 0}}} =
      iso8601:interval_bounds(iso8601:parse_interval(
          "2007-01-01T00:30:00+02:00/00:45:00")).

parse(Input)

-spec parse(iodata()) -> calendar:datetime().

Parse byte iodata in the implemented calendar, ordinal, week and reduced-date/time forms, returning a calendar-style datetime. Recognized offsets normalize to UTC; missing calendar fields default to month/day 1 and time 0. Legacy parsing rounds fractional hours/minutes to whole seconds but truncates explicit fractional seconds. Date/week fields are validated, though this is not a complete ISO grammar or strict clock validator. Invalid byte iodata and specific invalid date/week values raise badarg. Explicit positive expanded years with a leading + are accepted; negative years raise badarg.

  {{2012, 2, 3}, {0, 0, 0}} = iso8601:parse([<<"2012-">>, ["02", <<"-03">>]]).
  {{2012, 2, 3}, {4, 5, 6}} = iso8601:parse("2012-02-03T04:05:06.50").

parse_duration(Bin)

-spec parse_duration(iodata()) -> duration().

Parse a duration from byte iodata, including strings, binaries, nested byte fragments and valid improper byte-iodata tails. The supported order is optional sign then Y/M/D and T H/M/S fields. The result is a fixed-order plist with zero defaults. Fractional seconds are accepted but truncated to their integer part; fractional hours/minutes and week units are unsupported. Bare P and PT are accepted as zero durations. Invalid or non-byte iodata raises badarg.

  [{sign, []}, {years, 0}, {months, 0}, {days, 0}, {hours, 0},
   {minutes, 0}, {seconds, 1}] =
      iso8601:parse_duration([<<"PT">>, ["1.9", <<"S">>]]).

parse_exact(Input)

-spec parse_exact(iodata()) -> datetime().

Parse the same implemented date/time forms as parse/1, preserving fractional seconds as floating point rather than truncating explicit fractions. This is binary floating-point precision, not exact decimal arithmetic; results normally use float seconds even when the fraction is zero. Offset-free input and Z need no shift, while recognized numeric offsets normalize to UTC. Explicit positive expanded years are accepted; negative years are rejected by this positive-year parser.

  {{2012, 2, 3}, {4, 5, 6.5}} = iso8601:parse_exact("2012-02-03T04:05:06.50").
  true =
      (abs(element(3, element(2, iso8601:parse_exact("2012-01-01T00.0001"))) - 0.36) <
          1.0e-12).

parse_expanded(Input)

-spec parse_expanded(iodata()) -> expanded_datetime().

Parse implemented calendar, ordinal, week and reduced forms from byte iodata into an expanded datetime. Negative astronomical years are allowed for calendar/ordinal and offset-free or UTC forms; negative week dates and nonzero rounded adjustments at year zero or below raise badarg. Recognized offsets normalize to UTC. Fractional seconds are retained as floating point. Positive explicitly signed expanded years are also accepted by parse/1 and parse_exact/1; those positive-year APIs reject negative years.

  {{-1, 1, 1}, {0, 0, 0.0}} = iso8601:parse_expanded("-0001-01-01").

parse_interval(Input)

-spec parse_interval(iodata()) -> interval().

Parse byte iodata into one of four interval shapes: start/end, start/duration, duration/end, or duration-only. / and -- separators, signed durations and implemented abbreviated endpoints are supported. Complete endpoints are independent; omitted end-zone inheritance completes the end's raw local date/time before applying the start zone; an explicit end zone takes precedence. Negative endpoints, repeating intervals and unsupported/ambiguous fragments are rejected. No general endpoint-order validation is promised.

  {interval, start_end, {{2007, 2, 15}, {0, 0, 0}},
   {{2007, 3, 14}, {0, 0, 0}}} = iso8601:parse_interval("+2007-02-15/03-14").

subtract_time(Datetime, H, M, S)

-spec subtract_time(datetime(), number(), number(), number()) -> datetime().

Subtract numeric hours, minutes and seconds from a calendar datetime. The combined shift is rounded to a whole second, retaining the anchor's fractional second. The API does not accept timestamp tuples; a result before year zero raises badarg. At year zero a nonzero rounded shift also raises badarg; components that cancel or round to zero keep the zero-shift result.

  {{2016, 12, 31}, {23, 59, 59.25}} =
      iso8601:subtract_time({{2017, 1, 1}, {0, 0, 0.25}}, 0, 0, 1).