From e5b42a0a208d4054038b72cdbe5710adf7fff80a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Voet=20=28ryv=29?= Date: Tue, 21 Mar 2023 15:42:31 +0000 Subject: [PATCH] [IMP] tools: `date_range` accept date params Part-of: odoo/odoo#110737 --- odoo/addons/base/tests/test_misc.py | 23 +++++++++++++++ odoo/tools/date_utils.py | 43 +++++++++++++++-------------- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/odoo/addons/base/tests/test_misc.py b/odoo/addons/base/tests/test_misc.py index 5aff0370107..ca6f6f03ffe 100644 --- a/odoo/addons/base/tests/test_misc.py +++ b/odoo/addons/base/tests/test_misc.py @@ -97,6 +97,29 @@ class TestDateRangeFunction(BaseCase): self.assertEqual(dates, expected) + def test_date_range_with_date(self): + """ Check date_range with naive datetimes. """ + start = datetime.date(1985, 1, 1) + end = datetime.date(1986, 1, 1) + + expected = [ + datetime.date(1985, 1, 1), + datetime.date(1985, 2, 1), + datetime.date(1985, 3, 1), + datetime.date(1985, 4, 1), + datetime.date(1985, 5, 1), + datetime.date(1985, 6, 1), + datetime.date(1985, 7, 1), + datetime.date(1985, 8, 1), + datetime.date(1985, 9, 1), + datetime.date(1985, 10, 1), + datetime.date(1985, 11, 1), + datetime.date(1985, 12, 1), + datetime.date(1986, 1, 1), + ] + + self.assertEqual(list(date_utils.date_range(start, end)), expected) + def test_date_range_with_timezone_aware_datetimes_other_than_utc(self): """ Check date_range with timezone-aware datetimes other than UTC.""" timezone = pytz.timezone('Europe/Brussels') diff --git a/odoo/tools/date_utils.py b/odoo/tools/date_utils.py index 94dcc9d5c0f..48f99731e93 100644 --- a/odoo/tools/date_utils.py +++ b/odoo/tools/date_utils.py @@ -216,25 +216,35 @@ def json_default(obj): def date_range(start, end, step=relativedelta(months=1)): """Date range generator with a step interval. - :param datetime start: beginning date of the range. - :param datetime end: ending date of the range. + :param date | datetime start: beginning date of the range. + :param date | datetime end: ending date of the range. :param relativedelta step: interval of the range. :return: a range of datetime from start to end. :rtype: Iterator[datetime] """ + if isinstance(start, datetime) and isinstance(end, datetime): + are_naive = start.tzinfo is None and end.tzinfo is None + are_utc = start.tzinfo == pytz.utc and end.tzinfo == pytz.utc - are_naive = start.tzinfo is None and end.tzinfo is None - are_utc = start.tzinfo == pytz.utc and end.tzinfo == pytz.utc + # Cases with miscellenous timezone are more complexe because of DST. + are_others = start.tzinfo and end.tzinfo and not are_utc - # Cases with miscellenous timezone are more complexe because of DST. - are_others = start.tzinfo and end.tzinfo and not are_utc - - if are_others: - if start.tzinfo.zone != end.tzinfo.zone: + if are_others and start.tzinfo.zone != end.tzinfo.zone: raise ValueError("Timezones of start argument and end argument seem inconsistent") - if not are_naive and not are_utc and not are_others: - raise ValueError("Timezones of start argument and end argument mismatch") + if not are_naive and not are_utc and not are_others: + raise ValueError("Timezones of start argument and end argument mismatch") + + dt = start.replace(tzinfo=None) + end_dt = end.replace(tzinfo=None) + post_process = start.tzinfo.localize if start.tzinfo else lambda dt: dt + + elif isinstance(start, date) and isinstance(end, date): + dt, end_dt = start, end + post_process = lambda dt: dt + + else: + raise ValueError("start/end should be both date or both datetime type") if start > end: raise ValueError("start > end, start date must be before end") @@ -242,13 +252,6 @@ def date_range(start, end, step=relativedelta(months=1)): if start == start + step: raise ValueError("Looks like step is null") - if start.tzinfo: - localize = start.tzinfo.localize - else: - localize = lambda dt: dt - - dt = start.replace(tzinfo=None) - end = end.replace(tzinfo=None) - while dt <= end: - yield localize(dt) + while dt <= end_dt: + yield post_process(dt) dt = dt + step