From b8569a989fd30ca188d4be70816a0ab10c3dc4ea Mon Sep 17 00:00:00 2001
From: Thomas NOEL <tnoel@entrouvert.com>
Date: Fri, 18 Jan 2019 09:21:09 +0100
Subject: [PATCH] templatetags: add add_days and add_hours filters (#29406)

---
 tests/test_formdata.py            | 83 +++++++++++++++++++++++++++++++
 tests/test_templates.py           | 21 ++++++++
 wcs/qommon/templatetags/qommon.py | 32 ++++++++++++
 3 files changed, 136 insertions(+)

diff --git a/tests/test_formdata.py b/tests/test_formdata.py
index f224165b..156755db 100644
--- a/tests/test_formdata.py
+++ b/tests/test_formdata.py
@@ -944,6 +944,89 @@ def test_lazy_date_templatetags(pub, variable_test_data):
     condition = Condition({'type': 'django', 'value': 'today == now|date'})
     assert condition.evaluate() is True
 
+def test_lazy_date_with_maths(pub, variable_test_data):
+    # form_var_datefield  : 2018-07-31
+    # form_var_datefield2 : 2018-08-31
+    # form_var_datestring : "2018-07-31"
+    for condition_value in (
+            'form_var_datefield|add_days:0 <= "2019-01-01"',
+            'form_var_datefield|add_days:0 >= "1980-01-01"',
+            'form_var_datefield|add_days:0 == "2018-07-31"',
+            'form_var_datefield|add_days:0 == "31/07/2018"',
+            'form_var_datefield|add_days:5 == "2018-08-05"',
+            'form_var_datefield|add_days:5 <= "2018-08-05"',
+            'form_var_datefield|add_days:5 >= "2018-08-05"',
+            'form_var_datefield|add_days:-5 == "2018-07-26"',
+            'form_var_datefield|add_days:36500 > "2100-01-01"',   # 2118
+            'form_var_datefield|add_days:-36500 < "1950-01-01"',  # 1918
+            'form_var_datefield|add_days:31 == form_var_datefield2',
+            'form_var_datefield|add_days:5|add_days:-5 == form_var_datestring',
+            'form_var_datestring|add_days:31 == form_var_datefield2',
+            'form_var_datestring|add_days:32 > form_var_datefield2',
+            'form_var_datestring|add_days:30 < form_var_datefield2',
+            ):
+        condition = Condition({'type': 'django', 'value': condition_value})
+        assert condition.evaluate() is True
+
+    # form_var_datefield is in 2018, we hope today/now is after 2019
+    for condition_value in (
+            'form_var_datefield <= today',
+            '"1970-01-01"|add_days:0 < today',
+            '"2100-12-31"|add_days:0 > today',
+            'form_var_datefield|add_days:0 <= today',
+            'form_var_datefield|add_days:10 < today',
+            'form_var_datefield|add_days:36500 > today',
+            'form_var_datefield|add_days:36500 >= today',
+            'form_var_datefield <= today|add_days:0',
+            'form_var_datefield < today|add_days:-10',
+            'form_var_datefield > today|add_days:-36500',
+            'form_var_datefield >= today|add_days:-36500',
+            ):
+        condition = Condition({'type': 'django', 'value': condition_value})
+        assert condition.evaluate() is True
+        condition = Condition({'type': 'django', 'value': condition_value.replace('today', 'now')})
+        assert condition.evaluate() is True
+
+    for condition_value in (
+            '"1970-01-01"|add_hours:0 < today',
+            '"2100-12-31"|add_hours:0 > today',
+            'form_var_datefield|add_hours:0 <= today',
+            'form_var_datefield|add_hours:10 < today',
+            'form_var_datefield|add_hours:876000 > today',  # + 100 years
+            'form_var_datefield|add_hours:876000 >= today',
+            ):
+        condition = Condition({'type': 'django', 'value': condition_value})
+        assert condition.evaluate() is True
+        condition = Condition({'type': 'django', 'value': condition_value.replace('today', 'now')})
+        assert condition.evaluate() is True
+
+    for condition_value in (
+            'today|add_days:0 == today',
+            'today|add_hours:0 == today',
+            'now|add_hours:0 == now',
+            ):
+        condition = Condition({'type': 'django', 'value': condition_value})
+        assert condition.evaluate() is True
+
+    for condition_value in (
+            'today|add_days:2 >= today',
+            'today|add_days:2 > today',
+            'today|add_days:2 != today',
+            'not today|add_days:2 == today',
+            'not today|add_days:2 <= today',
+            'not today|add_days:2 < today',
+            'today|add_days:-2 <= today',
+            'today|add_days:-2 < today',
+            'today|add_days:-2 != today',
+            'not today|add_days:-2 >= today',
+            'not today|add_days:-2 > today',
+            'not today|add_days:-2 == today',
+            ):
+        condition = Condition({'type': 'django', 'value': condition_value})
+        assert condition.evaluate() is True
+        condition = Condition({'type': 'django', 'value': condition_value.replace('today', 'now')})
+        assert condition.evaluate() is True
+
 def test_lazy_templates(pub, variable_test_data):
     context = pub.substitutions.get_context_variables(mode='lazy')
     tmpl = Template('{{form_var_foo_foo}}')
diff --git a/tests/test_templates.py b/tests/test_templates.py
index e22b57c3..14db4c7c 100644
--- a/tests/test_templates.py
+++ b/tests/test_templates.py
@@ -259,6 +259,27 @@ def test_datetime_templatetags():
     assert tmpl.render({'plop': {'foo': 'bar'}}) == ''
     assert tmpl.render() == ''
 
+def test_date_maths():
+    tmpl = Template('{{ plop|add_days:4 }}')
+    assert tmpl.render({'plop': '2017-12-21'}) == 'Dec. 25, 2017'
+    assert tmpl.render({'plop': '2017-12-21 18:00'}) == 'Dec. 25, 2017'
+    tmpl = Template('{{ plop|add_days:"-1" }}')
+    assert tmpl.render({'plop': '2017-12-21'}) == 'Dec. 20, 2017'
+    assert tmpl.render({'plop': '2017-12-21 18:00'}) == 'Dec. 20, 2017'
+    tmpl = Template('{{ plop|add_days:1.5 }}')
+    assert tmpl.render({'plop': '2017-12-21'}) == 'Dec. 22, 2017'
+    assert tmpl.render({'plop': '2017-12-21 18:00'}) == 'Dec. 22, 2017'
+    tmpl = Template('{{ plop|add_days:"1.5" }}')
+    assert tmpl.render({'plop': '2017-12-21'}) == 'Dec. 22, 2017'
+    assert tmpl.render({'plop': '2017-12-21 18:00'}) == 'Dec. 22, 2017'
+
+    tmpl = Template('{{ plop|add_hours:24 }}')
+    assert tmpl.render({'plop': '2017-12-21'}) == 'Dec. 22, 2017, midnight'
+    assert tmpl.render({'plop': '2017-12-21 18:00'}) == 'Dec. 22, 2017, 6 p.m.'
+    tmpl = Template('{{ plop|add_hours:"12.5" }}')
+    assert tmpl.render({'plop': '2017-12-21'}) == 'Dec. 21, 2017, 12:30 p.m.'
+    assert tmpl.render({'plop': '2017-12-21 18:00'}) == 'Dec. 22, 2017, 6:30 a.m.'
+
 def test_variable_unicode_error_handling():
     tmpl = Template('{{ form_var_éléphant }}')
     assert tmpl.render() == ''
diff --git a/wcs/qommon/templatetags/qommon.py b/wcs/qommon/templatetags/qommon.py
index 6b556639..af473cd5 100644
--- a/wcs/qommon/templatetags/qommon.py
+++ b/wcs/qommon/templatetags/qommon.py
@@ -14,6 +14,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
+import copy
 import datetime
 from decimal import Decimal
 
@@ -130,6 +131,37 @@ def decimal(value, arg=None):
         value = parse_decimal(value)
     return defaultfilters.floatformat(value, arg=arg)
 
+@register.filter(expects_localtime=True, is_safe=False)
+def add_days(value, arg):
+    if hasattr(value, 'timetuple'):
+        # extract real value in case of lazy object
+        value = value.timetuple()
+    value = parse_date(value)  # consider only date, not hours
+    if not value:
+        return ''
+    from wcs.variables import lazy_date
+    arg = parse_decimal(arg)
+    if not arg:
+        return lazy_date(value)
+    result = value + datetime.timedelta(days=float(arg))
+    if hasattr(result, 'date'):
+        result = result.date()
+    return lazy_date(result)
+
+@register.filter(expects_localtime=True, is_safe=False)
+def add_hours(value, arg):
+    if hasattr(value, 'timetuple'):
+        # extract real value in case of lazy object
+        value = value.timetuple()
+    value = parse_datetime(value)
+    if not value:
+        return ''
+    from wcs.variables import lazy_date
+    arg = parse_decimal(arg)
+    if not arg:
+        return lazy_date(value)
+    return lazy_date(value + datetime.timedelta(hours=float(arg)))
+
 @register.simple_tag
 def standard_text(text_id):
     return mark_safe(TextsDirectory.get_html_text(str(text_id)))
-- 
2.20.1

