summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosé Valim <jose.valim@gmail.com>2018-12-04 23:18:50 +0100
committerGitHub <noreply@github.com>2018-12-04 23:18:50 +0100
commitb8a09a5c4daab273a9e0f2598c77b5c8133171ca (patch)
tree92b38af1ba313e73228dc997b798dc6f5a946ba8
parent00a67e3eea5ed663d13c5b01d54915f9fb26259a (diff)
downloadelixir-b8a09a5c4daab273a9e0f2598c77b5c8133171ca.tar.gz
Add week_of_year/3 (#8460)
-rw-r--r--lib/elixir/lib/calendar.ex12
-rw-r--r--lib/elixir/lib/calendar/date.ex36
-rw-r--r--lib/elixir/lib/calendar/iso.ex123
-rw-r--r--lib/elixir/test/elixir/calendar/holocene.exs3
4 files changed, 171 insertions, 3 deletions
diff --git a/lib/elixir/lib/calendar.ex b/lib/elixir/lib/calendar.ex
index cb0279c66..42db68e77 100644
--- a/lib/elixir/lib/calendar.ex
+++ b/lib/elixir/lib/calendar.ex
@@ -19,10 +19,13 @@ defmodule Calendar do
@type year :: integer
@type month :: pos_integer
@type day :: pos_integer
+ @type week :: pos_integer
+ @type day_of_week :: non_neg_integer
+ @type era :: non_neg_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.
@@ -152,7 +155,12 @@ defmodule Calendar do
@doc """
Calculates the day of the week from the given `year`, `month`, and `day`.
"""
- @callback day_of_week(year, month, day) :: non_neg_integer()
+ @callback day_of_week(year, month, day) :: day_of_week()
+
+ @doc """
+ Calculates the week of year from the given `year`, `month`, and `day`.
+ """
+ @callback week_of_year(year, month, day) :: {year(), week(), day_of_week()}
@doc """
Calculates the day of the year from the given `year`, `month`, and `day`.
diff --git a/lib/elixir/lib/calendar/date.ex b/lib/elixir/lib/calendar/date.ex
index 690cc0a07..40bddcd16 100644
--- a/lib/elixir/lib/calendar/date.ex
+++ b/lib/elixir/lib/calendar/date.ex
@@ -660,6 +660,42 @@ defmodule Date do
end
@doc """
+ Calculates the week of year of a given `date`.
+
+ It returns a tuple with the year, the week, and the day of the week.
+ For the ISO 8601 calendar (the default), the first week of the
+ year is the first one that has a Thursday.
+
+ ## Examples
+
+ iex> Date.week_of_year(~D[2016-01-01])
+ {2015, 53, 5}
+ iex> Date.week_of_year(~D[2016-01-04])
+ {2016, 1, 1}
+
+ iex> Date.week_of_year(~D[2018-12-31])
+ {2019, 1, 1}
+ iex> Date.week_of_year(~D[2019-01-01])
+ {2019, 1, 2}
+
+ iex> Date.week_of_year(~D[2016-11-01])
+ {2016, 44, 2}
+ iex> Date.week_of_year(~D[-0015-10-30])
+ {-15, 44, 3}
+ iex> Date.week_of_year(~D[2004-12-31])
+ {2004, 53, 5}
+
+ """
+ @doc since: "1.8.0"
+ @spec week_of_year(Calendar.date()) ::
+ {Calendar.year(), Calendar.week(), Calendar.day_of_week()}
+ def week_of_year(date)
+
+ def week_of_year(%{calendar: calendar, year: year, month: month, day: day}) do
+ calendar.week_of_year(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
diff --git a/lib/elixir/lib/calendar/iso.ex b/lib/elixir/lib/calendar/iso.ex
index 0e5d52411..f66dcb3a2 100644
--- a/lib/elixir/lib/calendar/iso.ex
+++ b/lib/elixir/lib/calendar/iso.ex
@@ -339,7 +339,128 @@ defmodule Calendar.ISO do
@impl true
def day_of_week(year, month, day)
when is_integer(year) and is_integer(month) and is_integer(day) do
- Integer.mod(date_to_iso_days(year, month, day) + 5, 7) + 1
+ iso_days_to_day_of_week(date_to_iso_days(year, month, day))
+ end
+
+ defp iso_days_to_day_of_week(iso_days) do
+ Integer.mod(iso_days + 5, 7) + 1
+ end
+
+ @doc """
+ Returns the week of a given date according to ISO.
+
+ ## Examples
+
+ # First day of the year is a Monday
+ iex> Calendar.ISO.week_of_year(2017, 12, 31)
+ {2017, 52, 7}
+ iex> Calendar.ISO.week_of_year(2018, 1, 1)
+ {2018, 1, 1}
+ iex> Calendar.ISO.week_of_year(2018, 1, 2)
+ {2018, 1, 2}
+
+ # First day of the year is a Tuesday
+ iex> Calendar.ISO.week_of_year(2018, 12, 31)
+ {2019, 1, 1}
+ iex> Calendar.ISO.week_of_year(2019, 1, 1)
+ {2019, 1, 2}
+ iex> Calendar.ISO.week_of_year(2019, 1, 2)
+ {2019, 1, 3}
+
+ # First day of the year is a Wednesday
+ iex> Calendar.ISO.week_of_year(2013, 12, 29)
+ {2013, 52, 7}
+ iex> Calendar.ISO.week_of_year(2013, 12, 30)
+ {2014, 1, 1}
+ iex> Calendar.ISO.week_of_year(2013, 12, 31)
+ {2014, 1, 2}
+ iex> Calendar.ISO.week_of_year(2014, 1, 1)
+ {2014, 1, 3}
+ iex> Calendar.ISO.week_of_year(2014, 1, 2)
+ {2014, 1, 4}
+
+ # First day of the year is a Thursday
+ iex> Calendar.ISO.week_of_year(2014, 12, 28)
+ {2014, 52, 7}
+ iex> Calendar.ISO.week_of_year(2014, 12, 29)
+ {2015, 1, 1}
+ iex> Calendar.ISO.week_of_year(2014, 12, 30)
+ {2015, 1, 2}
+ iex> Calendar.ISO.week_of_year(2014, 12, 31)
+ {2015, 1, 3}
+ iex> Calendar.ISO.week_of_year(2015, 1, 1)
+ {2015, 1, 4}
+ iex> Calendar.ISO.week_of_year(2015, 1, 2)
+ {2015, 1, 5}
+
+ # First day of the year is a Friday
+ iex> Calendar.ISO.week_of_year(2016, 1, 1)
+ {2015, 53, 5}
+ iex> Calendar.ISO.week_of_year(2016, 1, 2)
+ {2015, 53, 6}
+ iex> Calendar.ISO.week_of_year(2016, 1, 3)
+ {2015, 53, 7}
+ iex> Calendar.ISO.week_of_year(2016, 1, 4)
+ {2016, 1, 1}
+
+ # First day of the year is a Saturday
+ iex> Calendar.ISO.week_of_year(2011, 1, 1)
+ {2010, 52, 6}
+ iex> Calendar.ISO.week_of_year(2011, 1, 2)
+ {2010, 52, 7}
+ iex> Calendar.ISO.week_of_year(2011, 1, 3)
+ {2011, 1, 1}
+
+ # First day of the year is a Sunday
+ iex> Calendar.ISO.week_of_year(2017, 1, 1)
+ {2016, 52, 7}
+ iex> Calendar.ISO.week_of_year(2017, 1, 2)
+ {2017, 1, 1}
+
+ Any other date:
+
+ iex> Calendar.ISO.week_of_year(2018, 12, 4)
+ {2018, 49, 2}
+
+ """
+ @doc since: "1.8.0"
+ @spec week_of_year(year, month, day) :: {year, 1..53, 1..7}
+ @impl true
+ def week_of_year(year, month, day) do
+ true = day <= days_in_month(year, month)
+ {first_day_of_year, first_week_day_of_year} = first_day_and_week_day_of_year_in_iso_days(year)
+
+ iso_days =
+ first_day_of_year + days_before_month(month) + leap_day_offset(year, month) + day - 1
+
+ day_of_week = iso_days_to_day_of_week(iso_days)
+
+ cond do
+ # If we are in a monday, tuesday or wednesday where the thursday
+ # is in the next year, so we are already in the next year week.
+ month == 12 and day >= 29 and day_of_week < 4 ->
+ {year + 1, 1, day_of_week}
+
+ # If we are in a friday, saturday, sunday as one of the first
+ # three days in the year, so we are still in the previous year.
+ iso_days < first_week_day_of_year ->
+ {_, first_week_day_of_year} = first_day_and_week_day_of_year_in_iso_days(year - 1)
+ {year - 1, div(iso_days - first_week_day_of_year, 7) + 1, day_of_week}
+
+ true ->
+ {year, div(iso_days - first_week_day_of_year, 7) + 1, day_of_week}
+ end
+ end
+
+ defp first_day_and_week_day_of_year_in_iso_days(year) do
+ first_day_of_year = days_in_previous_years(year)
+ day_of_week = iso_days_to_day_of_week(first_day_of_year)
+
+ if day_of_week > 4 do
+ {first_day_of_year, first_day_of_year + 7 - day_of_week + 1}
+ else
+ {first_day_of_year, first_day_of_year - day_of_week + 1}
+ end
end
@doc """
diff --git a/lib/elixir/test/elixir/calendar/holocene.exs b/lib/elixir/test/elixir/calendar/holocene.exs
index 7d0be4a94..8a3172724 100644
--- a/lib/elixir/test/elixir/calendar/holocene.exs
+++ b/lib/elixir/test/elixir/calendar/holocene.exs
@@ -97,6 +97,9 @@ defmodule Calendar.Holocene do
defdelegate day_of_week(year, month, day), to: Calendar.ISO
@impl true
+ defdelegate week_of_year(year, month, day), to: Calendar.ISO
+
+ @impl true
defdelegate day_of_year(year, month, day), to: Calendar.ISO
@impl true