From 363ad7531fb5dcdc3e9844573592b0b44afb914b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jun=20Omae=20=28=E5=A4=A7=E5=89=8D=20=E6=BD=A4=29?= <42682+jun66j5@users.noreply.github.com> Date: Fri, 31 Jan 2025 22:22:06 +0900 Subject: [PATCH] Fix dates formatting `Y`, `w` and `W` symbols for week-numbering (#1179) --- babel/dates.py | 49 +++--- tests/test_dates.py | 359 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 385 insertions(+), 23 deletions(-) diff --git a/babel/dates.py b/babel/dates.py index 8a4932d4b..d060fdef1 100644 --- a/babel/dates.py +++ b/babel/dates.py @@ -1482,7 +1482,11 @@ def format_era(self, char: str, num: int) -> str: def format_year(self, char: str, num: int) -> str: value = self.value.year if char.isupper(): - value = self.value.isocalendar()[0] + month = self.value.month + if month == 1 and self.value.day < 7 and self.get_week_of_year() >= 52: + value -= 1 + elif month == 12 and self.value.day > 25 and self.get_week_of_year() <= 2: + value += 1 year = self.format(value, num) if num == 2: year = year[-2:] @@ -1505,18 +1509,10 @@ def format_month(self, char: str, num: int) -> str: def format_week(self, char: str, num: int) -> str: if char.islower(): # week of year - day_of_year = self.get_day_of_year() - week = self.get_week_number(day_of_year) - if week == 0: - date = self.value - datetime.timedelta(days=day_of_year) - week = self.get_week_number(self.get_day_of_year(date), - date.weekday()) + week = self.get_week_of_year() return self.format(week, num) else: # week of month - week = self.get_week_number(self.value.day) - if week == 0: - date = self.value - datetime.timedelta(days=self.value.day) - week = self.get_week_number(date.day, date.weekday()) + week = self.get_week_of_month() return str(week) def format_weekday(self, char: str = 'E', num: int = 4) -> str: @@ -1677,6 +1673,25 @@ def get_day_of_year(self, date: datetime.date | None = None) -> int: date = self.value return (date - date.replace(month=1, day=1)).days + 1 + def get_week_of_year(self) -> int: + """Return the week of the year.""" + day_of_year = self.get_day_of_year(self.value) + week = self.get_week_number(day_of_year) + if week == 0: + date = datetime.date(self.value.year - 1, 12, 31) + week = self.get_week_number(self.get_day_of_year(date), + date.weekday()) + elif week > 52: + weekday = datetime.date(self.value.year + 1, 1, 1).weekday() + if self.get_week_number(1, weekday) == 1 and \ + 32 - (weekday - self.locale.first_week_day) % 7 <= self.value.day: + week = 1 + return week + + def get_week_of_month(self) -> int: + """Return the week of the month.""" + return self.get_week_number(self.value.day) + def get_week_number(self, day_of_period: int, day_of_week: int | None = None) -> int: """Return the number of the week of a day within a period. This may be the week number in a year or the week number in a month. @@ -1703,20 +1718,8 @@ def get_week_number(self, day_of_period: int, day_of_week: int | None = None) -> if first_day < 0: first_day += 7 week_number = (day_of_period + first_day - 1) // 7 - if 7 - first_day >= self.locale.min_week_days: week_number += 1 - - if self.locale.first_week_day == 0: - # Correct the weeknumber in case of iso-calendar usage (first_week_day=0). - # If the weeknumber exceeds the maximum number of weeks for the given year - # we must count from zero.For example the above calculation gives week 53 - # for 2018-12-31. By iso-calender definition 2018 has a max of 52 - # weeks, thus the weeknumber must be 53-52=1. - max_weeks = datetime.date(year=self.value.year, day=28, month=12).isocalendar()[1] - if week_number > max_weeks: - week_number -= max_weeks - return week_number diff --git a/tests/test_dates.py b/tests/test_dates.py index bc6107f39..18bf43430 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -785,6 +785,365 @@ def test_russian_week_numbering(): assert dates.format_date(v, format='YYYY-ww', locale='de_DE') == '2016-52' +def test_week_numbering_isocalendar(): + locale = Locale.parse('de_DE') + assert locale.first_week_day == 0 + assert locale.min_week_days == 4 + + def week_number(value): + return dates.format_date(value, format="YYYY-'W'ww-e", locale=locale) + + for year in range(1991, 2010): + for delta in range(-9, 9): + value = date(year, 1, 1) + timedelta(days=delta) + expected = '%04d-W%02d-%d' % value.isocalendar() + assert week_number(value) == expected + +def test_week_numbering_monday_mindays_4(): + locale = Locale.parse('de_DE') + assert locale.first_week_day == 0 + assert locale.min_week_days == 4 + + def week_number(value): + value = date.fromisoformat(value) + return dates.format_date(value, format="YYYY-'W'ww-e", locale=locale) + + def week_of_month(value): + value = date.fromisoformat(value) + return dates.format_date(value, format='W', locale=locale) + + assert week_number('2003-01-01') == '2003-W01-3' + assert week_number('2003-01-06') == '2003-W02-1' + assert week_number('2003-12-28') == '2003-W52-7' + assert week_number('2003-12-29') == '2004-W01-1' + assert week_number('2003-12-31') == '2004-W01-3' + assert week_number('2004-01-01') == '2004-W01-4' + assert week_number('2004-01-05') == '2004-W02-1' + assert week_number('2004-12-26') == '2004-W52-7' + assert week_number('2004-12-27') == '2004-W53-1' + assert week_number('2004-12-31') == '2004-W53-5' + assert week_number('2005-01-01') == '2004-W53-6' + assert week_number('2005-01-03') == '2005-W01-1' + assert week_number('2005-01-10') == '2005-W02-1' + assert week_number('2005-12-31') == '2005-W52-6' + assert week_number('2006-01-01') == '2005-W52-7' + assert week_number('2006-01-02') == '2006-W01-1' + assert week_number('2006-01-09') == '2006-W02-1' + assert week_number('2006-12-31') == '2006-W52-7' + assert week_number('2007-01-01') == '2007-W01-1' + assert week_number('2007-12-30') == '2007-W52-7' + assert week_number('2007-12-31') == '2008-W01-1' + assert week_number('2008-01-01') == '2008-W01-2' + assert week_number('2008-01-07') == '2008-W02-1' + assert week_number('2008-12-28') == '2008-W52-7' + assert week_number('2008-12-29') == '2009-W01-1' + assert week_number('2008-12-31') == '2009-W01-3' + assert week_number('2009-01-01') == '2009-W01-4' + assert week_number('2009-01-05') == '2009-W02-1' + assert week_number('2009-12-27') == '2009-W52-7' + assert week_number('2009-12-28') == '2009-W53-1' + assert week_number('2009-12-31') == '2009-W53-4' + assert week_number('2010-01-01') == '2009-W53-5' + assert week_number('2010-01-03') == '2009-W53-7' + assert week_number('2010-01-04') == '2010-W01-1' + + assert week_of_month('2003-01-01') == '1' # Wed + assert week_of_month('2003-02-28') == '4' + assert week_of_month('2003-05-01') == '1' # Thu + assert week_of_month('2003-08-01') == '0' # Fri + assert week_of_month('2003-12-31') == '5' + assert week_of_month('2004-01-01') == '1' # Thu + assert week_of_month('2004-02-29') == '4' + + +def test_week_numbering_monday_mindays_1(): + locale = Locale.parse('tr_TR') + assert locale.first_week_day == 0 + assert locale.min_week_days == 1 + + def week_number(value): + value = date.fromisoformat(value) + return dates.format_date(value, format="YYYY-'W'ww-e", locale=locale) + + def week_of_month(value): + value = date.fromisoformat(value) + return dates.format_date(value, format='W', locale=locale) + + assert week_number('2003-01-01') == '2003-W01-3' + assert week_number('2003-01-06') == '2003-W02-1' + assert week_number('2003-12-28') == '2003-W52-7' + assert week_number('2003-12-29') == '2004-W01-1' + assert week_number('2003-12-31') == '2004-W01-3' + assert week_number('2004-01-01') == '2004-W01-4' + assert week_number('2004-01-05') == '2004-W02-1' + assert week_number('2004-12-26') == '2004-W52-7' + assert week_number('2004-12-27') == '2005-W01-1' + assert week_number('2004-12-31') == '2005-W01-5' + assert week_number('2005-01-01') == '2005-W01-6' + assert week_number('2005-01-03') == '2005-W02-1' + assert week_number('2005-12-25') == '2005-W52-7' + assert week_number('2005-12-26') == '2006-W01-1' + assert week_number('2005-12-31') == '2006-W01-6' + assert week_number('2006-01-01') == '2006-W01-7' + assert week_number('2006-01-02') == '2006-W02-1' + assert week_number('2006-12-31') == '2006-W53-7' + assert week_number('2007-01-01') == '2007-W01-1' + assert week_number('2007-01-08') == '2007-W02-1' + assert week_number('2007-12-30') == '2007-W52-7' + assert week_number('2007-12-31') == '2008-W01-1' + assert week_number('2008-01-01') == '2008-W01-2' + assert week_number('2008-01-07') == '2008-W02-1' + assert week_number('2008-12-28') == '2008-W52-7' + assert week_number('2008-12-29') == '2009-W01-1' + assert week_number('2008-12-31') == '2009-W01-3' + assert week_number('2009-01-01') == '2009-W01-4' + assert week_number('2009-01-05') == '2009-W02-1' + assert week_number('2009-12-27') == '2009-W52-7' + assert week_number('2009-12-28') == '2010-W01-1' + assert week_number('2009-12-31') == '2010-W01-4' + assert week_number('2010-01-01') == '2010-W01-5' + assert week_number('2010-01-03') == '2010-W01-7' + assert week_number('2010-01-04') == '2010-W02-1' + + assert week_of_month('2003-01-01') == '1' # Wed + assert week_of_month('2003-02-28') == '5' + assert week_of_month('2003-06-01') == '1' # Sun + assert week_of_month('2003-12-31') == '5' + assert week_of_month('2004-01-01') == '1' # Thu + assert week_of_month('2004-02-29') == '5' + + +def test_week_numbering_sunday_mindays_1(): + locale = Locale.parse('en_US') + assert locale.first_week_day == 6 + assert locale.min_week_days == 1 + + def week_number(value): + value = date.fromisoformat(value) + return dates.format_date(value, format="YYYY-'W'ww-e", locale=locale) + + def week_of_month(value): + value = date.fromisoformat(value) + return dates.format_date(value, format='W', locale=locale) + + assert week_number('2003-01-01') == '2003-W01-4' + assert week_number('2003-01-05') == '2003-W02-1' + assert week_number('2003-12-27') == '2003-W52-7' + assert week_number('2003-12-28') == '2004-W01-1' + assert week_number('2003-12-31') == '2004-W01-4' + assert week_number('2004-01-01') == '2004-W01-5' + assert week_number('2004-01-04') == '2004-W02-1' + assert week_number('2004-12-25') == '2004-W52-7' + assert week_number('2004-12-26') == '2005-W01-1' + assert week_number('2004-12-31') == '2005-W01-6' + assert week_number('2005-01-01') == '2005-W01-7' + assert week_number('2005-01-02') == '2005-W02-1' + assert week_number('2005-12-24') == '2005-W52-7' + assert week_number('2005-12-25') == '2005-W53-1' + assert week_number('2005-12-31') == '2005-W53-7' + assert week_number('2006-01-01') == '2006-W01-1' + assert week_number('2006-01-08') == '2006-W02-1' + assert week_number('2006-12-30') == '2006-W52-7' + assert week_number('2006-12-31') == '2007-W01-1' + assert week_number('2007-01-01') == '2007-W01-2' + assert week_number('2007-01-07') == '2007-W02-1' + assert week_number('2007-12-29') == '2007-W52-7' + assert week_number('2007-12-30') == '2008-W01-1' + assert week_number('2007-12-31') == '2008-W01-2' + assert week_number('2008-01-01') == '2008-W01-3' + assert week_number('2008-01-06') == '2008-W02-1' + assert week_number('2008-12-27') == '2008-W52-7' + assert week_number('2008-12-28') == '2009-W01-1' + assert week_number('2008-12-31') == '2009-W01-4' + assert week_number('2009-01-01') == '2009-W01-5' + assert week_number('2009-01-04') == '2009-W02-1' + assert week_number('2009-12-26') == '2009-W52-7' + assert week_number('2009-12-27') == '2010-W01-1' + assert week_number('2009-12-31') == '2010-W01-5' + assert week_number('2010-01-01') == '2010-W01-6' + assert week_number('2010-01-03') == '2010-W02-1' + + assert week_of_month('2003-01-01') == '1' # Wed + assert week_of_month('2003-02-01') == '1' # Sat + assert week_of_month('2003-02-28') == '5' + assert week_of_month('2003-12-31') == '5' + assert week_of_month('2004-01-01') == '1' # Thu + assert week_of_month('2004-02-29') == '5' + + +def test_week_numbering_sunday_mindays_4(): + locale = Locale.parse('pt_PT') + assert locale.first_week_day == 6 + assert locale.min_week_days == 4 + + def week_number(value): + value = date.fromisoformat(value) + return dates.format_date(value, format="YYYY-'W'ww-e", locale=locale) + + def week_of_month(value): + value = date.fromisoformat(value) + return dates.format_date(value, format='W', locale=locale) + + assert week_number('2003-01-01') == '2003-W01-4' + assert week_number('2003-01-05') == '2003-W02-1' + assert week_number('2003-12-27') == '2003-W52-7' + assert week_number('2003-12-28') == '2003-W53-1' + assert week_number('2003-12-31') == '2003-W53-4' + assert week_number('2004-01-01') == '2003-W53-5' + assert week_number('2004-01-04') == '2004-W01-1' + assert week_number('2004-12-25') == '2004-W51-7' + assert week_number('2004-12-26') == '2004-W52-1' + assert week_number('2004-12-31') == '2004-W52-6' + assert week_number('2005-01-01') == '2004-W52-7' + assert week_number('2005-01-02') == '2005-W01-1' + assert week_number('2005-12-24') == '2005-W51-7' + assert week_number('2005-12-25') == '2005-W52-1' + assert week_number('2005-12-31') == '2005-W52-7' + assert week_number('2006-01-01') == '2006-W01-1' + assert week_number('2006-01-08') == '2006-W02-1' + assert week_number('2006-12-30') == '2006-W52-7' + assert week_number('2006-12-31') == '2007-W01-1' + assert week_number('2007-01-01') == '2007-W01-2' + assert week_number('2007-01-07') == '2007-W02-1' + assert week_number('2007-12-29') == '2007-W52-7' + assert week_number('2007-12-30') == '2008-W01-1' + assert week_number('2007-12-31') == '2008-W01-2' + assert week_number('2008-01-01') == '2008-W01-3' + assert week_number('2008-01-06') == '2008-W02-1' + assert week_number('2008-12-27') == '2008-W52-7' + assert week_number('2008-12-28') == '2008-W53-1' + assert week_number('2008-12-31') == '2008-W53-4' + assert week_number('2009-01-01') == '2008-W53-5' + assert week_number('2009-01-04') == '2009-W01-1' + assert week_number('2009-12-26') == '2009-W51-7' + assert week_number('2009-12-27') == '2009-W52-1' + assert week_number('2009-12-31') == '2009-W52-5' + assert week_number('2010-01-01') == '2009-W52-6' + assert week_number('2010-01-03') == '2010-W01-1' + + assert week_of_month('2003-01-01') == '1' # Wed + assert week_of_month('2003-02-28') == '4' + assert week_of_month('2003-05-01') == '0' # Thu + assert week_of_month('2003-12-31') == '5' + assert week_of_month('2004-01-01') == '0' # Thu + assert week_of_month('2004-02-29') == '5' + + +def test_week_numbering_friday_mindays_1(): + locale = Locale.parse('dv_MV') + assert locale.first_week_day == 4 + assert locale.min_week_days == 1 + + def week_number(value): + value = date.fromisoformat(value) + return dates.format_date(value, format="YYYY-'W'ww-e", locale=locale) + + def week_of_month(value): + value = date.fromisoformat(value) + return dates.format_date(value, format='W', locale=locale) + + assert week_number('2003-01-01') == '2003-W01-6' + assert week_number('2003-01-03') == '2003-W02-1' + assert week_number('2003-12-25') == '2003-W52-7' + assert week_number('2003-12-26') == '2004-W01-1' + assert week_number('2003-12-31') == '2004-W01-6' + assert week_number('2004-01-01') == '2004-W01-7' + assert week_number('2004-01-02') == '2004-W02-1' + assert week_number('2004-12-23') == '2004-W52-7' + assert week_number('2004-12-24') == '2004-W53-1' + assert week_number('2004-12-30') == '2004-W53-7' + assert week_number('2004-12-31') == '2005-W01-1' + assert week_number('2005-01-01') == '2005-W01-2' + assert week_number('2005-01-07') == '2005-W02-1' + assert week_number('2005-12-29') == '2005-W52-7' + assert week_number('2005-12-30') == '2006-W01-1' + assert week_number('2005-12-31') == '2006-W01-2' + assert week_number('2006-01-01') == '2006-W01-3' + assert week_number('2006-01-06') == '2006-W02-1' + assert week_number('2006-12-28') == '2006-W52-7' + assert week_number('2006-12-29') == '2007-W01-1' + assert week_number('2006-12-31') == '2007-W01-3' + assert week_number('2007-01-01') == '2007-W01-4' + assert week_number('2007-01-05') == '2007-W02-1' + assert week_number('2007-12-27') == '2007-W52-7' + assert week_number('2007-12-28') == '2008-W01-1' + assert week_number('2007-12-31') == '2008-W01-4' + assert week_number('2008-01-01') == '2008-W01-5' + assert week_number('2008-01-04') == '2008-W02-1' + assert week_number('2008-12-25') == '2008-W52-7' + assert week_number('2008-12-26') == '2009-W01-1' + assert week_number('2008-12-31') == '2009-W01-6' + assert week_number('2009-01-01') == '2009-W01-7' + assert week_number('2009-01-02') == '2009-W02-1' + assert week_number('2009-12-24') == '2009-W52-7' + assert week_number('2009-12-25') == '2009-W53-1' + assert week_number('2009-12-31') == '2009-W53-7' + assert week_number('2010-01-01') == '2010-W01-1' + assert week_number('2010-01-08') == '2010-W02-1' + + assert week_of_month('2003-01-01') == '1' # Wed + assert week_of_month('2003-02-28') == '5' + assert week_of_month('2003-03-01') == '1' # Sun + assert week_of_month('2003-12-31') == '5' + assert week_of_month('2004-01-01') == '1' # Thu + assert week_of_month('2004-02-29') == '5' + + +def test_week_numbering_saturday_mindays_1(): + locale = Locale.parse('fr_DZ') + assert locale.first_week_day == 5 + assert locale.min_week_days == 1 + + def week_number(value): + value = date.fromisoformat(value) + return dates.format_date(value, format="YYYY-'W'ww-e", locale=locale) + + def week_of_month(value): + value = date.fromisoformat(value) + return dates.format_date(value, format='W', locale=locale) + + assert week_number('2003-01-01') == '2003-W01-5' + assert week_number('2003-01-04') == '2003-W02-1' + assert week_number('2003-12-26') == '2003-W52-7' + assert week_number('2003-12-27') == '2004-W01-1' + assert week_number('2003-12-31') == '2004-W01-5' + assert week_number('2004-01-01') == '2004-W01-6' + assert week_number('2004-01-03') == '2004-W02-1' + assert week_number('2004-12-24') == '2004-W52-7' + assert week_number('2004-12-31') == '2004-W53-7' + assert week_number('2005-01-01') == '2005-W01-1' + assert week_number('2005-01-08') == '2005-W02-1' + assert week_number('2005-12-30') == '2005-W52-7' + assert week_number('2005-12-31') == '2006-W01-1' + assert week_number('2006-01-01') == '2006-W01-2' + assert week_number('2006-01-07') == '2006-W02-1' + assert week_number('2006-12-29') == '2006-W52-7' + assert week_number('2006-12-31') == '2007-W01-2' + assert week_number('2007-01-01') == '2007-W01-3' + assert week_number('2007-01-06') == '2007-W02-1' + assert week_number('2007-12-28') == '2007-W52-7' + assert week_number('2007-12-31') == '2008-W01-3' + assert week_number('2008-01-01') == '2008-W01-4' + assert week_number('2008-01-05') == '2008-W02-1' + assert week_number('2008-12-26') == '2008-W52-7' + assert week_number('2008-12-27') == '2009-W01-1' + assert week_number('2008-12-31') == '2009-W01-5' + assert week_number('2009-01-01') == '2009-W01-6' + assert week_number('2009-01-03') == '2009-W02-1' + assert week_number('2009-12-25') == '2009-W52-7' + assert week_number('2009-12-26') == '2010-W01-1' + assert week_number('2009-12-31') == '2010-W01-6' + assert week_number('2010-01-01') == '2010-W01-7' + assert week_number('2010-01-02') == '2010-W02-1' + + assert week_of_month('2003-01-01') == '1' # Wed + assert week_of_month('2003-02-28') == '4' + assert week_of_month('2003-08-01') == '1' # Fri + assert week_of_month('2003-12-31') == '5' + assert week_of_month('2004-01-01') == '1' # Thu + assert week_of_month('2004-02-29') == '5' + + def test_en_gb_first_weekday(): assert Locale.parse('en').first_week_day == 0 # Monday in general assert Locale.parse('en_US').first_week_day == 6 # Sunday in the US