timesince and timeuntil - should we use python-dateutil?

242 views
Skip to first unread message

אורי

unread,
Aug 1, 2022, 9:27:59 PM8/1/22
to Django developers (Contributions to Django itself)
Hi,

Lately I discovered some inaccuracy bugs in timesince (and timeuntil), and I created a ticket (#33879). In short, the problems are when calculating a year minus one week, 2 weeks, one day etc. and also 2 years minus one or 2 days. In all such cases, Django's implementation of timesince is inaccurate, in some cases resulting in "1 year, 12 months" (for 2 years minus one or 2 days) or adding a week (for a year minus one or 2 weeks). I read the code of Django's timesince implementation and it's quite long, and I searched and found that python-dateutil already have such methods, which is possible to calculate exactly the number of days between two dates like this:

from dateutil.relativedelta import relativedelta

diff = relativedelta(date2, date1)

years = diff.years
months = diff.months
weeks = diff.days // 7
days = diff.days - weeks * 7 # ("
diff.days % 7" will also work here)

This calculates exactly the number of years, months, weeks and days and not an approximation like Django's implementation. And python-dateutil is stable and I think exists even before Django. I recently started using Django's timesince function in my own website, and I like the way it's translated (currently only to English and Hebrew). Do you think it's worth using python-dateutil in the Django implementation of timesince and timeuntil?

Of course, I can also implement my own version of timesince which uses python-dateutil and doesn't use Django. But since timesince and timeuntil are built-in tags in Django, I guess many websites are using them. Isn't it better to use a more precise implementation and avoid something like "1 year, 12 months"?

Thanks,
Uri Rodberg, Speedy Net

Carlton Gibson

unread,
Aug 2, 2022, 3:55:04 AM8/2/22
to django-d...@googlegroups.com
Hey Uri. 

Historically, taking on extra dependencies isn't something we've done lightly. 
The packaging situation is a lot better these days than it was in earlier years, but with the concerns about supply chain security I think maintaining that position is worthwhile. 
If we were to take on python-dateutil as a required dependency there would be folks needing to go through an additional security audit in order to get permission to upgrade Django. I wouldn't think two template tags justifies that. 

So then, it would need to be optional. 
But then it could just live in a third-party package. 
(I don't think we really document it, but one could shadow/override the built-in tags with the improved versions if your app was installed, I guess...) 

Or that would be my initial thought. 🤔

Kind Regards,

Carlton


--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CABD5YeEC_o%2BwQVF8pj3Kat57MXfX071LT0rZAq2XipFFCK%3DFBQ%40mail.gmail.com.

אורי

unread,
Aug 2, 2022, 5:06:24 AM8/2/22
to Django developers (Contributions to Django itself)
Hi Carlton,

I understand, thank you.

Uri Rodberg, Speedy Net

אורי

unread,
Aug 2, 2022, 11:31:33 AM8/2/22
to Django developers (Contributions to Django itself)
Hi,

I created my own utility function:

from dateutil.relativedelta import relativedelta

from django.utils.timesince import TIME_STRINGS as timesince_time_strings
from django.utils.html import avoid_wrapping
from django.utils.translation import gettext, get_language

def timesince(d, now):
    delta = relativedelta(now, d)

    years = delta.years
    months = delta.months
    weeks = delta.days // 7
    days = delta.days - weeks * 7

    timesince_counts = [(years, "year"), (months, "month")]
    if (years == 0):
        timesince_counts.append((weeks, "week"))
        if (months == 0):
            timesince_counts.append((days, "day"))

    result = []
    for (count, name) in timesince_counts:
        if (count > 0):
            result.append(avoid_wrapping(value=timesince_time_strings[name] % {"num": count}))
    return gettext(", ").join(result)


I don't need depth>2, I don't need hours and minutes and by definition my function returns "" if both dates are the same. now must be bigger than d or else I don't know what will happen... I think you just get "" otherwise.


On Tue, Aug 2, 2022 at 10:54 AM Carlton Gibson <carlton...@gmail.com> wrote:

st...@jigsawtech.co.uk

unread,
Aug 3, 2022, 4:40:48 AM8/3/22
to Django developers (Contributions to Django itself)
I'd suggest the docs are updated with a warning relating to what's Uri's found as it could be extremely important to someone and not highlighting it would be misleading .
Reply all
Reply to author
Forward
0 new messages