@@ -347,79 +347,41 @@ def _quotient_and_remainder(
347347 unit : Unit ,
348348 minimum_unit : Unit ,
349349 suppress : Iterable [Unit ],
350+ format : str ,
350351) -> tuple [float , float ]:
351352 """Divide `value` by `divisor`, returning the quotient and remainder.
352353
353- If `unit` is `minimum_unit`, the quotient will be a float number and the remainder
354- will be zero. The rationale is that if `unit` is the unit of the quotient, we cannot
355- represent the remainder because it would require a unit smaller than the
356- `minimum_unit`.
354+ If `unit` is `minimum_unit`, the quotient will be the rounding of `value / divisor`
355+ according to the `format` string and the remainder will be zero. The rationale is
356+ that if `unit` is the unit of the quotient, we cannot represent the remainder
357+ because it would require a unit smaller than the `minimum_unit`.
357358
358359 >>> from humanize.time import _quotient_and_remainder, Unit
359- >>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.DAYS, [])
360+ >>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.DAYS, [], "%0.2f" )
360361 (1.5, 0)
361362
362363 If `unit` is in `suppress`, the quotient will be zero and the remainder will be the
363364 initial value. The idea is that if we cannot use `unit`, we are forced to use a
364365 lower unit, so we cannot do the division.
365366
366- >>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.HOURS, [Unit.DAYS])
367+ >>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.HOURS, [Unit.DAYS], "%0.2f" )
367368 (0, 36)
368369
369370 In other cases, return the quotient and remainder as `divmod` would do it.
370371
371- >>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.HOURS, [])
372+ >>> _quotient_and_remainder(36, 24, Unit.DAYS, Unit.HOURS, [], "%0.2f" )
372373 (1, 12)
373374
374375 """
375376 if unit == minimum_unit :
376- return value / divisor , 0
377+ return _rounding_by_fmt ( format , value / divisor ) , 0
377378
378379 if unit in suppress :
379380 return 0 , value
380381
381382 return divmod (value , divisor )
382383
383384
384- def _carry (
385- value1 : float ,
386- value2 : float ,
387- ratio : float ,
388- unit : Unit ,
389- min_unit : Unit ,
390- suppress : Iterable [Unit ],
391- ) -> tuple [float , float ]:
392- """Return a tuple with two values.
393-
394- If `unit` is in `suppress`, multiply `value1` by `ratio` and add it to `value2`
395- (carry to right). The idea is that if we cannot represent `value1`, we need to
396- represent it in a lower unit.
397-
398- >>> from humanize.time import _carry, Unit
399- >>> _carry(2, 6, 24, Unit.DAYS, Unit.SECONDS, [Unit.DAYS])
400- (0, 54)
401-
402- If `unit` is the minimum unit, divide `value2` by `ratio` and add it to `value1`
403- (carry to left). We assume that `value2` has a lower unit, so we need to
404- carry it to `value1`.
405-
406- >>> _carry(2, 6, 24, Unit.DAYS, Unit.DAYS, [])
407- (2.25, 0)
408-
409- Otherwise, just return the same input:
410-
411- >>> _carry(2, 6, 24, Unit.DAYS, Unit.SECONDS, [])
412- (2, 6)
413- """
414- if unit == min_unit :
415- return value1 + value2 / ratio , 0
416-
417- if unit in suppress :
418- return 0 , value2 + value1 * ratio
419-
420- return value1 , value2
421-
422-
423385def _suitable_minimum_unit (min_unit : Unit , suppress : Iterable [Unit ]) -> Unit :
424386 """Return a minimum unit suitable that is not suppressed.
425387
@@ -574,23 +536,57 @@ def precisedelta(
574536 # years, days = divmod(years, days)
575537 #
576538 # The same applies for months, hours, minutes and milliseconds below
577- years , days = _quotient_and_remainder (days , 365 , YEARS , min_unit , suppress_set )
578- months , days = _quotient_and_remainder (days , 30.5 , MONTHS , min_unit , suppress_set )
539+ years , days = _quotient_and_remainder (
540+ days , 365 , YEARS , min_unit , suppress_set , format
541+ )
542+ months , days = _quotient_and_remainder (
543+ days , 30.5 , MONTHS , min_unit , suppress_set , format
544+ )
579545
580546 secs = days * 24 * 3600 + secs
581- days , secs = _quotient_and_remainder (secs , 24 * 3600 , DAYS , min_unit , suppress_set )
547+ days , secs = _quotient_and_remainder (
548+ secs , 24 * 3600 , DAYS , min_unit , suppress_set , format
549+ )
582550
583- hours , secs = _quotient_and_remainder (secs , 3600 , HOURS , min_unit , suppress_set )
584- minutes , secs = _quotient_and_remainder (secs , 60 , MINUTES , min_unit , suppress_set )
551+ hours , secs = _quotient_and_remainder (
552+ secs , 3600 , HOURS , min_unit , suppress_set , format
553+ )
554+ minutes , secs = _quotient_and_remainder (
555+ secs , 60 , MINUTES , min_unit , suppress_set , format
556+ )
585557
586- secs , usecs = _carry (secs , usecs , 1e6 , SECONDS , min_unit , suppress_set )
558+ usecs = secs * 1e6 + usecs
559+ secs , usecs = _quotient_and_remainder (
560+ usecs , 1e6 , SECONDS , min_unit , suppress_set , format
561+ )
587562
588563 msecs , usecs = _quotient_and_remainder (
589- usecs , 1000 , MILLISECONDS , min_unit , suppress_set
564+ usecs , 1000 , MILLISECONDS , min_unit , suppress_set , format
590565 )
591566
592- # if _unused != 0 we have lost some precision
593- usecs , _unused = _carry (usecs , 0 , 1 , MICROSECONDS , min_unit , suppress_set )
567+ # Due to rounding, it could be that a unit is high enough to be promoted to a higher
568+ # unit. Example: 59.9 minutes was rounded to 60 minutes, and thus it should become 0
569+ # minutes and one hour more.
570+ if msecs >= 1_000 and SECONDS not in suppress_set :
571+ msecs -= 1_000
572+ secs += 1
573+ if secs >= 60 and MINUTES not in suppress_set :
574+ secs -= 60
575+ minutes += 1
576+ if minutes >= 60 and HOURS not in suppress_set :
577+ minutes -= 60
578+ hours += 1
579+ if hours >= 24 and DAYS not in suppress_set :
580+ hours -= 24
581+ days += 1
582+ # When adjusting we should not deal anymore with fractional days as all rounding has
583+ # been already made. We promote 31 days to an extra month.
584+ if days >= 31 and MONTHS not in suppress_set :
585+ days -= 31
586+ months += 1
587+ if months >= 12 and YEARS not in suppress_set :
588+ months -= 12
589+ years += 1
594590
595591 fmts = [
596592 ("%d year" , "%d years" , years ),
@@ -606,10 +602,6 @@ def precisedelta(
606602 texts : list [str ] = []
607603 for unit , fmt in zip (reversed (Unit ), fmts ):
608604 singular_txt , plural_txt , fmt_value = fmt
609-
610- if unit == min_unit :
611- fmt_value = _rounding_by_fmt (format , fmt_value )
612-
613605 if fmt_value > 0 or (not texts and unit == min_unit ):
614606 _fmt_value = 2 if 1 < fmt_value < 2 else int (fmt_value )
615607 fmt_txt = _ngettext (singular_txt , plural_txt , _fmt_value )
0 commit comments