diff --git a/src/humanize/time.py b/src/humanize/time.py index 7c4a28eb..cea634dc 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -320,7 +320,7 @@ def _convert_aware_datetime( import datetime as dt - if isinstance(value, dt.datetime) and value.tzinfo is not None: + if isinstance(value, dt.datetime) and value.utcoffset() is not None: value = dt.datetime.fromtimestamp(value.timestamp()) # noqa: DTZ006 return value @@ -338,7 +338,7 @@ def naturalday(value: dt.date | dt.datetime, format: str = "%b %d") -> str: 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: + if isinstance(value, dt.datetime) and value.utcoffset() is not None: today = dt.datetime.now(value.tzinfo).date() else: today = dt.date.today() # noqa: DTZ011 @@ -369,7 +369,7 @@ def naturaldate(value: dt.date | dt.datetime) -> str: original_value = value try: - if isinstance(value, dt.datetime) and value.tzinfo is not None: + if isinstance(value, dt.datetime) and value.utcoffset() is not None: today = dt.datetime.now(value.tzinfo).date() else: today = dt.date.today() # noqa: DTZ011 diff --git a/tests/test_time.py b/tests/test_time.py index 887a3fc5..4c93eb49 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -42,6 +42,17 @@ def __str__(self) -> str: return f"{self.year}-{self.month}-{self.day}" +class NaiveTimezone(dt.tzinfo): + def utcoffset(self, value: dt.datetime | None) -> None: + return None + + def dst(self, value: dt.datetime | None) -> None: + return None + + def tzname(self, value: dt.datetime | None) -> None: + return None + + VALUE_ERROR_TEST = FakeDate(290149024, 2, 2) OVERFLOW_ERROR_TEST = FakeDate(120390192341, 2, 2) @@ -280,6 +291,36 @@ def test_naturalday(test_args: list[typing.Any], expected: str) -> None: assert humanize.naturalday(*test_args) == expected +@freeze_time(FROZEN_DATE) +@pytest.mark.parametrize("formatter", ["naturalday", "naturaldate", "naturaltime"]) +@pytest.mark.parametrize("tzinfo", [None, NaiveTimezone(), dt.UTC]) +def test_datetime_awareness(formatter: str, tzinfo: dt.tzinfo | None) -> None: + value = dt.datetime(2010, 2, 2, tzinfo=tzinfo) + format_time = getattr(humanize, formatter) + expected = "now" if formatter == "naturaltime" else "today" + assert format_time(value) == expected + if value.utcoffset() is None: + assert format_time(value) == format_time(value.replace(tzinfo=None)) + + +@pytest.mark.parametrize( + "value_tzinfo, when_tzinfo", + [ + (None, None), + (NaiveTimezone(), None), + (None, NaiveTimezone()), + (NaiveTimezone(), NaiveTimezone()), + (dt.UTC, dt.UTC), + ], +) +def test_naturaltime_when_datetime_awareness( + value_tzinfo: dt.tzinfo | None, when_tzinfo: dt.tzinfo | None +) -> None: + value = dt.datetime(2010, 2, 2, 12, tzinfo=value_tzinfo) + when = dt.datetime(2010, 2, 2, 11, tzinfo=when_tzinfo) + assert humanize.naturaltime(value, when=when) == "an hour from now" + + @freeze_time(FROZEN_DATE) @pytest.mark.parametrize( "test_input, expected",