Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions src/humanize/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ def naturalday(value: dt.date | dt.datetime, format: str = "%b %d") -> str:
"""
import datetime as dt

# Capture timezone before converting to a plain date so we can
# derive "today" in the same timezone as the input value.
tzinfo = getattr(value, "tzinfo", None)

try:
value = dt.date(value.year, value.month, value.day)
except AttributeError:
Expand All @@ -326,7 +330,12 @@ 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()

if tzinfo is not None:
today = dt.datetime.now(tzinfo).date()
else:
today = dt.date.today()
delta = value - today

if delta.days == 0:
return _("today")
Expand All @@ -344,16 +353,25 @@ 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

# Capture timezone before converting so we derive "today" correctly.
tzinfo = getattr(value, "tzinfo", None)

try:
value = dt.date(value.year, value.month, value.day)
date_value = dt.date(value.year, value.month, value.day)
except AttributeError:
# Passed value wasn't date-ish
return str(value)
except (OverflowError, ValueError):
# Date arguments out of range
return str(value)
delta = _abs_timedelta(value - dt.date.today())

if tzinfo is not None:
today = dt.datetime.now(tzinfo).date()
else:
today = dt.date.today()
delta = _abs_timedelta(date_value - today)
if delta.days >= 5 * 365 / 12:
# Pass original value so naturalday() can extract timezone info.
return naturalday(value, "%b %d %Y")
return naturalday(value)

Expand Down
45 changes: 45 additions & 0 deletions tests/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,51 @@ def test_naturaldate(test_input: dt.date, expected: str) -> None:
assert humanize.naturaldate(test_input) == expected


@freeze_time("2023-10-15 23:00:00", tz_offset=0)
def test_naturalday_tz_aware() -> None:
"""Test that naturalday compares dates in the value's timezone, not system local."""
# https://github.com/python-humanize/humanize/issues/152
utc = dt.timezone.utc
aedt = dt.timezone(dt.timedelta(hours=11))
edt = dt.timezone(dt.timedelta(hours=-4))
pdt = dt.timezone(dt.timedelta(hours=-7))

# A moment 7 hours in the future (UTC).
future = dt.datetime(2023, 10, 16, hour=6, tzinfo=utc)

# In UTC: now is Oct 15, future is Oct 16 → tomorrow
assert humanize.naturalday(future) == "tomorrow"

# In AEDT (+11): now is Oct 16 10:00, future is Oct 16 17:00 → today
assert humanize.naturalday(future.astimezone(aedt)) == "today"

# In EDT (-4): now is Oct 15 19:00, future is Oct 16 02:00 → tomorrow
assert humanize.naturalday(future.astimezone(edt)) == "tomorrow"

# In PDT (-7): now is Oct 15 16:00, future is Oct 15 23:00 → today
assert humanize.naturalday(future.astimezone(pdt)) == "today"


@freeze_time("2023-10-15 23:00:00", tz_offset=0)
def test_naturaldate_tz_aware() -> None:
"""Test naturaldate compares dates in value's timezone."""
# https://github.com/python-humanize/humanize/issues/152
utc = dt.timezone.utc
aedt = dt.timezone(dt.timedelta(hours=11))
edt = dt.timezone(dt.timedelta(hours=-4))

future = dt.datetime(2023, 10, 16, hour=6, tzinfo=utc)

# In UTC: tomorrow
assert humanize.naturaldate(future) == "tomorrow"

# In AEDT (+11): today
assert humanize.naturaldate(future.astimezone(aedt)) == "today"

# In EDT (-4): tomorrow
assert humanize.naturaldate(future.astimezone(edt)) == "tomorrow"


@pytest.mark.parametrize(
"seconds, expected",
[
Expand Down
Loading