diff options
author | José Valim <jose.valim@gmail.com> | 2018-12-04 23:18:50 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-04 23:18:50 +0100 |
commit | b8a09a5c4daab273a9e0f2598c77b5c8133171ca (patch) | |
tree | 92b38af1ba313e73228dc997b798dc6f5a946ba8 | |
parent | 00a67e3eea5ed663d13c5b01d54915f9fb26259a (diff) | |
download | elixir-b8a09a5c4daab273a9e0f2598c77b5c8133171ca.tar.gz |
Add week_of_year/3 (#8460)
-rw-r--r-- | lib/elixir/lib/calendar.ex | 12 | ||||
-rw-r--r-- | lib/elixir/lib/calendar/date.ex | 36 | ||||
-rw-r--r-- | lib/elixir/lib/calendar/iso.ex | 123 | ||||
-rw-r--r-- | lib/elixir/test/elixir/calendar/holocene.exs | 3 |
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 |