diff --git a/src/humanize/time.py b/src/humanize/time.py index f0b24fa..9d43d00 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -319,6 +319,12 @@ def naturalday(value: dt.date | dt.datetime, format: str = "%b %d") -> str: import datetime as dt try: + # When value is a tz-aware datetime, compute "today" in that timezone + # so the comparison uses the correct local date. + if isinstance(value, dt.datetime) and value.tzinfo is not None: + today = dt.datetime.now(value.tzinfo).date() + else: + today = dt.date.today() value = dt.date(value.year, value.month, value.day) except AttributeError: # Passed value wasn't date-ish @@ -326,7 +332,7 @@ def naturalday(value: dt.date | dt.datetime, format: str = "%b %d") -> str: except (OverflowError, ValueError): # Date arguments out of range return str(value) - delta = value - dt.date.today() + delta = value - today if delta.days == 0: return _("today") @@ -344,7 +350,12 @@ def naturaldate(value: dt.date | dt.datetime) -> str: """Like `naturalday`, but append a year for dates more than ~five months away.""" import datetime as dt + original_value = value try: + if isinstance(value, dt.datetime) and value.tzinfo is not None: + today = dt.datetime.now(value.tzinfo).date() + else: + today = dt.date.today() value = dt.date(value.year, value.month, value.day) except AttributeError: # Passed value wasn't date-ish @@ -352,10 +363,10 @@ def naturaldate(value: dt.date | dt.datetime) -> str: except (OverflowError, ValueError): # Date arguments out of range return str(value) - delta = _abs_timedelta(value - dt.date.today()) + delta = _abs_timedelta(value - today) if delta.days >= 5 * 365 / 12: - return naturalday(value, "%b %d %Y") - return naturalday(value) + return naturalday(original_value, "%b %d %Y") + return naturalday(original_value) def _quotient_and_remainder( diff --git a/tests/test_time.py b/tests/test_time.py index 63e171a..7699770 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -300,6 +300,32 @@ def test_naturaldate(test_input: dt.date, expected: str) -> None: assert humanize.naturaldate(test_input) == expected +@freeze_time("2023-10-15 23:00:00+00:00") +def test_naturaldate_tz_aware() -> None: + """naturaldate should compare dates in the timezone of the given value.""" + utc = dt.timezone.utc + aedt = dt.timezone(dt.timedelta(hours=11)) + cest = dt.timezone(dt.timedelta(hours=2)) + edt = dt.timezone(dt.timedelta(hours=-4)) + pdt = dt.timezone(dt.timedelta(hours=-7)) + future = dt.datetime(2023, 10, 16, hour=6, tzinfo=utc) + + # UTC: now is Oct 15, future is Oct 16 => tomorrow + assert humanize.naturaldate(future) == "tomorrow" + + # AEDT (+11): now is Oct 16 10:00, future is Oct 16 17:00 => today + assert humanize.naturaldate(future.astimezone(aedt)) == "today" + + # CEST (+2): now is Oct 16 01:00, future is Oct 16 08:00 => today + assert humanize.naturaldate(future.astimezone(cest)) == "today" + + # EDT (-4): now is Oct 15 19:00, future is Oct 16 02:00 => tomorrow + assert humanize.naturaldate(future.astimezone(edt)) == "tomorrow" + + # PDT (-7): now is Oct 15 16:00, future is Oct 15 23:00 => today + assert humanize.naturaldate(future.astimezone(pdt)) == "today" + + @pytest.mark.parametrize( "seconds, expected", [