User-friendly time format in Python?

PythonDatetimeDateTimeFormatting

Python Problem Overview


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.

Python Solutions


Solution 1 - Python

The code was originally published on a blog post "Python Pretty Date function" (http://evaisse.com/post/93417709/python-pretty-date-function)

It is reproduced here as the blog account has been suspended and the page is no longer available.

def pretty_date(time=False):
    """
    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
    """
    from datetime import datetime
    now = datetime.now()
    if type(time) is int:
        diff = now - datetime.fromtimestamp(time)
    elif isinstance(time, datetime):
        diff = now - time
    elif not time:
        diff = 0
    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"

Solution 2 - Python

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.

Solution 3 - Python

In looking for the same thing with the additional requirement that it handle future dates, I found this: http://pypi.python.org/pypi/py-pretty/1

Example code (from site):

from datetime import datetime, timedelta
now = datetime.now()
hrago = now - timedelta(hours=1)
yesterday = now - timedelta(days=1)
tomorrow = now + timedelta(days=1)
dayafter = now + timedelta(days=2)

import pretty
print pretty.date(now)                      # 'now'
print pretty.date(hrago)                    # 'an hour ago'
print pretty.date(hrago, short=True)        # '1h ago'
print pretty.date(hrago, asdays=True)       # 'today'
print pretty.date(yesterday, short=True)    # 'yest'
print pretty.date(tomorrow)                 # 'tomorrow'

Solution 4 - Python

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'

Solution 5 - Python

There is humanize package:

>>> from datetime import datetime, timedelta
>>> import humanize # $ pip install humanize
>>> humanize.naturaltime(datetime.now() - timedelta(days=1))
'a day ago'
>>> humanize.naturaltime(datetime.now() - timedelta(hours=2))
'2 hours ago'

It supports localization [tag:l10n], internationalization [tag:i18n]:

>>> _ = humanize.i18n.activate('ru_RU')
>>> print humanize.naturaltime(datetime.now() - timedelta(days=1))
день назад
>>> print humanize.naturaltime(datetime.now() - timedelta(hours=2))
2 часа назад

Solution 6 - Python

The answer Jed Smith linked to is good, and I used it for a year or so, but I think it could be improved in a few ways:

  • It's nice to be able to define each time unit in terms of the preceding unit, instead of having "magic" constants like 3600, 86400, etc. sprinkled throughout the code.
  • After much use, I find I don't want to go to the next unit quite so eagerly. Example: both 7 days and 13 days will show as "1 week"; I'd rather see "7 days" or "13 days" instead.

Here's what I came up with:

def PrettyRelativeTime(time_diff_secs):
    # Each tuple in the sequence gives the name of a unit, and the number of
    # previous units which go into it.
    weeks_per_month = 365.242 / 12 / 7
    intervals = [('minute', 60), ('hour', 60), ('day', 24), ('week', 7),
                 ('month', weeks_per_month), ('year', 12)]

    unit, number = 'second', abs(time_diff_secs)
    for new_unit, ratio in intervals:
        new_number = float(number) / ratio
        # If the new number is too small, don't go to the next unit.
        if new_number < 2:
            break
        unit, number = new_unit, new_number
    shown_num = int(number)
    return '{} {}'.format(shown_num, unit + ('' if shown_num == 1 else 's'))

Notice how every tuple in intervals is easy to interpret and check: a 'minute' is 60 seconds; an 'hour' is 60 minutes; etc. The only fudge is setting weeks_per_month to its average value; given the application, that should be fine. (And note that it's clear at a glance that the last three constants multiply out to 365.242, the number of days per year.)

One downside to my function is that it doesn't do anything outside the "## units" pattern: "Yesterday", "just now", etc. are right out. Then again, the original poster didn't ask for these fancy terms, so I prefer my function for its succinctness and the readability of its numerical constants. :)

Solution 7 - Python

The ago package provides this. Call human on a datetime object to get a human readable description of the difference.

from ago import human
from datetime import datetime
from datetime import timedelta

ts = datetime.now() - timedelta(days=1, hours=5)

print(human(ts))
# 1 day, 5 hours ago

print(human(ts, precision=1))
# 1 day ago

Solution 8 - Python

Using datetime objects with tzinfo:

def time_elapsed(etime):
    # need to add tzinfo to datetime.utcnow
    now = datetime.utcnow().replace(tzinfo=etime.tzinfo)
    opened_for = (now - etime).total_seconds()
    names = ["seconds","minutes","hours","days","weeks","months"]
    modulos = [ 1,60,3600,3600*24,3600*24*7,3660*24*30]
    values = []
    for m in modulos[::-1]:
        values.append(int(opened_for / m))
        opened_for -= values[-1]*m
    pretty = [] 
    for i,nm in enumerate(names[::-1]):
        if values[i]!=0:
            pretty.append("%i %s" % (values[i],nm))
    return " ".join(pretty)

Solution 9 - Python

I have written a detailed blog post for the solution on http://sunilarora.org/17329071 I am posting a quick snippet here as well.

from datetime import datetime
from dateutil.relativedelta import relativedelta

def get_fancy_time(d, display_full_version = False):
    """Returns a user friendly date format
    d: some datetime instace in the past
    display_second_unit: True/False
    """
    #some helpers lambda's
    plural = lambda x: 's' if x > 1 else ''
    singular = lambda x: x[:-1]
    #convert pluran (years) --> to singular (year)
    display_unit = lambda unit, name: '%s %s%s'%(unit, name, plural(unit)) if unit > 0 else ''

    #time units we are interested in descending order of significance
    tm_units = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']

    rdelta = relativedelta(datetime.utcnow(), d) #capture the date difference
    for idx, tm_unit in enumerate(tm_units):
        first_unit_val = getattr(rdelta, tm_unit)
        if first_unit_val > 0:
            primary_unit = display_unit(first_unit_val, singular(tm_unit))
            if display_full_version and idx < len(tm_units)-1:
                next_unit = tm_units[idx + 1]
                second_unit_val = getattr(rdelta, next_unit)
                if second_unit_val > 0:
                    secondary_unit = display_unit(second_unit_val, singular(next_unit))
                    return primary_unit + ', '  + secondary_unit
            return primary_unit
    return None

Solution 10 - Python

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 ""

Solution 11 - Python

This is the gist of @sunil 's post

>>> from datetime import datetime
>>> from dateutil.relativedelta import relativedelta
>>> then = datetime(2003, 9, 17, 20, 54, 47, 282310)
>>> relativedelta(then, datetime.now())
relativedelta(years=-11, months=-3, days=-9, hours=-18, minutes=-17, seconds=-8, microseconds=+912664)

Solution 12 - Python

You can download and install from below link. It should be more helpful for you. It has been providing user friendly message from second to year.

It's well tested.

https://github.com/nareshchaudhary37/timestamp_content

Below steps to install into your virtual env.

git clone https://github.com/nareshchaudhary37/timestamp_content
cd timestamp-content
python setup.py

Solution 13 - Python

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"

Solution 14 - Python

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionflybywireView Question on Stackoverflow
Solution 1 - PythonJed SmithView Answer on Stackoverflow
Solution 2 - PythonplowmanView Answer on Stackoverflow
Solution 3 - PythonKarimView Answer on Stackoverflow
Solution 4 - Pythonvishes_shellView Answer on Stackoverflow
Solution 5 - PythonjfsView Answer on Stackoverflow
Solution 6 - PythonChip HoggView Answer on Stackoverflow
Solution 7 - PythonrussellballestriniView Answer on Stackoverflow
Solution 8 - PythonCharles DoutriauxView Answer on Stackoverflow
Solution 9 - PythonsunilView Answer on Stackoverflow
Solution 10 - PythonZagsView Answer on Stackoverflow
Solution 11 - PythonSteven AlmerothView Answer on Stackoverflow
Solution 12 - PythonNaresh ChaudharyView Answer on Stackoverflow
Solution 13 - PythonMikko OhtamaaView Answer on Stackoverflow
Solution 14 - PythonAndreiView Answer on Stackoverflow