From 4af8a0095ab2afd21ef7915476224fdfc799caa8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Tue, 30 Nov 2021 20:01:04 +0100
Subject: [PATCH] templates: add a |duration filter (#25418)

---
 tests/test_templates.py           | 28 ++++++++++++++++++++++++++++
 wcs/qommon/humantime.py           | 25 +++++++++++++++++--------
 wcs/qommon/templatetags/qommon.py | 16 ++++++++++++++++
 3 files changed, 61 insertions(+), 8 deletions(-)

diff --git a/tests/test_templates.py b/tests/test_templates.py
index c27ee85e7..c2758a0f9 100644
--- a/tests/test_templates.py
+++ b/tests/test_templates.py
@@ -4,6 +4,7 @@ import string
 
 import pytest
 from django.test import override_settings
+from django.utils import translation
 from django.utils.timezone import now
 
 try:
@@ -1293,3 +1294,30 @@ def test_newline(pub):
     context = pub.substitutions.get_context_variables()
     assert Template('a{% newline %}b').render(context) == 'a\nb'
     assert Template('a{% newline windows=True %}b').render(context) == 'a\r\nb'
+
+
+def test_duration(pub):
+    pub.ngettext = translation.ngettext
+
+    context = {'value': 4800}
+    assert Template('{{ value|duration }}').render(context) == '1h20'
+    assert Template('{{ value|duration:"long" }}').render(context) == '1 hour and 20 minutes'
+
+    context = {'value': 2400}
+    assert Template('{{ value|duration }}').render(context) == '40min'
+    assert Template('{{ value|duration:"long" }}').render(context) == '40 minutes'
+
+    context = {'value': 7200}
+    assert Template('{{ value|duration }}').render(context) == '2h'
+    assert Template('{{ value|duration:"long" }}').render(context) == '2 hours'
+
+    context = {'value': 90600}
+    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 = {'value': 3660}
+    assert Template('{{ value|duration }}').render(context) == '1h01'
+
+    context = {'value': 'xx'}
+    assert Template('{{ value|duration }}').render(context) == ''
+    assert Template('{{ value|duration:"long" }}').render(context) == ''
diff --git a/wcs/qommon/humantime.py b/wcs/qommon/humantime.py
index 5b6c21bd5..d1e8fe700 100644
--- a/wcs/qommon/humantime.py
+++ b/wcs/qommon/humantime.py
@@ -65,7 +65,7 @@ def humanduration2seconds(humanduration):
     return seconds
 
 
-def seconds2humanduration(seconds):
+def seconds2humanduration(seconds, short=False):
     """Convert a time range in seconds to a human string representation"""
     if not isinstance(seconds, int):
         return ""
@@ -79,10 +79,19 @@ def seconds2humanduration(seconds):
     human = []
     if days:
         human.append(ngettext('%(total)s day', '%(total)s days', days) % {'total': days})
-    if hours:
-        human.append(ngettext('%(total)s hour', '%(total)s hours', hours) % {'total': hours})
-    if minutes:
-        human.append(ngettext('%(total)s minute', '%(total)s minutes', minutes) % {'total': minutes})
-    if seconds:
-        human.append(ngettext('%(total)s second', '%(total)s seconds', seconds) % {'total': seconds})
-    return list2human(human)
+    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('%(total)s hour', '%(total)s hours', hours) % {'total': hours})
+        if minutes:
+            human.append(ngettext('%(total)s minute', '%(total)s minutes', minutes) % {'total': minutes})
+        if seconds:
+            human.append(ngettext('%(total)s second', '%(total)s seconds', seconds) % {'total': seconds})
+        return list2human(human)
diff --git a/wcs/qommon/templatetags/qommon.py b/wcs/qommon/templatetags/qommon.py
index 1c6eb377c..b30b97098 100644
--- a/wcs/qommon/templatetags/qommon.py
+++ b/wcs/qommon/templatetags/qommon.py
@@ -52,6 +52,7 @@ from django.utils.timezone import is_naive, make_aware
 
 from wcs.qommon import calendar, evalutils, tokens, upload_storage
 from wcs.qommon.admin.texts import TextsDirectory
+from wcs.qommon.humantime import seconds2humanduration
 
 register = template.Library()
 
@@ -256,6 +257,21 @@ def decimal(value, arg=None):
     return defaultfilters.floatformat(value, arg=arg)
 
 
+@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
+    value = unlazy(value)
+    arg = unlazy(arg)
+    if not isinstance(value, datetime.timedelta):
+        try:
+            value = datetime.timedelta(seconds=int(value))
+        except (TypeError, ValueError):
+            return ''
+    return seconds2humanduration(int(value.total_seconds()), short=bool(arg != 'long'))
+
+
 @register.filter(expects_localtime=True, is_safe=False)
 def add_days(value, arg):
     if hasattr(value, 'timetuple'):
-- 
2.34.1

