diff --git a/publik_django_templatetags/publik/templatetags/publik.py b/publik_django_templatetags/publik/templatetags/publik.py index e853fca..c46e2dd 100644 --- a/publik_django_templatetags/publik/templatetags/publik.py +++ b/publik_django_templatetags/publik/templatetags/publik.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import datetime import math from decimal import Decimal from decimal import DivisionByZero as DecimalDivisionByZero @@ -23,6 +24,8 @@ from django import template from django.template import defaultfilters from django.utils.encoding import force_text +from publik_django_templatetags.publik import utils + register = template.Library() @@ -162,3 +165,16 @@ def sum_(list_): return sum(parse_decimal(term) for term in list_) except TypeError: # list_ is not iterable return '' + + +@register.filter(is_safe=False) +def duration(value, arg='short'): + if arg not in ('short', 'long'): + return '' + # value is expected to be a timedelta or a number of seconds + if not isinstance(value, datetime.timedelta): + try: + value = datetime.timedelta(seconds=int(value) * 60) + except (TypeError, ValueError): + return '' + return utils.seconds2humanduration(int(value.total_seconds()), short=bool(arg != 'long')) diff --git a/publik_django_templatetags/publik/utils.py b/publik_django_templatetags/publik/utils.py new file mode 100644 index 0000000..d2f3c58 --- /dev/null +++ b/publik_django_templatetags/publik/utils.py @@ -0,0 +1,63 @@ +# publik-django-templatetags +# Copyright (C) 2023 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from django.utils.translation import gettext_lazy as _ +from django.utils.translation import ngettext_lazy + +_minute = 60 +_hour = 60 * 60 +_day = _hour * 24 + + +def list2human(stringlist): + '''Transform a string list to human enumeration''' + beginning = stringlist[:-1] + if not beginning: + return "".join(stringlist) + return _("%(first)s and %(second)s") % {'first': _(", ").join(beginning), 'second': stringlist[-1]} + + +def seconds2humanduration(seconds, short=False): + """Convert a time range in seconds to a human string representation""" + if not isinstance(seconds, int): + return "" + + days = int(seconds / _day) + seconds = seconds - _day * days + hours = int(seconds / _hour) + seconds = seconds - _hour * hours + minutes = int(seconds / _minute) + seconds = seconds - _minute * minutes + human = [] + if days: + human.append(ngettext_lazy('%(total)s day', '%(total)s days', days) % {'total': days}) + if short: + if hours and minutes: + human.append(_('%(hours)sh%(minutes)02d') % {'hours': hours, 'minutes': minutes}) + elif hours: + human.append(_('%(hours)sh') % {'hours': hours}) + elif minutes: + human.append(_('%(minutes)smin') % {'minutes': minutes}) + return list2human(human) + else: + if hours: + human.append(ngettext_lazy('%(total)s hour', '%(total)s hours', hours) % {'total': hours}) + if minutes: + human.append(ngettext_lazy('%(total)s minute', '%(total)s minutes', minutes) % {'total': minutes}) + if seconds: + human.append(ngettext_lazy('%(total)s second', '%(total)s seconds', seconds) % {'total': seconds}) + return list2human(human) diff --git a/tests/test_publik.py b/tests/test_publik.py index c34a60e..681632e 100644 --- a/tests/test_publik.py +++ b/tests/test_publik.py @@ -304,3 +304,28 @@ def test_sum(): assert t.render(Context({'list': None})) == '' # list is not iterable assert t.render(Context({'list': '123'})) == '' # consider string as not iterable assert t.render(Context()) == '' + + +def test_duration(): + context = Context({'value': 80}) + assert Template('{{ value|duration }}').render(context) == '1h20' + assert Template('{{ value|duration:"long" }}').render(context) == '1 hour and 20 minutes' + + context = Context({'value': 40}) + assert Template('{{ value|duration }}').render(context) == '40min' + assert Template('{{ value|duration:"long" }}').render(context) == '40 minutes' + + context = Context({'value': 120}) + assert Template('{{ value|duration }}').render(context) == '2h' + assert Template('{{ value|duration:"long" }}').render(context) == '2 hours' + + context = Context({'value': 1510}) + assert Template('{{ value|duration }}').render(context) == '1 day and 1h10' + assert Template('{{ value|duration:"long" }}').render(context) == '1 day, 1 hour and 10 minutes' + + context = Context({'value': 61}) + assert Template('{{ value|duration }}').render(context) == '1h01' + + context = Context({'value': 'xx'}) + assert Template('{{ value|duration }}').render(context) == '' + assert Template('{{ value|duration:"long" }}').render(context) == ''