From 22afe14caad95b7592ed405cf1c5d2fe71b40e9d Mon Sep 17 00:00:00 2001 From: Philipp Joram Date: Thu, 1 Oct 2020 16:48:05 +0200 Subject: [PATCH 1/3] Use black==20.8b1 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e21eccf6..7af3b16d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: args: ["--py36-plus"] - repo: https://github.com/psf/black - rev: 19.10b0 + rev: 20.8b1 hooks: - id: black args: ["--target-version", "py36"] From 3c800b067ced822850ec193ea425947c9e6509e7 Mon Sep 17 00:00:00 2001 From: Philipp Joram Date: Thu, 16 Jul 2020 18:16:48 +0200 Subject: [PATCH 2/3] Allow custom reference time in naturaldelta and naturaltime This allows having a different point in time to which a value is compared. Useful if one is dealing with datetimes that are not in the local timezone. --- src/humanize/time.py | 26 +++++++++++++++++++++----- tests/test_time.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/humanize/time.py b/src/humanize/time.py index b0bf85be..0e4d8812 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -81,7 +81,7 @@ def date_and_delta(value, *, now=None): return date, abs_timedelta(delta) -def naturaldelta(value, months=True, minimum_unit="seconds"): +def naturaldelta(value, months=True, minimum_unit="seconds", when=None): """Return a natural representation of a timedelta or number of seconds. This is similar to `naturaltime`, but does not add tense to the result. @@ -91,16 +91,30 @@ def naturaldelta(value, months=True, minimum_unit="seconds"): months (bool): If `True`, then a number of months (based on 30.5 days) will be used for fuzziness between years. minimum_unit (str): The lowest unit that can be used. + when (datetime.timedelta): Point in time relative to which _value_ is + interpreted. Defaults to the current time in the local timezone. Returns: str: A natural representation of the amount of time elapsed. + + Examples + Compare two timestamps in a custom local timezone:: + + from datetime import datetime, timedelta, timezone + from dateutil.tz import gettz + + berlin = gettz("Europe/Berlin") + now = datetime.now(tz=berlin) + later = now + timedelta(minutes=30) + + assert naturaldelta(later, when=now) == "30 minutes" """ tmp = Unit[minimum_unit.upper()] if tmp not in (Unit.SECONDS, Unit.MILLISECONDS, Unit.MICROSECONDS): raise ValueError(f"Minimum unit '{minimum_unit}' not supported") minimum_unit = tmp - date, delta = date_and_delta(value) + date, delta = date_and_delta(value, now=when) if date is None: return value @@ -173,7 +187,7 @@ def naturaldelta(value, months=True, minimum_unit="seconds"): return ngettext("%d year", "%d years", years) % years -def naturaltime(value, future=False, months=True, minimum_unit="seconds"): +def naturaltime(value, future=False, months=True, minimum_unit="seconds", when=None): """Return a natural representation of a time in a resolution that makes sense. This is more or less compatible with Django's `naturaltime` filter. @@ -186,11 +200,13 @@ def naturaltime(value, future=False, months=True, minimum_unit="seconds"): months (bool): If `True`, then a number of months (based on 30.5 days) will be used for fuzziness between years. minimum_unit (str): The lowest unit that can be used. + when (datetime.datetime): Point in time relative to which _value_ is + interpreted. Defaults to the current time in the local timezone. Returns: str: A natural representation of the input in a resolution that makes sense. """ - now = _now() + now = when or _now() date, delta = date_and_delta(value, now=now) if date is None: return value @@ -199,7 +215,7 @@ def naturaltime(value, future=False, months=True, minimum_unit="seconds"): future = date > now ago = _("%s from now") if future else _("%s ago") - delta = naturaldelta(delta, months, minimum_unit) + delta = naturaldelta(delta, months, minimum_unit, when=when) if delta == _("a moment"): return _("now") diff --git a/tests/test_time.py b/tests/test_time.py index bf5a8532..90c01300 100644 --- a/tests/test_time.py +++ b/tests/test_time.py @@ -24,6 +24,8 @@ with freeze_time("2020-02-02"): NOW = dt.datetime.now() + NOW_UTC = dt.datetime.now(tz=dt.timezone.utc) + NOW_UTC_PLUS_01_00 = dt.datetime.now(tz=dt.timezone(offset=dt.timedelta(hours=1))) TODAY = dt.date.today() TOMORROW = TODAY + ONE_DAY_DELTA YESTERDAY = TODAY - ONE_DAY_DELTA @@ -331,6 +333,37 @@ def test_naturaldelta_minimum_unit_explicit(minimum_unit, seconds, expected): assert humanize.naturaldelta(delta, minimum_unit=minimum_unit) == expected +@pytest.mark.parametrize( + "test_input, when, expected", + [ + (NOW, NOW, "a moment"), + (NOW_UTC, NOW_UTC, "a moment"), + ], +) +def test_naturaldelta_when_explicit(test_input, when, expected): + # Act / Assert + assert humanize.naturaldelta(test_input, when=when) == expected + + +@pytest.mark.parametrize( + "value, when", + [ + (NOW_UTC, None), + (NOW_UTC, NOW), + (NOW_UTC_PLUS_01_00, None), + (NOW_UTC_PLUS_01_00, NOW), + ], +) +def test_naturaldelta_when_missing_tzinfo(value, when): + """Subtraction `when - value` is not defined by the `datetime` module when + either operand has not timezone-info (`tz=None`) and raises a TypeError. + """ + + # Act / Assert + with pytest.raises(TypeError): + humanize.naturaldelta(value, when=when) + + @pytest.mark.parametrize( "seconds, expected", [ From b37dc30ba61c2446eecb1a9d3e9ac8c9adf00f03 Mon Sep 17 00:00:00 2001 From: Philipp Joram Date: Sat, 3 Oct 2020 09:28:49 +0200 Subject: [PATCH 3/3] Use conventional `import datetime as dt` in docstring --- src/humanize/time.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/humanize/time.py b/src/humanize/time.py index 0e4d8812..bc1922c0 100644 --- a/src/humanize/time.py +++ b/src/humanize/time.py @@ -100,12 +100,12 @@ def naturaldelta(value, months=True, minimum_unit="seconds", when=None): Examples Compare two timestamps in a custom local timezone:: - from datetime import datetime, timedelta, timezone + import datetime as dt from dateutil.tz import gettz berlin = gettz("Europe/Berlin") - now = datetime.now(tz=berlin) - later = now + timedelta(minutes=30) + now = dt.datetime.now(tz=berlin) + later = now + dt.timedelta(minutes=30) assert naturaldelta(later, when=now) == "30 minutes" """