Skip to content

Commit d30c2b6

Browse files
authored
Merge pull request #144 from phijor/master
2 parents 3993a24 + b37dc30 commit d30c2b6

3 files changed

Lines changed: 55 additions & 6 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ repos:
66
args: ["--py36-plus"]
77

88
- repo: https://github.com/psf/black
9-
rev: 19.10b0
9+
rev: 20.8b1
1010
hooks:
1111
- id: black
1212
args: ["--target-version", "py36"]

src/humanize/time.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def date_and_delta(value, *, now=None):
8181
return date, abs_timedelta(delta)
8282

8383

84-
def naturaldelta(value, months=True, minimum_unit="seconds"):
84+
def naturaldelta(value, months=True, minimum_unit="seconds", when=None):
8585
"""Return a natural representation of a timedelta or number of seconds.
8686
8787
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"):
9191
months (bool): If `True`, then a number of months (based on 30.5 days) will be
9292
used for fuzziness between years.
9393
minimum_unit (str): The lowest unit that can be used.
94+
when (datetime.timedelta): Point in time relative to which _value_ is
95+
interpreted. Defaults to the current time in the local timezone.
9496
9597
Returns:
9698
str: A natural representation of the amount of time elapsed.
99+
100+
Examples
101+
Compare two timestamps in a custom local timezone::
102+
103+
import datetime as dt
104+
from dateutil.tz import gettz
105+
106+
berlin = gettz("Europe/Berlin")
107+
now = dt.datetime.now(tz=berlin)
108+
later = now + dt.timedelta(minutes=30)
109+
110+
assert naturaldelta(later, when=now) == "30 minutes"
97111
"""
98112
tmp = Unit[minimum_unit.upper()]
99113
if tmp not in (Unit.SECONDS, Unit.MILLISECONDS, Unit.MICROSECONDS):
100114
raise ValueError(f"Minimum unit '{minimum_unit}' not supported")
101115
minimum_unit = tmp
102116

103-
date, delta = date_and_delta(value)
117+
date, delta = date_and_delta(value, now=when)
104118
if date is None:
105119
return value
106120

@@ -173,7 +187,7 @@ def naturaldelta(value, months=True, minimum_unit="seconds"):
173187
return ngettext("%d year", "%d years", years) % years
174188

175189

176-
def naturaltime(value, future=False, months=True, minimum_unit="seconds"):
190+
def naturaltime(value, future=False, months=True, minimum_unit="seconds", when=None):
177191
"""Return a natural representation of a time in a resolution that makes sense.
178192
179193
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"):
186200
months (bool): If `True`, then a number of months (based on 30.5 days) will be
187201
used for fuzziness between years.
188202
minimum_unit (str): The lowest unit that can be used.
203+
when (datetime.datetime): Point in time relative to which _value_ is
204+
interpreted. Defaults to the current time in the local timezone.
189205
190206
Returns:
191207
str: A natural representation of the input in a resolution that makes sense.
192208
"""
193-
now = _now()
209+
now = when or _now()
194210
date, delta = date_and_delta(value, now=now)
195211
if date is None:
196212
return value
@@ -199,7 +215,7 @@ def naturaltime(value, future=False, months=True, minimum_unit="seconds"):
199215
future = date > now
200216

201217
ago = _("%s from now") if future else _("%s ago")
202-
delta = naturaldelta(delta, months, minimum_unit)
218+
delta = naturaldelta(delta, months, minimum_unit, when=when)
203219

204220
if delta == _("a moment"):
205221
return _("now")

tests/test_time.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
with freeze_time("2020-02-02"):
2626
NOW = dt.datetime.now()
27+
NOW_UTC = dt.datetime.now(tz=dt.timezone.utc)
28+
NOW_UTC_PLUS_01_00 = dt.datetime.now(tz=dt.timezone(offset=dt.timedelta(hours=1)))
2729
TODAY = dt.date.today()
2830
TOMORROW = TODAY + ONE_DAY_DELTA
2931
YESTERDAY = TODAY - ONE_DAY_DELTA
@@ -331,6 +333,37 @@ def test_naturaldelta_minimum_unit_explicit(minimum_unit, seconds, expected):
331333
assert humanize.naturaldelta(delta, minimum_unit=minimum_unit) == expected
332334

333335

336+
@pytest.mark.parametrize(
337+
"test_input, when, expected",
338+
[
339+
(NOW, NOW, "a moment"),
340+
(NOW_UTC, NOW_UTC, "a moment"),
341+
],
342+
)
343+
def test_naturaldelta_when_explicit(test_input, when, expected):
344+
# Act / Assert
345+
assert humanize.naturaldelta(test_input, when=when) == expected
346+
347+
348+
@pytest.mark.parametrize(
349+
"value, when",
350+
[
351+
(NOW_UTC, None),
352+
(NOW_UTC, NOW),
353+
(NOW_UTC_PLUS_01_00, None),
354+
(NOW_UTC_PLUS_01_00, NOW),
355+
],
356+
)
357+
def test_naturaldelta_when_missing_tzinfo(value, when):
358+
"""Subtraction `when - value` is not defined by the `datetime` module when
359+
either operand has not timezone-info (`tz=None`) and raises a TypeError.
360+
"""
361+
362+
# Act / Assert
363+
with pytest.raises(TypeError):
364+
humanize.naturaldelta(value, when=when)
365+
366+
334367
@pytest.mark.parametrize(
335368
"seconds, expected",
336369
[

0 commit comments

Comments
 (0)