summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKip Cole <kipcole9@gmail.com>2018-11-25 17:26:16 +0800
committerJosé Valim <jose.valim@plataformatec.com.br>2018-12-04 12:28:58 +0100
commitedd2c6f780532f6bed4addfd087a06172cc679b1 (patch)
treeb0a022437fa613e3029c23a9a5bf8ea3f9e14a28
parent74d23f2a38ae93f2eb44afe54ac45f70fbe866eb (diff)
downloadelixir-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.ex31
-rw-r--r--lib/elixir/lib/calendar/date.ex102
-rw-r--r--lib/elixir/lib/calendar/iso.ex117
-rw-r--r--lib/elixir/test/elixir/calendar/date_test.exs1
-rw-r--r--lib/elixir/test/elixir/calendar/holocene.exs12
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