Projet

Général

Profil

0001-templatetags-add-date-filters-36943.patch

Valentin Deniaud, 27 janvier 2020 13:34

Télécharger (24 ko)

Voir les différences:

Subject: [PATCH] templatetags: add date filters (#36943)

 combo/public/templatetags/combo.py | 154 ++++++++++++++++-
 combo/utils/date.py                |  59 +++++++
 tests/test_public_templatetags.py  | 264 ++++++++++++++++++++++++++++-
 3 files changed, 471 insertions(+), 6 deletions(-)
 create mode 100644 combo/utils/date.py
combo/public/templatetags/combo.py
26 26
from django.core.exceptions import PermissionDenied
27 27
from django.template import VariableDoesNotExist
28 28
from django.template.base import TOKEN_BLOCK, TOKEN_VAR, TOKEN_COMMENT
29
from django.template import defaultfilters
29 30
from django.template.defaultfilters import stringfilter
30
from django.utils import dateparse
31
from django.utils import dateparse, six
31 32
from django.utils.encoding import force_text
32 33

  
33 34
from combo.data.models import Page, Placeholder
34 35
from combo.public.menu import get_menu_context
35 36
from combo.utils import NothingInCacheException, flatten_context
37
from combo.utils.date import make_date, make_datetime
36 38
from combo.apps.dashboard.models import DashboardCell, Tile
37 39

  
38 40
register = template.Library()
......
176 178

  
177 179
@register.filter
178 180
def parse_date(date_string):
181
    try:
182
        return make_date(date_string)
183
    except ValueError:
184
        pass
185
    # fallback to Django function
179 186
    try:
180 187
        return dateparse.parse_date(date_string)
181 188
    except (ValueError, TypeError):
182 189
        return None
183 190

  
191
@register.filter(expects_localtime=True, is_safe=False)
192
def date(value, arg=None):
193
    if arg is None:
194
        return parse_date(value) or ''
195
    if not isinstance(value, (datetime.datetime, datetime.date, datetime.time)):
196
        value = parse_datetime(value) or parse_date(value)
197
    return defaultfilters.date(value, arg=arg)
198

  
184 199
@register.filter
185
def parse_datetime(date_string):
186
    if isinstance(date_string, time.struct_time):
187
        return datetime.datetime.fromtimestamp(time.mktime(date_string))
200
def parse_datetime(datetime_string):
188 201
    try:
189
        return dateparse.parse_datetime(date_string)
202
        return make_datetime(datetime_string)
203
    except ValueError:
204
        pass
205
    # fallback to Django function
206
    try:
207
        return dateparse.parse_datetime(datetime_string)
190 208
    except (ValueError, TypeError):
191 209
        return None
192 210

  
211
@register.filter(name='datetime', expects_localtime=True, is_safe=False)
212
def datetime_(value, arg=None):
213
    if arg is None:
214
        return parse_datetime(value) or ''
215
    if not isinstance(value, (datetime.datetime, datetime.date, datetime.time)):
216
        value = parse_datetime(value)
217
    return defaultfilters.date(value, arg=arg)
218

  
193 219
@register.filter
194 220
def parse_time(time_string):
221
    # if input is a datetime, extract its time
222
    try:
223
        dt = parse_datetime(time_string)
224
        if dt:
225
            return dt.time()
226
    except (ValueError, TypeError):
227
        pass
228
    # fallback to Django function
195 229
    try:
196 230
        return dateparse.parse_time(time_string)
197 231
    except (ValueError, TypeError):
198 232
        return None
199 233

  
234
@register.filter(expects_localtime=True, is_safe=False)
235
def time(value, arg=None):
236
    if arg is None:
237
        parsed = parse_time(value)
238
        return parsed if parsed is not None else ''  # because bool(midnight) == False
239
    if not isinstance(value, (datetime.datetime, datetime.date, datetime.time)):
240
        value = parse_time(value)
241
    return defaultfilters.date(value, arg=arg)
242

  
200 243
@register.filter
201 244
def shown_because_admin(cell, request):
202 245
    if not (request.user and request.user.is_superuser):
......
269 312
@register.filter
270 313
def startswith(string, substring):
271 314
    return string and force_text(string).startswith(force_text(substring))
315

  
316
def parse_float(value):
317
    if isinstance(value, six.string_types):
318
        # replace , by . for French users comfort
319
        value = value.replace(',', '.')
320
    try:
321
        return float(value)
322
    except (ValueError, TypeError):
323
        return ''
324

  
325
def get_as_datetime(s):
326
    result = parse_datetime(s)
327
    if not result:
328
        result = parse_date(s)
329
        if result:
330
            result = datetime.datetime(year=result.year, month=result.month, day=result.day)
331
    return result
332

  
333
@register.filter(expects_localtime=True, is_safe=False)
334
def add_days(value, arg):
335
    value = parse_date(value)  # consider only date, not hours
336
    if not value:
337
        return ''
338
    arg = parse_float(arg)
339
    if not arg:
340
        return value
341
    result = value + datetime.timedelta(days=float(arg))
342
    return result
343

  
344
@register.filter(expects_localtime=True, is_safe=False)
345
def add_hours(value, arg):
346
    value = parse_datetime(value)
347
    if not value:
348
        return ''
349
    arg = parse_float(arg)
350
    if not arg:
351
        return value
352
    return value + datetime.timedelta(hours=float(arg))
353

  
354
@register.filter(expects_localtime=True, is_safe=False)
355
def age_in_days(value, today=None):
356
    value = parse_date(value)
357
    if not value:
358
        return ''
359
    if today is not None:
360
        today = parse_date(today)
361
        if not today:
362
            return ''
363
    else:
364
        today = datetime.date.today()
365
    return (today - value).days
366

  
367
@register.filter(expects_localtime=True, is_safe=False)
368
def age_in_hours(value, now=None):
369
    # consider value and now as datetimes (and not dates)
370
    value = parse_datetime(value)
371
    if not value:
372
        return ''
373
    if now is not None:
374
        now = parse_datetime(now)
375
        if not now:
376
            return ''
377
    else:
378
        now = datetime.datetime.now()
379
    return int((now - value).total_seconds() / 3600)
380

  
381
def age_in_years_and_months(born, today=None):
382
    '''Compute age since today as the number of years and months elapsed'''
383
    born = make_date(born)
384
    if not born:
385
        return ''
386
    if today is not None:
387
        today = make_date(today)
388
        if not today:
389
            return ''
390
    else:
391
        today = datetime.date.today()
392
    before = (today.month, today.day) < (born.month, born.day)
393
    years = today.year - born.year
394
    months = today.month - born.month
395
    if before:
396
        years -= 1
397
        months += 12
398
    if today.day < born.day:
399
        months -= 1
400
    return years, months
401

  
402
@register.filter(expects_localtime=True, is_safe=False)
403
def age_in_years(value, today=None):
404
    try:
405
        return age_in_years_and_months(value, today)[0]
406
    except ValueError:
407
        return ''
408

  
409
@register.filter(expects_localtime=True, is_safe=False)
410
def age_in_months(value, today=None):
411
    try:
412
        years, months = age_in_years_and_months(value, today)
413
    except ValueError:
414
        return ''
415
    return years * 12 + months
combo/utils/date.py
1
import datetime
2
import time
3

  
4
DATE_FORMATS = {
5
    'C': ['%Y-%m-%d', '%y-%m-%d'],
6
    'fr': ['%d/%m/%Y', '%d/%m/%y'],
7
}
8

  
9
DATETIME_FORMATS = {
10
    'C': ['%Y-%m-%d %H:%M', '%Y-%m-%d %H:%M:%S', '%Y-%m-%dT%H:%M:%S', '%Y-%m-%dT%H:%M:%SZ',
11
          '%y-%m-%d %H:%M', '%y-%m-%d %H:%M:%S'],
12
    'fr': ['%d/%m/%Y %H:%M', '%d/%m/%Y %H:%M:%S', '%d/%m/%Y %Hh%M',
13
           '%d/%m/%y %H:%M', '%d/%m/%y %H:%M:%S', '%d/%m/%y %Hh%M'],
14
}
15

  
16

  
17
def get_as_datetime(s):
18
    formats = []
19
    for value in DATETIME_FORMATS.values():
20
        formats.extend(value)
21
    for value in DATE_FORMATS.values():
22
        formats.extend(value)
23
    for format_string in formats:
24
        try:
25
            return datetime.datetime.strptime(s, format_string)
26
        except ValueError:
27
            pass
28
    raise ValueError()
29

  
30

  
31
def make_date(date_var):
32
    '''Extract a date from a datetime, a date, a struct_time or a string'''
33
    if isinstance(date_var, datetime.datetime):
34
        return date_var.date()
35
    if isinstance(date_var, datetime.date):
36
        return date_var
37
    if isinstance(date_var, time.struct_time) or (
38
            isinstance(date_var, tuple) and len(date_var) == 9):
39
        return datetime.date(*date_var[:3])
40
    try:
41
        return get_as_datetime(str(date_var)).date()
42
    except ValueError:
43
        raise ValueError('invalid date value: %s' % date_var)
44

  
45

  
46
def make_datetime(datetime_var):
47
    '''Extract a date from a datetime, a date, a struct_time or a string'''
48
    if isinstance(datetime_var, datetime.datetime):
49
        return datetime_var
50
    if isinstance(datetime_var, datetime.date):
51
        return datetime.datetime(year=datetime_var.year, month=datetime_var.month,
52
                                 day=datetime_var.day)
53
    if isinstance(datetime_var, time.struct_time) or (
54
            isinstance(datetime_var, tuple) and len(datetime_var) == 9):
55
        return datetime.datetime(*datetime_var[:6])
56
    try:
57
        return get_as_datetime(str(datetime_var))
58
    except ValueError:
59
        raise ValueError('invalid datetime value: %s' % datetime_var)
tests/test_public_templatetags.py
1
import datetime
1 2
import os
2 3
import shutil
3 4
import time
......
48 49
    t = Template('{{ someday|parse_date|date:"Y m d" }}')
49 50
    expected = '2015 04 15'
50 51
    assert t.render(Context({'someday': '2015-04-15'})) == expected
51
    assert t.render(Context({'someday': '2015-04-15T13:11:12Z'})) == ''
52
    assert t.render(Context({'someday': '2015-04-15T13:11:12Z'})) == expected
52 53
    assert t.render(Context({'someday': 'foobar'})) == ''
53 54
    assert t.render(Context({'someday': ''})) == ''
54 55
    assert t.render(Context({'someday': None})) == ''
......
201 202
    assert t.render(context) == ''
202 203
    context = Context({'foo': 'bar'})
203 204
    assert t.render(context) == 'ok'
205

  
206
def test_datetime_templatetags():
207
    tmpl = Template('{{ plop|datetime }}')
208
    assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == 'Dec. 21, 2017, 10:32 a.m.'
209
    assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == 'Dec. 21, 2017, 10:32 a.m.'
210
    assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 21, 2017, midnight'
211
    assert tmpl.render(Context({'plop': '21/12/2017'})) == 'Dec. 21, 2017, midnight'
212
    assert tmpl.render(Context({'plop': '10h32'})) == ''
213
    assert tmpl.render(Context({'plop': 'x'})) == ''
214
    assert tmpl.render(Context({'plop': None})) == ''
215
    assert tmpl.render(Context({'plop': 3})) == ''
216
    assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == ''
217
    assert tmpl.render(Context()) == ''
218

  
219
    tmpl = Template('{{ plop|datetime:"d i" }}')
220
    assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '21 32'
221
    assert tmpl.render(Context({'plop': '2017-12-21 10:32:42'})) == '21 32'
222
    assert tmpl.render(Context({'plop': '21/12/2017 10:32'})) == '21 32'
223
    assert tmpl.render(Context({'plop': '21/12/2017 10:32:42'})) == '21 32'
224
    assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == '21 32'
225
    assert tmpl.render(Context({'plop': '21/12/2017'})) == '21 00'
226
    assert tmpl.render(Context({'plop': '10h32'})) == ''
227
    assert tmpl.render(Context({'plop': 'x'})) == ''
228
    assert tmpl.render(Context({'plop': None})) == ''
229
    assert tmpl.render(Context({'plop': 3})) == ''
230
    assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == ''
231
    assert tmpl.render(Context()) == ''
232

  
233
    tmpl = Template('{% if d1|datetime > d2|datetime %}d1>d2{% else %}d1<=d2{% endif %}')
234
    assert tmpl.render(Context({'d1': '2017-12-22', 'd2': '2017-12-21'})) == 'd1>d2'
235
    assert tmpl.render(Context({'d1': '2017-12-21', 'd2': '2017-12-21'})) == 'd1<=d2'
236
    assert tmpl.render(Context({'d1': '2017-12-21 10:30', 'd2': '2017-12-21 09:00'})) == 'd1>d2'
237
    assert tmpl.render(Context({'d1': '2017-12-21 10:30', 'd2': '2017-12-21'})) == 'd1>d2'
238
    assert tmpl.render(Context({'d1': '2017-12-22'})) == 'd1<=d2'
239
    assert tmpl.render(Context({'d2': '2017-12-22'})) == 'd1<=d2'
240

  
241
    tmpl = Template('{{ plop|date }}')
242
    assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 21, 2017'
243
    assert tmpl.render(Context({'plop': '21/12/2017'})) == 'Dec. 21, 2017'
244
    assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == 'Dec. 21, 2017'
245
    assert tmpl.render(Context({'plop': '21/12/2017 10:32'})) == 'Dec. 21, 2017'
246
    assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == 'Dec. 21, 2017'
247
    assert tmpl.render(Context({'plop': '21/12/2017 10:32:42'})) == 'Dec. 21, 2017'
248
    assert tmpl.render(Context({'plop': '10:32'})) == ''
249
    assert tmpl.render(Context({'plop': 'x'})) == ''
250
    assert tmpl.render(Context({'plop': None})) == ''
251
    assert tmpl.render(Context({'plop': 3})) == ''
252
    assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == ''
253
    assert tmpl.render(Context()) == ''
254

  
255
    tmpl = Template('{{ plop|date:"d" }}')
256
    assert tmpl.render(Context({'plop': '2017-12-21'})) == '21'
257
    assert tmpl.render(Context({'plop': '21/12/2017'})) == '21'
258
    assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '21'
259
    assert tmpl.render(Context({'plop': '10:32'})) == ''
260
    assert tmpl.render(Context({'plop': 'x'})) == ''
261
    assert tmpl.render(Context({'plop': None})) == ''
262
    assert tmpl.render(Context({'plop': 3})) == ''
263
    assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == ''
264
    assert tmpl.render(Context()) == ''
265

  
266
    tmpl = Template('{% if d1|date > d2|date %}d1>d2{% else %}d1<=d2{% endif %}')
267
    assert tmpl.render(Context({'d1': '2017-12-22', 'd2': '2017-12-21'})) == 'd1>d2'
268
    assert tmpl.render(Context({'d1': '2017-12-21', 'd2': '2017-12-21'})) == 'd1<=d2'
269
    assert tmpl.render(Context({'d1': '2017-12-22'})) == 'd1<=d2'
270
    assert tmpl.render(Context({'d2': '2017-12-22'})) == 'd1<=d2'
271

  
272
    tmpl = Template('{{ plop|time }}')
273
    assert tmpl.render(Context({'plop': '10:32'})) == '10:32 a.m.'
274
    assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '10:32 a.m.'
275
    assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == '10:32 a.m.'
276
    assert tmpl.render(Context({'plop': '21/12/2017'})) == 'midnight'
277
    assert tmpl.render(Context({'plop': 'x'})) == ''
278
    assert tmpl.render(Context({'plop': None})) == ''
279
    assert tmpl.render(Context({'plop': 3})) == ''
280
    assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == ''
281
    assert tmpl.render(Context()) == ''
282

  
283
    tmpl = Template('{{ plop|time:"H i" }}')
284
    assert tmpl.render(Context({'plop': '10:32'})) == '10 32'
285
    assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '10 32'
286
    assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == '10 32'
287
    assert tmpl.render(Context({'plop': '21/12/2017'})) == '00 00'
288
    assert tmpl.render(Context({'plop': 'x'})) == ''
289
    assert tmpl.render(Context({'plop': None})) == ''
290
    assert tmpl.render(Context({'plop': 3})) == ''
291
    assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == ''
292
    assert tmpl.render(Context()) == ''
293

  
294
    # old fashion, with parse_*
295
    tmpl = Template('{{ plop|parse_datetime|date:"d i" }}')
296
    assert tmpl.render(Context({'plop': '2017-12-21 10:32'})) == '21 32'
297
    assert tmpl.render(Context({'plop': '2017-12-21 10:32:42'})) == '21 32'
298
    assert tmpl.render(Context({'plop': '21/12/2017 10:32'})) == '21 32'
299
    assert tmpl.render(Context({'plop': '21/12/2017 10:32:42'})) == '21 32'
300
    assert tmpl.render(Context({'plop': '21/12/2017 10h32'})) == '21 32'
301
    assert tmpl.render(Context({'plop': 'x'})) == ''
302
    assert tmpl.render(Context({'plop': None})) == ''
303
    assert tmpl.render(Context({'plop': 3})) == ''
304
    assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == ''
305
    assert tmpl.render(Context()) == ''
306

  
307
    tmpl = Template('{{ plop|parse_date|date:"d" }}')
308
    assert tmpl.render(Context({'plop': '2017-12-21'})) == '21'
309
    assert tmpl.render(Context({'plop': '21/12/2017'})) == '21'
310
    assert tmpl.render(Context({'plop': 'x'})) == ''
311
    assert tmpl.render(Context({'plop': None})) == ''
312
    assert tmpl.render(Context({'plop': 3})) == ''
313
    assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == ''
314
    assert tmpl.render(Context()) == ''
315

  
316
    tmpl = Template('{{ plop|parse_time|date:"H i" }}')
317
    assert tmpl.render(Context({'plop': '10:32'})) == '10 32'
318
    assert tmpl.render(Context({'plop': 'x'})) == ''
319
    assert tmpl.render(Context({'plop': None})) == ''
320
    assert tmpl.render(Context({'plop': 3})) == ''
321
    assert tmpl.render(Context({'plop': {'foo': 'bar'}})) == ''
322
    assert tmpl.render(Context()) == ''
323

  
324

  
325
def test_date_maths():
326
    tmpl = Template('{{ plop|add_days:4 }}')
327
    assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 25, 2017'
328
    assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 25, 2017'
329
    tmpl = Template('{{ plop|add_days:"-1" }}')
330
    assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 20, 2017'
331
    assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 20, 2017'
332
    tmpl = Template('{{ plop|add_days:1.5 }}')
333
    assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 22, 2017'
334
    assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017'
335
    tmpl = Template('{{ plop|add_days:"1.5" }}')
336
    assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 22, 2017'
337
    assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017'
338

  
339
    tmpl = Template('{{ plop|add_hours:24 }}')
340
    assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 22, 2017, midnight'
341
    assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017, 6 p.m.'
342
    tmpl = Template('{{ plop|add_hours:"12.5" }}')
343
    assert tmpl.render(Context({'plop': '2017-12-21'})) == 'Dec. 21, 2017, 12:30 p.m.'
344
    assert tmpl.render(Context({'plop': '2017-12-21 18:00'})) == 'Dec. 22, 2017, 6:30 a.m.'
345

  
346

  
347
def test_age_in():
348
    context = {
349
        'form_var_datefield': time.strptime('2018-07-31', '%Y-%m-%d'),
350
        'form_var_datefield2': time.strptime('2018-08-31', '%Y-%m-%d'),
351
        'form_var_datestring': '2018-07-31',
352
        'today': datetime.date.today(),
353
        'now': datetime.datetime.now(),
354
    }
355
    for condition_value in (  # hope date is > 2018
356
            # age_in_days
357
            '"1970-01-01"|age_in_days > 0',
358
            '"01/01/1970"|age_in_days > 0',
359
            '"2500-01-01"|age_in_days < 0',
360
            '"01/01/2500"|age_in_days < 0',
361
            'form_var_datefield|age_in_days > 50',
362
            'form_var_datefield|age_in_days:form_var_datestring == 0',
363
            'form_var_datefield|age_in_days:form_var_datefield2 == 31',
364
            'form_var_datefield2|age_in_days:form_var_datefield == -31',
365
            'form_var_datefield|age_in_days:form_var_datefield == 0',
366
            'form_var_datestring|age_in_days:form_var_datefield == 0',
367
            'form_var_datestring|age_in_days:form_var_datestring == 0',
368
            'today|add_days:-5|age_in_days == 5',
369
            'today|add_days:5|age_in_days == -5',
370
            'today|age_in_days == 0',
371
            # with datetimes
372
            '"1970-01-01 02:03"|age_in_days > 0',
373
            '"01/01/1970 02h03"|age_in_days > 0',
374
            '"2500-01-01 02:03"|age_in_days < 0',
375
            '"01/01/2500 02h03"|age_in_days < 0',
376
            'now|age_in_days == 0',
377
            'now|add_hours:-24|age_in_days == 1',
378
            'now|add_hours:24|age_in_days == -1',
379
            '"2010-11-12 13:14"|age_in_days:"2010-11-12 13:14" == 0',
380
            '"2010-11-12 13:14"|age_in_days:"2010-11-12 12:14" == 0',
381
            '"2010-11-12 13:14"|age_in_days:"2010-11-12 14:14" == 0',
382
            '"2010-11-12 13:14"|age_in_days:"2010-11-13 13:13" == 1',
383
            '"2010-11-12 13:14"|age_in_days:"2010-11-13 13:15" == 1',
384

  
385
            # age_in_hours
386
            'now|add_hours:-5|age_in_hours == 5',
387
            'now|add_hours:25|age_in_hours == -24',
388
            'now|age_in_hours == 0',
389
            '"2010-11-12 13:14"|age_in_hours:"2010-11-12 13:14" == 0',
390
            '"2010-11-12 13:14"|age_in_hours:"2010-11-12 12:14" == -1',
391
            '"2010-11-12 13:14"|age_in_hours:"2010-11-12 14:14" == 1',
392
            '"2010-11-12 13:14"|age_in_hours:"2010-11-13 13:13" == 23',
393
            '"2010-11-12 13:14"|age_in_hours:"2010-11-13 13:15" == 24',
394
            '"1970-01-01 02:03"|age_in_hours > 0',
395
            '"01/01/1970 02h03"|age_in_hours > 0',
396
            '"2500-01-01 02:03"|age_in_hours < 0',
397
            '"01/01/2500 02h03"|age_in_hours < 0',
398
            # with dates
399
            '"1970-01-01"|age_in_hours > 0',
400
            '"01/01/1970"|age_in_hours > 0',
401
            '"2500-01-01"|age_in_hours < 0',
402
            '"01/01/2500"|age_in_hours < 0',
403
            'form_var_datefield|age_in_hours > 1200',
404
            'form_var_datefield|age_in_hours:form_var_datestring == 0',
405
            'form_var_datefield|age_in_hours:form_var_datefield2 == 744',  # 31*24
406
            'form_var_datefield2|age_in_hours:form_var_datefield == -744',
407
            'form_var_datefield|age_in_hours:form_var_datefield == 0',
408
            'form_var_datestring|age_in_hours:form_var_datefield == 0',
409
            'form_var_datestring|age_in_hours:form_var_datestring == 0',
410
            'today|add_days:-1|age_in_hours >= 24',
411
            'today|add_days:1|age_in_hours <= -0',
412
            'today|add_days:1|age_in_hours >= -24',
413
            'today|age_in_hours >= 0',
414

  
415
            # age_in_years
416
            '"1970-01-01"|age_in_years > 0',
417
            '"01/01/1970"|age_in_years > 0',
418
            '"2500-01-01"|age_in_years < 0',
419
            '"01/01/2500"|age_in_years < 0',
420
            'form_var_datefield|age_in_years:"2019-07-31" == 1',
421
            'form_var_datefield|age_in_years:"2019-09-20" == 1',
422
            'form_var_datefield|age_in_years:"2020-07-30" == 1',
423
            'form_var_datefield|age_in_years:"2020-07-31" == 2',
424
            'form_var_datestring|age_in_years:"2019-07-31" == 1',
425
            'today|age_in_years == 0',
426
            'today|add_days:-500|age_in_years == 1',
427
            'today|add_days:-300|age_in_years == 0',
428
            'today|add_days:300|age_in_years == -1',
429
            'now|age_in_years == 0',
430
            'now|add_days:-500|age_in_years == 1',
431
            'now|add_days:-300|age_in_years == 0',
432
            'now|add_days:300|age_in_years == -1',
433
            '"1970-01-01 02:03"|age_in_years > 0',
434
            '"2500-01-01 02:03"|age_in_years < 0',
435

  
436
            # age_in_months
437
            'form_var_datefield|age_in_months:form_var_datefield2 == 1',
438
            'form_var_datefield2|age_in_months:form_var_datefield == -1',
439
            'form_var_datefield|age_in_months:"2019-07-31" == 12',
440
            'form_var_datefield|age_in_months:"2019-08-20" == 12',
441
            'form_var_datefield|age_in_months:"2019-09-20" == 13',
442
            'form_var_datestring|age_in_months:"2019-09-20" == 13',
443
            '"1970-01-01"|age_in_months > 0',
444
            '"01/01/1970"|age_in_months > 0',
445
            '"2500-01-01"|age_in_months < 0',
446
            '"01/01/2500"|age_in_months < 0',
447
            '"1970-01-01 02:03"|age_in_months > 0',
448
            '"2500-01-01 02:03"|age_in_months < 0',
449

  
450
            # fail produce empty string
451
            'foobar|age_in_days == ""',
452
            '"foobar"|age_in_days == ""',
453
            '"1970-01-01"|age_in_days:"foobar" == ""',
454
            'foobar|age_in_hours == ""',
455
            '"foobar"|age_in_hours == ""',
456
            '"1970-01-01"|age_in_hours:"foobar" == ""',
457
            'foobar|age_in_years == ""',
458
            '"foobar"|age_in_years == ""',
459
            '"1970-01-01"|age_in_years:"foobar" == ""',
460
            'foobar|age_in_months == ""',
461
            '"foobar"|age_in_months == ""',
462
            '"1970-01-01"|age_in_months:"foobar" == ""',
463
            ):
464
        tmpl = Template('{%% if %s %%}Good{%% endif %%}' % condition_value)
465
        assert tmpl.render(Context(context)) == 'Good'
204
-