elias Publicada 20 de febrero de 2023 · 4 min read

Una etiqueta para mostrar rangos de fechas en Django

Una imagen decorativa para esta página.

Implementamos una etiqueta personalizada para mostrar rangos de fechas en las plantillas HTML de Django.

HTML django templates

Una cosa que hacemos muy frecuentemente cuando desarrollamos aplicaciones web con Django es mostrar un rango de fechas al usuario. Podría tratarse del periodo durante el cual está abierto el formulario de registro a un evento, o la duración del propio evento.

A veces utilizamos una template tag propia que optimiza cómo se muestra un rango de fecha y hora para que no quede excesivamente largo. El uso es algo así:

{% load app_name %}
<p>
    Event time:
    {% datetimerange start end %}
</p>

Nuestro objetivo inicial era gestionar estos dos casos:

  • Si start y end están separados por menos de 24 horas, entonces queremos mostrar la fecha solo una vez e incluir el rango entre horas.
  • En otro caso, debemos mostrar ambas fechas y horas.

Por ejemplo, si configuramos start a las 17:00 del 18 de febrero de 2023 y end a las 02:00 del 19 de febrero de 2023, queremos obtener algo como 18/02/2023 17:00 - 02:00. Si end fuera más tarde, por ejemplo las 09:00 del 20 de febrero de 2023, entonces querríamos que se mostrase así: 18/02/2023 17:00 - 20/02/2023 09:00.

Además, nos gustaría gestionar los rangos incompletos en los que uno de start o end no está definido, devolviendo Hasta %(datetime)s o Desde %(datetime)s, respectivamente. Si ambos son None entonces podemos retornar None en nuestra template tag y utilizar el filtro de serie default para asignar un valor por defecto.

Así mismo, podemos utilizar el filtro date, que ya viene con Django, para dar formato a los datetimes. Este filtro recibe una cadena de formato o un alias de la configuración de Django (ver, por ejemplo, SHORT_DATE_FORMAT o TIME_FORMAT).

Implementación del filtro datetimerange

Gran parte del trabajo ya está gestionado por Django de manera que trataremos de aprovechar las utilidades incluidas al máximo. Este es el código que podrías poner en <app_name>/templatetags/<app_name>.py (revisa la guía para crear filtros y tags personalizados para saber más sobre cómo estructurar este código).

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
    )

Últimamente lo utilizamos mucho y esperamos que también te sea útil!

Da el primer paso para hacer realidad tu proyecto.

Get in touch