diff --git a/CHANGELOG.md b/CHANGELOG.md index c1b0057e..c1ed4d29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Updated tzdata to fix issues with 2024b - Fix deprecation: Module.eval_quoted/4 is deprecated. Use Code.eval_quoted/3 instead - Fix deprecation: "min..max inside match is deprecated" +- Fix week of year numbering for `{Wmon}`, `{Wsun}`, `%W` and `%U` in years starting on a Sunday or Monday --- diff --git a/lib/format/datetime/formatter.ex b/lib/format/datetime/formatter.ex index 2ec932c1..67337406 100644 --- a/lib/format/datetime/formatter.ex +++ b/lib/format/datetime/formatter.ex @@ -555,45 +555,13 @@ defmodule Timex.Format.DateTime.Formatter do pad_numeric(week, flags, width) end - def format_token(_locale, :week_mon, %{:year => year} = date, _modifiers, flags, width) do - new_year = Timex.Date.new!(year, 1, 1) - week_start = Timex.Date.beginning_of_week(new_year, :monday) - - # This date can be calculated by taking the day number of the year, - # shifting the day number of the year down by the number of days which - # occurred in the previous year, then dividing by 7 - day_num = - if Date.compare(week_start, new_year) == :lt do - prev_year_day_start = Date.day_of_year(week_start) - prev_year_day_end = Date.day_of_year(Timex.Date.new!(week_start.year, 12, 31)) - shift = prev_year_day_end - prev_year_day_start - shift + Date.day_of_year(Timex.Date.new!(year, date.month, date.day)) - else - Date.day_of_year(Timex.Date.new!(year, date.month, date.day)) - end - - div(day_num, 7) + def format_token(_locale, :week_mon, date, _modifiers, flags, width) do + week_number(date, :monday) |> pad_numeric(flags, width) end - def format_token(_locale, :week_sun, %{:year => year} = date, _modifiers, flags, width) do - new_year = Timex.Date.new!(year, 1, 1) - week_start = Timex.Date.beginning_of_week(new_year, :sunday) - - # This date can be calculated by taking the day number of the year, - # shifting the day number of the year down by the number of days which - # occurred in the previous year, then dividing by 7 - day_num = - if Date.compare(week_start, new_year) == :lt do - prev_year_day_start = Date.day_of_year(week_start) - prev_year_day_end = Date.day_of_year(Timex.Date.new!(week_start.year, 12, 31)) - shift = prev_year_day_end - prev_year_day_start - shift + Date.day_of_year(Timex.Date.new!(year, date.month, date.day)) - else - Date.day_of_year(Timex.Date.new!(year, date.month, date.day)) - end - - div(day_num, 7) + def format_token(_locale, :week_sun, date, _modifiers, flags, width) do + week_number(date, :sunday) |> pad_numeric(flags, width) end @@ -859,6 +827,13 @@ defmodule Timex.Format.DateTime.Formatter do {:error, {:formatter, :unsupported_token, token}} end + # Week of the year counted from the first `weekstart` day, as strftime + # defines %W and %U: days before that first day belong to week 0. + defp week_number(date, weekstart) do + weekday = Timex.weekday!(date, weekstart) - 1 + div(Timex.day(date) + 6 - weekday, 7) + end + defp pad_numeric(number, flags, width) when is_integer(number), do: pad_numeric("#{number}", flags, width) diff --git a/lib/format/datetime/formatters/default.ex b/lib/format/datetime/formatters/default.ex index 9c9ed848..3c630ded 100644 --- a/lib/format/datetime/formatters/default.ex +++ b/lib/format/datetime/formatters/default.ex @@ -47,8 +47,8 @@ defmodule Timex.Format.DateTime.Formatters.Default do ### Weeks * `{Wiso}` - ISO week number (01..53) - * `{Wmon}` - week number of the year, Monday first (01..53) - * `{Wsun}` - week number of the year, Sunday first (01..53) + * `{Wmon}` - week number of the year, Monday first (00..53) + * `{Wsun}` - week number of the year, Sunday first (00..53) ### Time diff --git a/lib/format/datetime/formatters/strftime.ex b/lib/format/datetime/formatters/strftime.ex index 5be9aff2..be1704c5 100644 --- a/lib/format/datetime/formatters/strftime.ex +++ b/lib/format/datetime/formatters/strftime.ex @@ -63,8 +63,8 @@ defmodule Timex.Format.DateTime.Formatters.Strftime do ### Weeks * `%V` - ISO week number (01..53) - * `%W` - week number of the year, Monday first (00..52) - * `%U` - week number of the year, Sunday first (00..52) + * `%W` - week number of the year, Monday first (00..53) + * `%U` - week number of the year, Sunday first (00..53) ### Time diff --git a/test/format_default_test.exs b/test/format_default_test.exs index df41d5b8..9e0fabf6 100644 --- a/test/format_default_test.exs +++ b/test/format_default_test.exs @@ -167,6 +167,23 @@ defmodule DateFormatTest.FormatDefault do date = Timex.to_datetime({2014, 12, 31}) assert {:ok, "52"} = format(date, "{Wmon}") assert {:ok, "52"} = format(date, "{Wsun}") + + # Years beginning on Sunday (2023) or Monday (2024) + date = Timex.to_datetime({2023, 4, 28}) + assert {:ok, "17"} = format(date, "{Wmon}") + assert {:ok, "17"} = format(date, "{Wsun}") + + date = Timex.to_datetime({2023, 4, 30}) + assert {:ok, "17"} = format(date, "{Wmon}") + assert {:ok, "18"} = format(date, "{Wsun}") + + date = Timex.to_datetime({2024, 1, 1}) + assert {:ok, "1"} = format(date, "{Wmon}") + assert {:ok, "0"} = format(date, "{Wsun}") + + date = Timex.to_datetime({2024, 1, 8}) + assert {:ok, "2"} = format(date, "{Wmon}") + assert {:ok, "1"} = format(date, "{Wsun}") end test "format simple compound date formats" do diff --git a/test/format_strftime_test.exs b/test/format_strftime_test.exs index 63a80f30..dd53b2ca 100644 --- a/test/format_strftime_test.exs +++ b/test/format_strftime_test.exs @@ -193,6 +193,14 @@ defmodule DateFormatTest.FormatStrftime do date = Timex.to_datetime({2013, 1, 7}) assert {:ok, "01"} = format(date, "%W") assert {:ok, "1"} = format(date, "%-W") + date = Timex.to_datetime({2023, 4, 28}) + assert {:ok, "17"} = format(date, "%W") + date = Timex.to_datetime({2023, 5, 1}) + assert {:ok, "18"} = format(date, "%W") + date = Timex.to_datetime({2024, 1, 1}) + assert {:ok, "01"} = format(date, "%W") + date = Timex.to_datetime({2024, 1, 8}) + assert {:ok, "02"} = format(date, "%W") end test "format %U" do @@ -206,6 +214,14 @@ defmodule DateFormatTest.FormatStrftime do date = Timex.to_datetime({2013, 1, 7}) assert {:ok, "01"} = format(date, "%U") assert {:ok, "1"} = format(date, "%-U") + date = Timex.to_datetime({2023, 4, 28}) + assert {:ok, "17"} = format(date, "%U") + date = Timex.to_datetime({2023, 4, 30}) + assert {:ok, "18"} = format(date, "%U") + date = Timex.to_datetime({2024, 1, 1}) + assert {:ok, "00"} = format(date, "%U") + date = Timex.to_datetime({2024, 1, 7}) + assert {:ok, "01"} = format(date, "%U") end test "various simple date combinations" do