Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing option to display years and months when years > 1 #108

Open
adonig opened this issue Apr 13, 2023 · 4 comments
Open

Missing option to display years and months when years > 1 #108

adonig opened this issue Apr 13, 2023 · 4 comments

Comments

@adonig
Copy link

adonig commented Apr 13, 2023

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)
@hugovk
Copy link
Member

hugovk commented Apr 13, 2023

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.

@adonig
Copy link
Author

adonig commented Apr 13, 2023

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.

@hugovk
Copy link
Member

hugovk commented Apr 13, 2023

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.

@dharapvj
Copy link

dharapvj commented Mar 5, 2024

Would like to chime in here. I would like to see naturaldelta behavior to be..

  1. 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

  1. May be >= 10m, we can drop the seconds part without option.
  2. Similarly for hours, output should be allowed to get configured between 3 hours vs 3h43m and 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           6d14hkubectl get pod
NAME   READY   STATUS    RESTARTS   AGE
temp   1/1     Running   0          18skubectl get pod
NAME   READY   STATUS    RESTARTS   AGE
temp   1/1     Running   0          105skubectl get pod
NAME   READY   STATUS    RESTARTS   AGE
temp   1/1     Running   0          2m4s

Logic for above calculation is here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants