77
88import datetime as dt
99import math
10+ import warnings
1011from enum import Enum
1112from functools import total_ordering
1213
1314from .i18n import _gettext as _
1415from .i18n import _ngettext
16+ from .number import intcomma
1517
1618__all__ = [
1719 "naturaldelta" ,
2325
2426
2527@total_ordering
26- class _Unit (Enum ):
28+ class Unit (Enum ):
2729 MICROSECONDS = 0
2830 MILLISECONDS = 1
2931 SECONDS = 2
@@ -98,6 +100,8 @@ def naturaldelta(
98100 minimum_unit (str): The lowest unit that can be used.
99101 when (datetime.datetime): Point in time relative to which _value_ is
100102 interpreted. Defaults to the current time in the local timezone.
103+ Deprecated in version 3.14; If you need to construct a timedelta,
104+ do it inline as the first argument.
101105
102106 Returns:
103107 str: A natural representation of the amount of time elapsed.
@@ -114,8 +118,16 @@ def naturaldelta(
114118
115119 assert naturaldelta(later, when=now) == "30 minutes"
116120 """
117- tmp = _Unit [minimum_unit .upper ()]
118- if tmp not in (_Unit .SECONDS , _Unit .MILLISECONDS , _Unit .MICROSECONDS ):
121+ if when :
122+ warnings .warn (
123+ "The `when` parameter of `naturaldelta()` is deprecated and will be "
124+ "removed in humanize 4.0. If you need to construct a timedelta, "
125+ "do it inline as the first argument." ,
126+ DeprecationWarning ,
127+ stacklevel = 2 ,
128+ )
129+ tmp = Unit [minimum_unit .upper ()]
130+ if tmp not in (Unit .SECONDS , Unit .MILLISECONDS , Unit .MICROSECONDS ):
119131 raise ValueError (f"Minimum unit '{ minimum_unit } ' not supported" )
120132 minimum_unit = tmp
121133
@@ -133,13 +145,13 @@ def naturaldelta(
133145
134146 if not years and days < 1 :
135147 if seconds == 0 :
136- if minimum_unit == _Unit .MICROSECONDS and delta .microseconds < 1000 :
148+ if minimum_unit == Unit .MICROSECONDS and delta .microseconds < 1000 :
137149 return (
138150 _ngettext ("%d microsecond" , "%d microseconds" , delta .microseconds )
139151 % delta .microseconds
140152 )
141- elif minimum_unit == _Unit .MILLISECONDS or (
142- minimum_unit == _Unit .MICROSECONDS
153+ elif minimum_unit == Unit .MILLISECONDS or (
154+ minimum_unit == Unit .MICROSECONDS
143155 and 1000 <= delta .microseconds < 1_000_000
144156 ):
145157 milliseconds = delta .microseconds / 1000
@@ -189,7 +201,7 @@ def naturaldelta(
189201 else :
190202 return _ngettext ("1 year, %d day" , "1 year, %d days" , days ) % days
191203 else :
192- return _ngettext ("%d year" , "%d years" , years ) % years
204+ return _ngettext ("%s year" , "%s years" , years ) % intcomma ( years )
193205
194206
195207def naturaltime (
@@ -284,20 +296,20 @@ def _quotient_and_remainder(value, divisor, unit, minimum_unit, suppress):
284296 represent the remainder because it would require a unit smaller than the
285297 `minimum_unit`.
286298
287- >>> from humanize.time import _quotient_and_remainder, _Unit
288- >>> _quotient_and_remainder(36, 24, _Unit .DAYS, _Unit .DAYS, [])
299+ >>> from humanize.time import _quotient_and_remainder, Unit
300+ >>> _quotient_and_remainder(36, 24, Unit .DAYS, Unit .DAYS, [])
289301 (1.5, 0)
290302
291303 If unit is in `suppress`, the quotient will be zero and the remainder will be the
292304 initial value. The idea is that if we cannot use `unit`, we are forced to use a
293305 lower unit so we cannot do the division.
294306
295- >>> _quotient_and_remainder(36, 24, _Unit .DAYS, _Unit .HOURS, [_Unit .DAYS])
307+ >>> _quotient_and_remainder(36, 24, Unit .DAYS, Unit .HOURS, [Unit .DAYS])
296308 (0, 36)
297309
298310 In other case return quotient and remainder as `divmod` would do it.
299311
300- >>> _quotient_and_remainder(36, 24, _Unit .DAYS, _Unit .HOURS, [])
312+ >>> _quotient_and_remainder(36, 24, Unit .DAYS, Unit .HOURS, [])
301313 (1, 12)
302314
303315 """
@@ -316,20 +328,20 @@ def _carry(value1, value2, ratio, unit, min_unit, suppress):
316328 (carry to right). The idea is that if we cannot represent `value1` we need to
317329 represent it in a lower unit.
318330
319- >>> from humanize.time import _carry, _Unit
320- >>> _carry(2, 6, 24, _Unit .DAYS, _Unit .SECONDS, [_Unit .DAYS])
331+ >>> from humanize.time import _carry, Unit
332+ >>> _carry(2, 6, 24, Unit .DAYS, Unit .SECONDS, [Unit .DAYS])
321333 (0, 54)
322334
323335 If the unit is the minimum unit, `value2` is divided by `ratio` and added to
324336 `value1` (carry to left). We assume that `value2` has a lower unit so we need to
325337 carry it to `value1`.
326338
327- >>> _carry(2, 6, 24, _Unit .DAYS, _Unit .DAYS, [])
339+ >>> _carry(2, 6, 24, Unit .DAYS, Unit .DAYS, [])
328340 (2.25, 0)
329341
330342 Otherwise, just return the same input:
331343
332- >>> _carry(2, 6, 24, _Unit .DAYS, _Unit .SECONDS, [])
344+ >>> _carry(2, 6, 24, Unit .DAYS, Unit .SECONDS, [])
333345 (2, 6)
334346 """
335347 if unit == min_unit :
@@ -345,21 +357,21 @@ def _suitable_minimum_unit(min_unit, suppress):
345357
346358 If not suppressed, return the same unit:
347359
348- >>> from humanize.time import _suitable_minimum_unit, _Unit
349- >>> _suitable_minimum_unit(_Unit .HOURS, []).name
360+ >>> from humanize.time import _suitable_minimum_unit, Unit
361+ >>> _suitable_minimum_unit(Unit .HOURS, []).name
350362 'HOURS'
351363
352364 But if suppressed, find a unit greather than the original one that is not
353365 suppressed:
354366
355- >>> _suitable_minimum_unit(_Unit .HOURS, [_Unit .HOURS]).name
367+ >>> _suitable_minimum_unit(Unit .HOURS, [Unit .HOURS]).name
356368 'DAYS'
357369
358- >>> _suitable_minimum_unit(_Unit .HOURS, [_Unit .HOURS, _Unit .DAYS]).name
370+ >>> _suitable_minimum_unit(Unit .HOURS, [Unit .HOURS, Unit .DAYS]).name
359371 'MONTHS'
360372 """
361373 if min_unit in suppress :
362- for unit in _Unit :
374+ for unit in Unit :
363375 if unit > min_unit and unit not in suppress :
364376 return unit
365377
@@ -373,12 +385,12 @@ def _suitable_minimum_unit(min_unit, suppress):
373385def _suppress_lower_units (min_unit , suppress ):
374386 """Extend suppressed units (if any) with all units lower than the minimum unit.
375387
376- >>> from humanize.time import _suppress_lower_units, _Unit
377- >>> [x.name for x in sorted(_suppress_lower_units(_Unit .SECONDS, [_Unit .DAYS]))]
388+ >>> from humanize.time import _suppress_lower_units, Unit
389+ >>> [x.name for x in sorted(_suppress_lower_units(Unit .SECONDS, [Unit .DAYS]))]
378390 ['MICROSECONDS', 'MILLISECONDS', 'DAYS']
379391 """
380392 suppress = set (suppress )
381- for u in _Unit :
393+ for u in Unit :
382394 if u == min_unit :
383395 break
384396 suppress .add (u )
@@ -457,11 +469,11 @@ def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ->
457469 if date is None :
458470 return value
459471
460- suppress = [_Unit [s .upper ()] for s in suppress ]
472+ suppress = [Unit [s .upper ()] for s in suppress ]
461473
462474 # Find a suitable minimum unit (it can be greater the one that the
463475 # user gave us if it is suppressed).
464- min_unit = _Unit [minimum_unit .upper ()]
476+ min_unit = Unit [minimum_unit .upper ()]
465477 min_unit = _suitable_minimum_unit (min_unit , suppress )
466478 del minimum_unit
467479
@@ -475,7 +487,7 @@ def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ->
475487 usecs = delta .microseconds
476488
477489 MICROSECONDS , MILLISECONDS , SECONDS , MINUTES , HOURS , DAYS , MONTHS , YEARS = list (
478- _Unit
490+ Unit
479491 )
480492
481493 # Given DAYS compute YEARS and the remainder of DAYS as follows:
@@ -524,12 +536,16 @@ def precisedelta(value, minimum_unit="seconds", suppress=(), format="%0.2f") ->
524536 ]
525537
526538 texts = []
527- for unit , fmt in zip (reversed (_Unit ), fmts ):
539+ for unit , fmt in zip (reversed (Unit ), fmts ):
528540 singular_txt , plural_txt , value = fmt
529541 if value > 0 or (not texts and unit == min_unit ):
530542 fmt_txt = _ngettext (singular_txt , plural_txt , value )
531543 if unit == min_unit and math .modf (value )[0 ] > 0 :
532544 fmt_txt = fmt_txt .replace ("%d" , format )
545+ elif unit == YEARS :
546+ fmt_txt = fmt_txt .replace ("%d" , "%s" )
547+ texts .append (fmt_txt % intcomma (value ))
548+ continue
533549
534550 texts .append (fmt_txt % value )
535551
0 commit comments