diff --git a/lib/elixir/lib/calendar/duration.ex b/lib/elixir/lib/calendar/duration.ex index ba1333a3908..cca7414710c 100644 --- a/lib/elixir/lib/calendar/duration.ex +++ b/lib/elixir/lib/calendar/duration.ex @@ -205,9 +205,14 @@ defmodule Duration do iex> Duration.parse("P1Y2M3DT4H5M6S") {:ok, %Duration{year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6}} - iex> Duration.parse("PT10H30M") {:ok, %Duration{hour: 10, minute: 30, second: 0}} + iex> Duration.parse("P3Y-2MT3H") + {:ok, %Duration{year: 3, month: -2, hour: 3}} + iex> Duration.parse("-P3Y2MT3H") + {:ok, %Duration{year: -3, month: -2, hour: -3}} + iex> Duration.parse("-P3Y-2MT3H") + {:ok, %Duration{year: -3, month: 2, hour: -3}} """ @spec parse(String.t()) :: {:ok, t} | {:error, String.t()} @@ -215,6 +220,16 @@ defmodule Duration do parse(duration_string, %{}, "", false) end + def parse("-P" <> duration_string) do + case parse(duration_string, %{}, "", false) do + {:ok, duration} -> + {:ok, negate(duration)} + + error -> + error + end + end + def parse(_) do {:error, "invalid duration string"} end @@ -227,9 +242,6 @@ defmodule Duration do iex> Duration.parse!("P1Y2M3DT4H5M6S") %Duration{year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6} - iex> Duration.parse!("PT10H30M") - %Duration{hour: 10, minute: 30, second: 0} - """ @spec parse!(String.t()) :: t def parse!(duration_string) do @@ -242,9 +254,10 @@ defmodule Duration do end end - defp parse(<<>>, duration, "", _), do: {:ok, new(Enum.into(duration, []))} + defp parse(<<>>, duration, "", _), do: {:ok, new!(Enum.into(duration, []))} - defp parse(<>, duration, buffer, is_time) when c in ?0..?9 or c == ?. do + defp parse(<>, duration, buffer, is_time) + when c in ?0..?9 or c in [?., ?-] do parse(rest, duration, <>, is_time) end diff --git a/lib/elixir/pages/getting-started/sigils.md b/lib/elixir/pages/getting-started/sigils.md index 3b13317e90f..d079d47db8c 100644 --- a/lib/elixir/pages/getting-started/sigils.md +++ b/lib/elixir/pages/getting-started/sigils.md @@ -207,7 +207,8 @@ iex> time_zone ### Duration -A [%Duration{}](`Duration`) struct represents a collection of time scale units, The `~P` sigil allows developers to create Durations from an ISO 8601-2 formatted duration string: +A [%Duration{}](`Duration`) struct represents a collection of time scale units. +The `~P` sigil allows developers to create Durations from an ISO 8601-2 formatted duration string: ```elixir iex> ~P[P1Y2M3DT4H5M6S] diff --git a/lib/elixir/test/elixir/calendar/duration_test.exs b/lib/elixir/test/elixir/calendar/duration_test.exs index 750345d5199..cbaa56f9c39 100644 --- a/lib/elixir/test/elixir/calendar/duration_test.exs +++ b/lib/elixir/test/elixir/calendar/duration_test.exs @@ -255,6 +255,10 @@ defmodule DurationTest do assert Duration.parse!("PT6S") == %Duration{second: 6} assert Duration.parse!("PT1.6S") == %Duration{second: 1, microsecond: {600_000, 6}} assert Duration.parse!("PT1.12345678S") == %Duration{second: 1, microsecond: {123_456, 6}} + assert Duration.parse!("P3Y4W-3DT-6S") == %Duration{year: 3, week: 4, day: -3, second: -6} + assert Duration.parse!("PT-4.23S") == %Duration{second: -4, microsecond: {-230_000, 6}} + assert Duration.parse!("-P3WT5H3M") == %Duration{week: -3, hour: -5, minute: -3} + assert Duration.parse!("-P-3WT5H3M") == %Duration{week: 3, hour: -5, minute: -3} assert_raise ArgumentError, ~s/failed to parse duration. reason: "unexpected character: H"/,