From fb1df73a80f41c9c6a3cbbc0cec36bd143ac19b5 Mon Sep 17 00:00:00 2001 From: Tech Lead Agent Date: Mon, 21 Sep 2026 08:28:13 +0700 Subject: [PATCH] time: opt-in weekday names in naturalday (issue #102) --- src/humanize/time.py | 28 +++++++++++++++++++++++++++- tests/test_time.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/humanize/time.py b/src/humanize/time.py index 82a80424..8cdf37e7 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -325,13 +325,33 @@ def _convert_aware_datetime( return value -def naturalday(value: dt.date | dt.datetime, format: str = "%b %d") -> str: +_WEEKDAY_NAMES = ( + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", +) + + +def naturalday( + value: dt.date | dt.datetime, + format: str = "%b %d", + weekdays: bool = False, +) -> str: """Return a natural day. For date values that are tomorrow, today or yesterday compared to present day return representing string. Otherwise, return a string formatted according to `format`. + When ``weekdays`` is true, dates within the surrounding week (2-6 days + away) are returned as weekday names instead, e.g. "last Friday" or + "this Monday" (issue #102). The default ``weekdays=False`` keeps the + previous behaviour unchanged. + """ import datetime as dt @@ -360,6 +380,12 @@ def naturalday(value: dt.date | dt.datetime, format: str = "%b %d") -> str: if delta.days == -1: return _("yesterday") + if weekdays and -6 <= delta.days <= 6: + weekday = _(_WEEKDAY_NAMES[value.weekday()]) + if delta.days < 0: + return _("last %s") % weekday + return _("this %s") % weekday + return value.strftime(format) diff --git a/tests/test_time.py b/tests/test_time.py index f63b5cb0..5b949dd8 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -876,3 +876,32 @@ def test_time_unit() -> None: ) def test_rounding_by_fmt(fmt: str, value: float, expected: float) -> None: assert time._rounding_by_fmt(fmt, value) == pytest.approx(expected) + + +@freeze_time(FROZEN_DATE) +@pytest.mark.parametrize( + ("value", "expected"), + [ + # FROZEN_DATE = 2010-02-02 (a Tuesday) + (TODAY, "today"), + (TOMORROW, "tomorrow"), + (YESTERDAY, "yesterday"), + (dt.date(2010, 1, 30), "last Saturday"), + (dt.date(2010, 1, 27), "last Wednesday"), # 6 days ago: boundary + (dt.date(2010, 2, 4), "this Thursday"), + (dt.date(2010, 2, 8), "this Monday"), # 6 days ahead: boundary + (dt.date(2010, 1, 26), "Jan 26"), # 7 days ago: falls back to format + (dt.date(2010, 2, 9), "Feb 09"), # 7 days ahead: falls back to format + (dt.date(2010, 3, 5), "Mar 05"), + ], +) +def test_naturalday_weekdays(value: typing.Any, expected: str) -> None: + """Opt-in ``weekdays=True`` names nearby weekdays (issue #102).""" + assert humanize.naturalday(value, weekdays=True) == expected + + +@freeze_time(FROZEN_DATE) +def test_naturalday_weekdays_default_unchanged() -> None: + """Default output is unchanged when ``weekdays`` is not passed.""" + assert humanize.naturalday(dt.date(2010, 1, 30)) == "Jan 30" + assert humanize.naturalday(dt.date(2010, 2, 4)) == "Feb 04"