diff options
author | Kip Cole <kipcole9@gmail.com> | 2018-11-25 17:26:16 +0800 |
---|---|---|
committer | José Valim <jose.valim@plataformatec.com.br> | 2018-12-04 12:28:58 +0100 |
commit | edd2c6f780532f6bed4addfd087a06172cc679b1 (patch) | |
tree | b0a022437fa613e3029c23a9a5bf8ea3f9e14a28 | |
parent | 74d23f2a38ae93f2eb44afe54ac45f70fbe866eb (diff) | |
download | elixir-edd2c6f780532f6bed4addfd087a06172cc679b1.tar.gz |
Add Calendar callbacks and Calendar.ISO implementations
* day_of_year/3
* quarter_of_year/3
* year_of_era/1
* day_of_era/3
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
-rw-r--r-- | lib/elixir/lib/calendar.ex | 31 | ||||
-rw-r--r-- | lib/elixir/lib/calendar/date.ex | 102 | ||||
-rw-r--r-- | lib/elixir/lib/calendar/iso.ex | 117 | ||||
-rw-r--r-- | lib/elixir/test/elixir/calendar/date_test.exs | 1 | ||||
-rw-r--r-- | lib/elixir/test/elixir/calendar/holocene.exs | 12 |
5 files changed, 257 insertions, 6 deletions
diff --git a/lib/elixir/lib/calendar.ex b/lib/elixir/lib/calendar.ex index 0068565cd..cb0279c66 100644 --- a/lib/elixir/lib/calendar.ex +++ b/lib/elixir/lib/calendar.ex @@ -17,11 +17,12 @@ defmodule Calendar do """ @type year :: integer - @type month :: integer - @type day :: integer - @type hour :: integer - @type minute :: integer - @type second :: integer + @type month :: pos_integer + @type day :: pos_integer + @type hour :: non_neg_integer + @type minute :: non_neg_integer + @type second :: non_neg_integer + @type era :: non_neg_integer @typedoc """ The internal time format is used when converting between calendars. @@ -154,6 +155,26 @@ defmodule Calendar do @callback day_of_week(year, month, day) :: non_neg_integer() @doc """ + Calculates the day of the year from the given `year`, `month`, and `day`. + """ + @callback day_of_year(year, month, day) :: non_neg_integer() + + @doc """ + Calculates the quarter of the year from the given `year`, `month`, and `day`. + """ + @callback quarter_of_year(year, month, day) :: non_neg_integer() + + @doc """ + Calculates the year and era from the given `year`. + """ + @callback year_of_era(year) :: {year, era} + + @doc """ + Calculates the day and era from the given `year`, `month`, and `day`. + """ + @callback day_of_era(year, month, day) :: {day, era} + + @doc """ Converts the date into a string according to the calendar. """ @callback date_to_string(year, month, day) :: String.t() diff --git a/lib/elixir/lib/calendar/date.ex b/lib/elixir/lib/calendar/date.ex index 1eb88fbab..690cc0a07 100644 --- a/lib/elixir/lib/calendar/date.ex +++ b/lib/elixir/lib/calendar/date.ex @@ -652,13 +652,113 @@ defmodule Date do """ @doc since: "1.4.0" - @spec day_of_week(Calendar.date()) :: non_neg_integer() + @spec day_of_week(Calendar.date()) :: Calendar.day() def day_of_week(date) def day_of_week(%{calendar: calendar, year: year, month: month, day: day}) do calendar.day_of_week(year, month, day) end + @doc """ + Calculates the day of the year of a given `date`. + + Returns the day of the year as an integer. For the ISO 8601 + calendar (the default), it is an integer from 1 to 366. + + ## Examples + + iex> Date.day_of_year(~D[2016-01-01]) + 1 + iex> Date.day_of_year(~D[2016-11-01]) + 306 + iex> Date.day_of_year(~D[-0015-10-30]) + 303 + iex> Date.day_of_year(~D[2004-12-31]) + 366 + + """ + @doc since: "1.8.0" + @spec day_of_year(Calendar.date()) :: Calendar.day() + def day_of_year(date) + + def day_of_year(%{calendar: calendar, year: year, month: month, day: day}) do + calendar.day_of_year(year, month, day) + end + + @doc """ + Calculates the quarter of the year of a given `date`. + + Returns the day of the year as an integer. For the ISO 8601 + calendar (the default), it is an integer from 1 to 4. + + ## Examples + + iex> Date.quarter_of_year(~D[2016-10-31]) + 4 + iex> Date.quarter_of_year(~D[2016-01-01]) + 1 + iex> Date.quarter_of_year(~N[2016-04-01 01:23:45]) + 2 + iex> Date.quarter_of_year(~D[-0015-09-30]) + 3 + + """ + @doc since: "1.8.0" + @spec quarter_of_year(Calendar.date()) :: non_neg_integer() + def quarter_of_year(date) + + def quarter_of_year(%{calendar: calendar, year: year, month: month, day: day}) do + calendar.quarter_of_year(year, month, day) + end + + @doc """ + Calculates the year-of-era and era for a given + calendar year. + + Returns a tuple `{year, era}` representing the + year within the era and the era number. + + ## Examples + + iex> Date.year_of_era(~D[0001-01-01]) + {1, 1} + iex> Date.year_of_era(~D[0000-12-31]) + {1, 0} + iex> Date.year_of_era(~D[-0001-01-01]) + {2, 0} + + """ + @doc since: "1.8.0" + @spec year_of_era(Calendar.date()) :: {Calendar.year(), non_neg_integer()} + def year_of_era(date) + + def year_of_era(%{calendar: calendar, year: year}) do + calendar.year_of_era(year) + end + + @doc """ + Calculates the day-of-era and era for a given + calendar `date`. + + Returns a tuple `{day, era}` representing the + day within the era and the era number. + + ## Examples + + iex> Date.day_of_era(~D[0001-01-01]) + {1, 1} + iex> Date.day_of_era(~D[0000-12-31]) + {1, 0} + + """ + @doc since: "1.8.0" + @spec day_of_era(Calendar.date()) :: {Calendar.day(), non_neg_integer()} + def day_of_era(date) + + def day_of_era(%{calendar: calendar, year: year, month: month, day: day}) do + calendar.day_of_era(year, month, day) + end + ## Helpers defimpl String.Chars do diff --git a/lib/elixir/lib/calendar/iso.ex b/lib/elixir/lib/calendar/iso.ex index 370f70884..0e5d52411 100644 --- a/lib/elixir/lib/calendar/iso.ex +++ b/lib/elixir/lib/calendar/iso.ex @@ -37,6 +37,11 @@ defmodule Calendar.ISO do @months_in_year 12 + # The ISO epoch starts, in this implementation, + # with ~D[0000-01-01]. Era "1" starts + # on ~D[0001-01-01] which is 366 days later. + @iso_epoch 366 + @doc false def __match_date__ do quote do @@ -338,6 +343,118 @@ defmodule Calendar.ISO do end @doc """ + Calculates the day of the year from the given `year`, `month`, and `day`. + + It is an integer from 1 to 366. + + ## Examples + + iex> Calendar.ISO.day_of_year(2016, 1, 31) + 31 + iex> Calendar.ISO.day_of_year(-99, 2, 1) + 32 + iex> Calendar.ISO.day_of_year(2018, 2, 28) + 59 + + """ + @doc since: "1.8.0" + @spec day_of_year(year, month, day) :: 1..366 + @impl true + def day_of_year(year, month, day) + when is_integer(year) and is_integer(month) and is_integer(day) do + true = day <= days_in_month(year, month) + days_before_month(month) + leap_day_offset(year, month) + day + end + + @doc """ + Calculates the quarter of the year from the given `year`, `month`, and `day`. + + It is an integer from 1 to 4. + + ## Examples + + iex> Calendar.ISO.quarter_of_year(2016, 1, 31) + 1 + iex> Calendar.ISO.quarter_of_year(2016, 4, 3) + 2 + iex> Calendar.ISO.quarter_of_year(-99, 9, 31) + 3 + iex> Calendar.ISO.quarter_of_year(2018, 12, 28) + 4 + + """ + @doc since: "1.8.0" + @spec quarter_of_year(year, month, day) :: 1..4 + @impl true + def quarter_of_year(year, month, day) + when is_integer(year) and is_integer(month) and is_integer(day) do + div(month - 1, 3) + 1 + end + + @doc """ + Calculates the year and era from the given `year`. + + The ISO calendar has two eras: the current era which + starts in year 1 and is defined as era "1". And a + second era for those years less than 1 defined as + era "0". + + ## Examples + + iex> Calendar.ISO.year_of_era(1) + {1, 1} + iex> Calendar.ISO.year_of_era(2018) + {2018, 1} + iex> Calendar.ISO.year_of_era(0) + {1, 0} + iex> Calendar.ISO.year_of_era(-1) + {2, 0} + + """ + @doc since: "1.8.0" + @spec year_of_era(year) :: {year, era :: 0..1} + @impl true + def year_of_era(year) when is_integer(year) and year > 0 do + {year, 1} + end + + def year_of_era(year) when is_integer(year) and year < 1 do + {abs(year) + 1, 0} + end + + @doc """ + Calculates the day and era from the given `year`, `month`, and `day`. + + ## Examples + + iex> Calendar.ISO.day_of_era(0, 1, 1) + {366, 0} + iex> Calendar.ISO.day_of_era(1, 1, 1) + {1, 1} + iex> Calendar.ISO.day_of_era(0, 12, 31) + {1, 0} + iex> Calendar.ISO.day_of_era(0, 12, 30) + {2, 0} + iex> Calendar.ISO.day_of_era(-1, 12, 31) + {367, 0} + + """ + @doc since: "1.8.0" + @spec day_of_era(year, month, day) :: {day :: pos_integer(), era :: 0..1} + @impl true + def day_of_era(year, month, day) + when is_integer(year) and is_integer(month) and is_integer(day) and year > 0 do + day = date_to_iso_days(year, month, day) - @iso_epoch + 1 + {day, 1} + end + + def day_of_era(year, month, day) + when is_integer(year) and is_integer(month) and is_integer(day) and year < 1 do + day = abs(date_to_iso_days(year, month, day) - @iso_epoch) + {day, 0} + end + + @doc """ Converts the given time into a string. ## Examples diff --git a/lib/elixir/test/elixir/calendar/date_test.exs b/lib/elixir/test/elixir/calendar/date_test.exs index 6757e1216..be90cf5a6 100644 --- a/lib/elixir/test/elixir/calendar/date_test.exs +++ b/lib/elixir/test/elixir/calendar/date_test.exs @@ -51,6 +51,7 @@ defmodule DateTest do assert Date.day_of_week(~D[2016-11-04]) == 5 assert Date.day_of_week(~D[2016-11-05]) == 6 assert Date.day_of_week(~D[2016-11-06]) == 7 + assert Date.day_of_week(~D[2016-11-07]) == 1 end test "convert/2" do diff --git a/lib/elixir/test/elixir/calendar/holocene.exs b/lib/elixir/test/elixir/calendar/holocene.exs index 7ce93ca67..7d0be4a94 100644 --- a/lib/elixir/test/elixir/calendar/holocene.exs +++ b/lib/elixir/test/elixir/calendar/holocene.exs @@ -97,6 +97,18 @@ defmodule Calendar.Holocene do defdelegate day_of_week(year, month, day), to: Calendar.ISO @impl true + defdelegate day_of_year(year, month, day), to: Calendar.ISO + + @impl true + defdelegate quarter_of_year(year, month, day), to: Calendar.ISO + + @impl true + defdelegate year_of_era(year), to: Calendar.ISO + + @impl true + defdelegate day_of_era(year, month, day), to: Calendar.ISO + + @impl true def valid_date?(year, month, day) do :calendar.valid_date(year, month, day) end |