Missing option to display years and months when years > 1
What did you do?
In [18]: humanize.naturaldelta(timedelta(days=4.6*365), months=True)
Out[18]: '4 years'
In [19]: humanize.naturaldelta(timedelta(days=4.6*365), months=False)
Out[19]: '4 years'
What did you expect to happen?
In [18]: humanize.naturaldelta(timedelta(days=4.6*365), months=True)
Out[18]: '4 years, 7 months'
In [19]: humanize.naturaldelta(timedelta(days=4.6*365), months=False)
Out[19]: '4 years'
What actually happened?
see above
What versions are you using?
- OS: x86_64 GNU/Linux
- Python: 3.11.3
- Humanize: 4.6.0
How to fix?
# Excerpt from humanize/time.py
# ... more code here
elif years == 1:
if not num_months and not days:
return _("a year")
if not num_months:
return _ngettext("1 year, %d day", "1 year, %d days", days) % days
if use_months:
if num_months == 1:
return _("1 year, 1 month")
return (
_ngettext("1 year, %d month", "1 year, %d months", num_months)
% num_months
)
return _ngettext("1 year, %d day", "1 year, %d days", days) % days
# TODO: Here we should check whether use_months is true and format accordingly.
return _ngettext("%d year", "%d years", years).replace("%d", "%s") % intcomma(years)
What it's doing is rounding to years, when years >= 2:
>>> import humanize
>>> from datetime import timedelta
>>> for year in range(5):
... humanize.naturaldelta(timedelta(days=(year+0.6) * 365), months=True)
...
'7 months'
'1 year, 7 months'
'2 years'
'3 years'
'4 years'
And similarly with months=True, except the granularity is in days not months:
>>> for year in range(5):
... humanize.naturaldelta(timedelta(days=(year+0.6) * 365), months=False)
...
'219 days'
'1 year, 219 days'
'2 years'
'3 years'
'4 years'
And likewise for naturaltime:
>>> for year in range(5):
... humanize.naturaltime(timedelta(days=(year+0.6) * 365), months=True)
...
'7 months ago'
'1 year, 7 months ago'
'2 years ago'
'3 years ago'
'4 years ago'
>>> for year in range(5):
... humanize.naturaltime(timedelta(days=(year+0.6) * 365), months=False)
...
'219 days ago'
'1 year, 219 days ago'
'2 years ago'
'3 years ago'
'4 years ago'
So months=True doesn't control whether to show a more granular time, but what units to use.
Hi @hugovk! Thank you for the explanation. My use case is that we need to display the amortization of an investment. Currently it says "4.6 years" and I thought I could use humanize to turn it into something like "4 years, 7 months" because that's more human friendly. There's a way to do this using a hack:
In [12]: def naturaldelta(delta):
...: if delta < timedelta(days=2*365):
...: return humanize.naturaldelta(delta)
...: return f"{humanize.naturaldelta(delta)},{humanize.naturaldelta(timedelta(days=delta.days%365))}"
...:
In [13]: naturaldelta(timedelta(days=4.6*365))
Out[13]: '4 years,7 months'
In [14]: naturaldelta(timedelta(days=1.6*365))
Out[14]: '1 year, 7 months'
In [15]: naturaldelta(timedelta(days=0.6*365))
Out[15]: '7 months'
The problem with this is that it might break translations.
I guess one idea is to add a new option to make the cutoff configurable, and (I expect) use it to replace the 1 in elif years == 1.
That would also require a lot more handling in that block to deal with singular and plural years, especially for the translations.
Would like to chime in here. I would like to see naturaldelta behavior to be..
- allow 1 or 2 units in output.. i.e.
>>> delta = dt.timedelta(seconds=36, minutes=3)
>>> humanize.naturaldelta(delta)
should allow for either 3 minutes OR 3m36s
- May be >= 10m, we can drop the seconds part without option.
- Similarly for hours, output should be allowed to get configured between
3 hoursvs3h43mand then again, after may be >=10h, we drop the minutes part altogether.
This is a default output format in kubernetes client to show the age. It always shows 2 units (unless we are in seconds)
❯ kubectl get deployments -A
NAMESPACE NAME READY UP-TO-DATE AVAILABLE AGE
kube-system coredns 2/2 2 2 6d14h
local-path-storage local-path-provisioner 1/1 1 1 6d14h
❯ kubectl get pod
NAME READY STATUS RESTARTS AGE
temp 1/1 Running 0 18s
❯ kubectl get pod
NAME READY STATUS RESTARTS AGE
temp 1/1 Running 0 105s
❯ kubectl get pod
NAME READY STATUS RESTARTS AGE
temp 1/1 Running 0 2m4s
Logic for above calculation is here.