开发者

User-friendly time format in Python?

Python: I need to show file modification times in the "1 day ago", "t开发者_JAVA技巧wo hours ago", format.

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


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"


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.


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'


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'


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 l10n, internationalization i18n:

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


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. :)


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


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)


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


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


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)


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


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'
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜