User-friendly time format in Python?

Viewed 38533

Python: I need to show file modification times in the "1 day ago", "two hours ago", format.

Is there something ready to do that? It should be in English.

15 Answers

If you happen to be using Django, then new in version 1.4 is the naturaltime template filter.

To use it, first add 'django.contrib.humanize' to your INSTALLED_APPS setting in settings.py, and {% load humanize %} into the template you're using the filter in.

Then, in your template, if you have a datetime variable my_date, you can print its distance from the present by using {{ my_date|naturaltime }}, which will be rendered as something like 4 minutes ago.

Other new things in Django 1.4.

Documentation for naturaltime and other filters in the django.contrib.humanize set.

You can also do that with arrow package

From github page:

>>> import arrow
>>> utc = arrow.utcnow()
>>> utc = utc.shift(hours=-1)
>>> utc.humanize()
'an hour ago'
DAY_INCREMENTS = [
    [365, "year"],
    [30, "month"],
    [7, "week"],
    [1, "day"],
]

SECOND_INCREMENTS = [
    [3600, "hour"],
    [60, "minute"],
    [1, "second"],
]


def time_ago(dt):
    diff = datetime.now() - dt  # use timezone.now() or equivalent if `dt` is timezone aware
    if diff.days < 0:
        return "in the future?!?"
    for increment, label in DAY_INCREMENTS:
        if diff.days >= increment:
            increment_diff = int(diff.days / increment)
            return str(increment_diff) + " " + label + plural(increment_diff) + " ago"
    for increment, label in SECOND_INCREMENTS:
        if diff.seconds >= increment:
            increment_diff = int(diff.seconds / increment)
            return str(increment_diff) + " " + label + plural(increment_diff) + " ago"
    return "just now"


def plural(num):
    if num != 1:
        return "s"
    return ""

Here is an updated answer based on Jed Smith's implementation that properly hands both offset-naive and offset-aware datetimes. You can also give a default timezones. Python 3.5+.

import datetime

def pretty_date(time=None, default_timezone=datetime.timezone.utc):
    """
    Get a datetime object or a int() Epoch timestamp and return a
    pretty string like 'an hour ago', 'Yesterday', '3 months ago',
    'just now', etc
    """

    # Assumes all timezone naive dates are UTC
    if time.tzinfo is None or time.tzinfo.utcoffset(time) is None:
        if default_timezone:
            time = time.replace(tzinfo=default_timezone)

    now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)

    if type(time) is int:
        diff = now - datetime.fromtimestamp(time)
    elif isinstance(time, datetime.datetime):
        diff = now - time
    elif not time:
        diff = now - now
    second_diff = diff.seconds
    day_diff = diff.days

    if day_diff < 0:
        return ''

    if day_diff == 0:
        if second_diff < 10:
            return "just now"
        if second_diff < 60:
            return str(second_diff) + " seconds ago"
        if second_diff < 120:
            return "a minute ago"
        if second_diff < 3600:
            return str(second_diff / 60) + " minutes ago"
        if second_diff < 7200:
            return "an hour ago"
        if second_diff < 86400:
            return str(second_diff / 3600) + " hours ago"
    if day_diff == 1:
        return "Yesterday"
    if day_diff < 7:
        return str(day_diff) + " days ago"
    if day_diff < 31:
        return str(day_diff / 7) + " weeks ago"
    if day_diff < 365:
        return str(day_diff / 30) + " months ago"
    return str(day_diff / 365) + " years ago"

I've been dragging and tweaking this code from programming language to programming language for so long, I don't remember where I originally got it from. It served me well in PHP, Java, and TypeScript, and now it's time for Python.

It handles past and future dates, as well as edge cases.

def unix_time() -> int:
    return int(time.time())


def pretty_time(t: int, absolute=False) -> str:
    if not type(t) is int:
        return "N/A"
    if t == 0:
        return "Never"

    now = unix_time()
    if t == now:
        return "Now"

    periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"]
    lengths = [60, 60, 24, 7, 4.35, 12, 10]

    diff = now - t

    if absolute:
        suffix = ""
    else:
        if diff >= 0:
            suffix = "ago"
        else:
            diff *= -1
            suffix = "remaining"

    i = 0
    while diff >= lengths[i] and i < len(lengths) - 1:
        diff /= lengths[i]
        i += 1

    diff = round(diff)
    if diff > 1:
        periods[i] += "s"

    return "{0} {1} {2}".format(diff, periods[i], suffix)
def time_ago(self):
        start_time = self.date # The start date
        now_time = datetime.now()

        difference = int((now_time - start_time).total_seconds())

        second = [1, 'seconds']
        minute = [60, 'minutes']
        hour = [60 * minute[0], 'hours']
        day = [24 * hour[0], 'days']
        week = [7 * day[0], 'weeks']
        month = [4 * week[0], 'months']
        year = [12 * month[0], 'years']

        times = [year, month, week, day, hour, minute, second]
        for time in times:
            if difference >= time[0]:
                time_ago = int(difference / time[0])
                if time_ago <= 1:
                    timeframe = time[1].rstrip('s')
                else:
                    timeframe = time[1]

                time_item = str(time_ago) + ' ' + timeframe
                return time_item
        return 'Date Error'
Related