Dave Plug
Dave Plug

Reputation: 1079

Display and format Django DurationField in template

Im using Django 1.8, and i have one of my field defined as DurationField, but i dont find any way to correctly display it on my template, if i output it like this:

{{runtime}}
i just get 0:00:00.007980

is there any filter or any other way of displaying something more like

2hours 30 min

Upvotes: 15

Views: 9646

Answers (4)

Benbb96
Benbb96

Reputation: 2273

This is the one I use, it rounds minutes and display only the information needed :

@register.filter
def duration(timedelta):
    """
    Format a duration field
    "2h and 30 min" or only "45 min" for example

    :rtype: str
    """
    total_seconds = int(timedelta.total_seconds())
    hours = total_seconds // 3600
    minutes = round((total_seconds % 3600) / 60)
    if minutes == 60:
        hours += 1
        minutes = 0
    if hours and minutes:
        # Display both
        return f'{hours}h and {minutes} min'
    elif hours:
        # Display only hours
        return f'{hours}h'
    # Display only minutes
    return f'{minutes} min'

Upvotes: 1

tklodd
tklodd

Reputation: 1079

In order to make this as readable as possible for crawlers and screen readers, so as to improve your SEO rating, the best practice is to use the html time tag, with the datetime attribute.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time
https://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#valid-duration-string

I have written a template tag to accomplish the formatting of the duration, both in machine and human-readable forms. This displays days, hours, minutes, seconds, and microseconds, using the largest increments possible and hiding any values that are 0. It also handles pluralizing the labels and adding commas and spaces as necessary.

datetime.py (templatetag file)

import datetime

from django import template
register = template.Library()
from django.template.defaultfilters import pluralize

@register.filter
def duration(value, mode=""):

    assert mode in ["machine", "phrase", "clock"]

    remainder = value
    response = ""
    days = 0
    hours = 0
    minutes = 0
    seconds = 0
    microseconds = 0

    if remainder.days > 0:
        days = remainder.days
        remainder -= datetime.timedelta(days=remainder.days)

    if round(remainder.seconds/3600) > 1:
        hours = round(remainder.seconds/3600)
        remainder -= datetime.timedelta(hours=hours)

    if round(remainder.seconds/60) > 1:
        minutes = round(remainder.seconds/60)
        remainder -= datetime.timedelta(minutes=minutes)

    if remainder.seconds > 0:
        seconds = remainder.seconds
        remainder -= datetime.timedelta(seconds=seconds)

    if remainder.microseconds > 0:
        microseconds = remainder.microseconds
        remainder -= datetime.timedelta(microseconds=microseconds)

    if mode == "machine":

        response = "P{days}DT{hours}H{minutes}M{seconds}.{microseconds}S".format(
            days=days,
            hours=hours,
            minutes=minutes,
            seconds=seconds,
            microseconds=str(microseconds).zfill(6),
        )

    elif mode == "phrase":
        
        response = []
        if days:
            response.append(
                "{days} day{plural_suffix}".format(
                    days=days, 
                    plural_suffix=pluralize(days),
                )
            )
        if hours:
            response.append(
                "{hours} hour{plural_suffix}".format(
                    hours=hours,
                    plural_suffix=pluralize(hours),
                )
            )
        if minutes:
            response.append(
                "{minutes} minute{plural_suffix}".format(
                    minutes=minutes,
                    plural_suffix=pluralize(minutes),
                )
            )
        if seconds:
            response.append(
                "{seconds} second{plural_suffix}".format(
                    seconds=seconds,
                    plural_suffix=pluralize(seconds),
                )
            )
        if microseconds:
            response.append(
                "{microseconds} microsecond{plural_suffix}".format(
                    microseconds=microseconds,
                    plural_suffix=pluralize(microseconds),
                )
            )

        response = ", ".join(response)

    elif mode == "clock":

        response = []
        if days:
            response.append(
                "{days} day{plural_suffix}".format(
                    days=days, 
                    plural_suffix=pluralize(days),
                )
            )
        if hours or minutes or seconds or microseconds:
            time_string = "{hours}:{minutes}".format(
                hours = str(hours).zfill(2),
                minutes = str(minutes).zfill(2),
            )
            if seconds or microseconds:
                time_string += ":{seconds}".format(
                    seconds = str(seconds).zfill(2),
                )                   
                if microseconds:
                    time_string += ".{microseconds}".format(
                        microseconds = str(microseconds).zfill(6),
                    )

            response.append(time_string)

        response = ", ".join(response)

    return response

template.html (template file)

{% load datetime %}
<!-- phrase format -->
<time datetime="{{ event.duration|duration:'machine' }}">
    {{ event.duration|duration:'phrase' }}
</time>

<!-- clock format -->
<time datetime="{{ event.duration|duration:'machine' }}">
    {{ event.duration|duration:'clock' }}
</time>

Example Output

<!-- phrase format -->
<time datetime="P2DT1H0M0.000000S">2 days, 1 hour</time>

<!-- clock format -->
<time datetime="P2DT1H0M0.000000S">2 days, 01:00</time>

Upvotes: 0

aumo
aumo

Reputation: 5554

No, I don't think there is any built in filter to format a timedelta, it should be fairly easy to write one yourself though.

Here is a basic example:

from django import template


register = template.Library()


@register.filter
def duration(td):
    total_seconds = int(td.total_seconds())
    hours = total_seconds // 3600
    minutes = (total_seconds % 3600) // 60

    return '{} hours {} min'.format(hours, minutes)

    

Upvotes: 19

Gustavo Gon&#231;alves
Gustavo Gon&#231;alves

Reputation: 493

Contribution for Aumo answer:

from django import template


register = template.Library()


@register.filter
def duration(td):

    total_seconds = int(td.total_seconds())

    days = total_seconds // 86400
    remaining_hours = total_seconds % 86400
    remaining_minutes = remaining_hours % 3600
    hours = remaining_hours // 3600
    minutes = remaining_minutes // 60
    seconds = remaining_minutes % 60

    days_str = f'{days}d ' if days else ''
    hours_str = f'{hours}h ' if hours else ''
    minutes_str = f'{minutes}m ' if minutes else ''
    seconds_str = f'{seconds}s' if seconds and not hours_str else ''

    return f'{days_str}{hours_str}{minutes_str}{seconds_str}'

Upvotes: 4

Related Questions