A Django template tag for displaying datetime ranges
A simple implementation of a custom Django template tag to render date ranges in your HTML templates.
HTML django templates
Something that comes up a lot when building web applications with Django is displaying a date range to the user. It might be the period during which a registration form is open or the duration of an event.
We sometimes use a handy custom template tag which optimizes how a date time range is displayed so that it doesn't look too verbose. Using it looks something like this:
{% load app_name %}
<p>
Event time:
{% datetimerange start end %}
</p>
Our goal was to handle these two cases:
- If
start
andend
are less than 24 hours apart, we want to display the date only once and communicate the range between the times. - Otherwise we need to display both dates and times.
For instance, if we set start
to 17:00 February 18, 2023 and end to 02:00 February 19, 2023 we want to get something like this: 18/02/2023 17:00 - 02:00
. If end
was later, like 09:00 February 20, 2023, we'd like to get something like 18/02/2023 17:00 - 20/02/2023 09:00
.
Additionally, we can also handle incomplete ranges where either start
or end
are None
by writing Until %(datetime)s
or From %(datetime)s
, respectively. If both date times are None
we can just return None
in our template tag and use the builtin default
filter to provide a default value as needed.
We can use the builtin date
filter to handle datetime, date and time formatting. This filter takes either a format string or an alias from the Django configuration (see e.g. SHORT_DATE_FORMAT
or TIME_FORMAT
).
Implementing the datetimerange
filter
There's a lot of work that Django has already done for us so we try to build on existing utilities as much as possible. Here's the code that you can put in <app_name>/templatetags/<app_name>.py
(see the guide to creating custom template tags and filters to know more about how to structure this code).
from django import template
from django.template.defaultfilters import date as date_filter
from django.utils import timezone
from django.utils.html import format_html
from django.utils.translation import gettext as _
register = template.Library()
@register.simple_tag()
def datetimerangeformat(
start: timezone.datetime,
end: timezone.datetime,
datetime_format: str = 'SHORT_DATETIME_FORMAT',
date_format: str = 'SHORT_DATE_FORMAT',
time_format: str = 'TIME_FORMAT'
):
'''
Displays the range between the two given datetimes.
- If either `start` or `end` is `None`, then it displays *Until {end}* or
*From {start}*.
- If neither is `None` and they are less than 24 hours apart, it displays
*{start.date} {start.time} - {end.time}.*
- If both are `None` it returns `None`. Use the builtin `default` filter to
provide a default value if necessary.
Datetimes are localized to the current timezone. Datetimes, dates and times
are formatted using the builtin `date` filter which is passed each of the
keyword arguments to this tag appropriately.
Translation of the *From* and *Until* words is included.
'''
tz = timezone.get_current_timezone()
if start is not None:
start = start.astimezone(tz)
if end is not None:
end = end.astimezone(tz)
if start is None and end is None:
return None
if end is None:
return _('From %(datetime)s') % {
'datetime': date_filter(start, datetime_format)
}
if start is None:
return _('Until %(datetime)s') % {
'datetime': date_filter(end, datetime_format)
}
if end - start < timezone.timedelta(hours=24):
inside = '%s %s - %s' % (
date_filter(start, date_format),
date_filter(start, time_format),
date_filter(end, time_format)
)
else:
inside = '%s - %s' % (
date_filter(start, datetime_format),
date_filter(end, datetime_format)
)
return format_html(
'<span title="{} - {}">{}</span>',
date_filter(start, datetime_format),
date_filter(end, datetime_format),
inside
)
We've found it pretty useful lately and hope you do too!