From 70868eff9b482d1fb524f05233874c0671b0dbca Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 15 Jan 2020 11:57:46 +0100 Subject: [PATCH] templatetags: add date filters (#36943) --- combo/public/templatetags/combo.py | 129 +++++++++++++++++++++++++++- tests/test_public_templatetags.py | 130 +++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+), 1 deletion(-) diff --git a/combo/public/templatetags/combo.py b/combo/public/templatetags/combo.py index b9f6ac6..edcb695 100644 --- a/combo/public/templatetags/combo.py +++ b/combo/public/templatetags/combo.py @@ -27,7 +27,7 @@ from django.core.exceptions import PermissionDenied from django.template import VariableDoesNotExist from django.template.base import TOKEN_BLOCK, TOKEN_VAR, TOKEN_COMMENT from django.template.defaultfilters import stringfilter -from django.utils import dateparse +from django.utils import dateparse, six from django.utils.encoding import force_text from combo.data.models import Page, Placeholder @@ -269,3 +269,130 @@ def get_page(page_slug): @register.filter def startswith(string, substring): return string and force_text(string).startswith(force_text(substring)) + +def parse_float(value): + if isinstance(value, six.string_types): + # replace , by . for French users comfort + value = value.replace(',', '.') + try: + return float(value) + except (ValueError, TypeError): + return '' + +def get_as_datetime(s): + result = parse_datetime(s) + if not result: + result = parse_date(s) + if result: + result = datetime.datetime(year=result.year, month=result.month, day=result.day) + return result + +def make_date(date_var): + '''Extract a date from a datetime, a date, a struct_time or a string''' + if isinstance(date_var, datetime.datetime): + return date_var.date() + if isinstance(date_var, datetime.date): + return date_var + if isinstance(date_var, time.struct_time) or ( + isinstance(date_var, tuple) and len(date_var) == 9): + return datetime.date(*date_var[:3]) + try: + return get_as_datetime(date_var).date() + except AttributeError: + return '' + +def make_datetime(datetime_var): + '''Extract a date from a datetime, a date, a struct_time or a string''' + if isinstance(datetime_var, datetime.datetime): + return datetime_var + if isinstance(datetime_var, datetime.date): + return datetime.datetime(year=datetime_var.year, month=datetime_var.month, + day=datetime_var.day) + if isinstance(datetime_var, time.struct_time) or ( + isinstance(datetime_var, tuple) and len(datetime_var) == 9): + return datetime.datetime(*datetime_var[:6]) + return get_as_datetime(datetime_var) + +@register.filter(expects_localtime=True, is_safe=False) +def add_days(value, arg): + value = make_date(value) # consider only date, not hours + if not value: + return '' + arg = parse_float(arg) + if not arg: + return value + result = value + datetime.timedelta(days=float(arg)) + return result + +@register.filter(expects_localtime=True, is_safe=False) +def add_hours(value, arg): + value = make_datetime(value) + if not value: + return '' + arg = parse_float(arg) + if not arg: + return value + return value + datetime.timedelta(hours=float(arg)) + +@register.filter(expects_localtime=True, is_safe=False) +def age_in_days(value, today=None): + value = make_date(value) + if not value: + return '' + if today is not None: + today = make_date(today) + if not today: + return '' + else: + today = datetime.date.today() + return (today - value).days + +@register.filter(expects_localtime=True, is_safe=False) +def age_in_hours(value, now=None): + # consider value and now as datetimes (and not dates) + value = make_datetime(value) + if not value: + return '' + if now is not None: + now = make_datetime(now) + if not now: + return '' + else: + now = datetime.datetime.now() + return int((now - value).total_seconds() / 3600) + +def age_in_years_and_months(born, today=None): + '''Compute age since today as the number of years and months elapsed''' + born = make_date(born) + if not born: + return '' + if today is not None: + today = make_date(today) + if not today: + return '' + else: + today = datetime.date.today() + before = (today.month, today.day) < (born.month, born.day) + years = today.year - born.year + months = today.month - born.month + if before: + years -= 1 + months += 12 + if today.day < born.day: + months -= 1 + return years, months + +@register.filter(expects_localtime=True, is_safe=False) +def age_in_years(value, today=None): + try: + return age_in_years_and_months(value, today)[0] + except IndexError: + return '' + +@register.filter(expects_localtime=True, is_safe=False) +def age_in_months(value, today=None): + try: + years, months = age_in_years_and_months(value, today) + except ValueError: + return '' + return years * 12 + months diff --git a/tests/test_public_templatetags.py b/tests/test_public_templatetags.py index d5efd57..642ff9b 100644 --- a/tests/test_public_templatetags.py +++ b/tests/test_public_templatetags.py @@ -1,3 +1,4 @@ +import datetime import os import shutil import time @@ -201,3 +202,132 @@ def test_startswith(): assert t.render(context) == '' context = Context({'foo': 'bar'}) assert t.render(context) == 'ok' + +def test_date_maths(): + tmpl = Template('{{ plop|add_days:4 }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 25, 2017' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 25, 2017' + tmpl = Template('{{ plop|add_days:"-1" }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 20, 2017' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 20, 2017' + tmpl = Template('{{ plop|add_days:1.5 }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 22, 2017' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017' + tmpl = Template('{{ plop|add_days:"1.5" }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 22, 2017' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017' + + tmpl = Template('{{ plop|add_hours:24 }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 22, 2017, midnight' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017, 6 p.m.' + tmpl = Template('{{ plop|add_hours:"12.5" }}') + assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 21, 2017, 12:30 p.m.' + assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017, 6:30 a.m.' + +def test_age_in(): + context = { + 'form_var_datefield': time.strptime('2018-07-31', '%Y-%m-%d'), + 'form_var_datefield2': time.strptime('2018-08-31', '%Y-%m-%d'), + 'form_var_datestring': '2018-07-31', + 'today': datetime.date.today(), + 'now': datetime.datetime.now(), + } + for condition_value in ( # hope date is > 2018 + # age_in_days + '"1970-01-01"|age_in_days > 0', + '"2500-01-01"|age_in_days < 0', + 'form_var_datefield|age_in_days > 50', + 'form_var_datefield|age_in_days:form_var_datestring == 0', + 'form_var_datefield|age_in_days:form_var_datefield2 == 31', + 'form_var_datefield2|age_in_days:form_var_datefield == -31', + 'form_var_datefield|age_in_days:form_var_datefield == 0', + 'form_var_datestring|age_in_days:form_var_datefield == 0', + 'form_var_datestring|age_in_days:form_var_datestring == 0', + 'today|add_days:-5|age_in_days == 5', + 'today|add_days:5|age_in_days == -5', + 'today|age_in_days == 0', + # with datetimes + '"1970-01-01 02:03"|age_in_days > 0', + '"2500-01-01 02:03"|age_in_days < 0', + 'now|age_in_days == 0', + 'now|add_hours:-24|age_in_days == 1', + 'now|add_hours:24|age_in_days == -1', + '"2010-11-12 13:14"|age_in_days:"2010-11-12 13:14" == 0', + '"2010-11-12 13:14"|age_in_days:"2010-11-12 12:14" == 0', + '"2010-11-12 13:14"|age_in_days:"2010-11-12 14:14" == 0', + '"2010-11-12 13:14"|age_in_days:"2010-11-13 13:13" == 1', + '"2010-11-12 13:14"|age_in_days:"2010-11-13 13:15" == 1', + + # age_in_hours + 'now|add_hours:-5|age_in_hours == 5', + 'now|add_hours:25|age_in_hours == -24', + 'now|age_in_hours == 0', + '"2010-11-12 13:14"|age_in_hours:"2010-11-12 13:14" == 0', + '"2010-11-12 13:14"|age_in_hours:"2010-11-12 12:14" == -1', + '"2010-11-12 13:14"|age_in_hours:"2010-11-12 14:14" == 1', + '"2010-11-12 13:14"|age_in_hours:"2010-11-13 13:13" == 23', + '"2010-11-12 13:14"|age_in_hours:"2010-11-13 13:15" == 24', + '"1970-01-01 02:03"|age_in_hours > 0', + '"2500-01-01 02:03"|age_in_hours < 0', + # with dates + '"1970-01-01"|age_in_hours > 0', + '"2500-01-01"|age_in_hours < 0', + 'form_var_datefield|age_in_hours > 1200', + 'form_var_datefield|age_in_hours:form_var_datestring == 0', + 'form_var_datefield|age_in_hours:form_var_datefield2 == 744', # 31*24 + 'form_var_datefield2|age_in_hours:form_var_datefield == -744', + 'form_var_datefield|age_in_hours:form_var_datefield == 0', + 'form_var_datestring|age_in_hours:form_var_datefield == 0', + 'form_var_datestring|age_in_hours:form_var_datestring == 0', + 'today|add_days:-1|age_in_hours >= 24', + 'today|add_days:1|age_in_hours <= -0', + 'today|add_days:1|age_in_hours >= -24', + 'today|age_in_hours >= 0', + + # age_in_years + '"1970-01-01"|age_in_years > 0', + '"2500-01-01"|age_in_years < 0', + 'form_var_datefield|age_in_years:"2019-07-31" == 1', + 'form_var_datefield|age_in_years:"2019-09-20" == 1', + 'form_var_datefield|age_in_years:"2020-07-30" == 1', + 'form_var_datefield|age_in_years:"2020-07-31" == 2', + 'form_var_datestring|age_in_years:"2019-07-31" == 1', + 'today|age_in_years == 0', + 'today|add_days:-500|age_in_years == 1', + 'today|add_days:-300|age_in_years == 0', + 'today|add_days:300|age_in_years == -1', + 'now|age_in_years == 0', + 'now|add_days:-500|age_in_years == 1', + 'now|add_days:-300|age_in_years == 0', + 'now|add_days:300|age_in_years == -1', + '"1970-01-01 02:03"|age_in_years > 0', + '"2500-01-01 02:03"|age_in_years < 0', + + # age_in_months + 'form_var_datefield|age_in_months:form_var_datefield2 == 1', + 'form_var_datefield2|age_in_months:form_var_datefield == -1', + 'form_var_datefield|age_in_months:"2019-07-31" == 12', + 'form_var_datefield|age_in_months:"2019-08-20" == 12', + 'form_var_datefield|age_in_months:"2019-09-20" == 13', + 'form_var_datestring|age_in_months:"2019-09-20" == 13', + '"1970-01-01"|age_in_months > 0', + '"2500-01-01"|age_in_months < 0', + '"1970-01-01 02:03"|age_in_months > 0', + '"2500-01-01 02:03"|age_in_months < 0', + + # fail produce empty string + 'foobar|age_in_days == ""', + '"foobar"|age_in_days == ""', + '"1970-01-01"|age_in_days:"foobar" == ""', + 'foobar|age_in_hours == ""', + '"foobar"|age_in_hours == ""', + '"1970-01-01"|age_in_hours:"foobar" == ""', + 'foobar|age_in_years == ""', + '"foobar"|age_in_years == ""', + '"1970-01-01"|age_in_years:"foobar" == ""', + 'foobar|age_in_months == ""', + '"foobar"|age_in_months == ""', + '"1970-01-01"|age_in_months:"foobar" == ""', + ): + tmpl = Template('{%% if %s %%}Good{%% endif %%}' % condition_value) + assert tmpl.render(Context(context)) == 'Good' -- 2.20.1