Skip to content

Commit a47a89e

Browse files
authored
fix: handle tz-aware datetimes in naturalday and naturaldate (#297)
1 parent b172d67 commit a47a89e

File tree

2 files changed

+41
-4
lines changed

2 files changed

+41
-4
lines changed

src/humanize/time.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -319,14 +319,20 @@ def naturalday(value: dt.date | dt.datetime, format: str = "%b %d") -> str:
319319
import datetime as dt
320320

321321
try:
322+
# When value is a tz-aware datetime, compute "today" in that timezone
323+
# so the comparison uses the correct local date.
324+
if isinstance(value, dt.datetime) and value.tzinfo is not None:
325+
today = dt.datetime.now(value.tzinfo).date()
326+
else:
327+
today = dt.date.today()
322328
value = dt.date(value.year, value.month, value.day)
323329
except AttributeError:
324330
# Passed value wasn't date-ish
325331
return str(value)
326332
except (OverflowError, ValueError):
327333
# Date arguments out of range
328334
return str(value)
329-
delta = value - dt.date.today()
335+
delta = value - today
330336

331337
if delta.days == 0:
332338
return _("today")
@@ -344,18 +350,23 @@ def naturaldate(value: dt.date | dt.datetime) -> str:
344350
"""Like `naturalday`, but append a year for dates more than ~five months away."""
345351
import datetime as dt
346352

353+
original_value = value
347354
try:
355+
if isinstance(value, dt.datetime) and value.tzinfo is not None:
356+
today = dt.datetime.now(value.tzinfo).date()
357+
else:
358+
today = dt.date.today()
348359
value = dt.date(value.year, value.month, value.day)
349360
except AttributeError:
350361
# Passed value wasn't date-ish
351362
return str(value)
352363
except (OverflowError, ValueError):
353364
# Date arguments out of range
354365
return str(value)
355-
delta = _abs_timedelta(value - dt.date.today())
366+
delta = _abs_timedelta(value - today)
356367
if delta.days >= 5 * 365 / 12:
357-
return naturalday(value, "%b %d %Y")
358-
return naturalday(value)
368+
return naturalday(original_value, "%b %d %Y")
369+
return naturalday(original_value)
359370

360371

361372
def _quotient_and_remainder(

tests/test_time.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,32 @@ def test_naturaldate(test_input: dt.date, expected: str) -> None:
300300
assert humanize.naturaldate(test_input) == expected
301301

302302

303+
@freeze_time("2023-10-15 23:00:00+00:00")
304+
def test_naturaldate_tz_aware() -> None:
305+
"""naturaldate should compare dates in the timezone of the given value."""
306+
utc = dt.timezone.utc
307+
aedt = dt.timezone(dt.timedelta(hours=11))
308+
cest = dt.timezone(dt.timedelta(hours=2))
309+
edt = dt.timezone(dt.timedelta(hours=-4))
310+
pdt = dt.timezone(dt.timedelta(hours=-7))
311+
future = dt.datetime(2023, 10, 16, hour=6, tzinfo=utc)
312+
313+
# UTC: now is Oct 15, future is Oct 16 => tomorrow
314+
assert humanize.naturaldate(future) == "tomorrow"
315+
316+
# AEDT (+11): now is Oct 16 10:00, future is Oct 16 17:00 => today
317+
assert humanize.naturaldate(future.astimezone(aedt)) == "today"
318+
319+
# CEST (+2): now is Oct 16 01:00, future is Oct 16 08:00 => today
320+
assert humanize.naturaldate(future.astimezone(cest)) == "today"
321+
322+
# EDT (-4): now is Oct 15 19:00, future is Oct 16 02:00 => tomorrow
323+
assert humanize.naturaldate(future.astimezone(edt)) == "tomorrow"
324+
325+
# PDT (-7): now is Oct 15 16:00, future is Oct 15 23:00 => today
326+
assert humanize.naturaldate(future.astimezone(pdt)) == "today"
327+
328+
303329
@pytest.mark.parametrize(
304330
"seconds, expected",
305331
[

0 commit comments

Comments
 (0)