How can I produce a human readable difference when subtracting two UNIX timestamps using Python?

PythonTimeFormatting

Python Problem Overview


This question is similar to this question about subtracting dates with Python, but not identical. I'm not dealing with strings, I have to figure out the difference between two epoch time stamps and produce the difference in a human readable format.

For instance:

32 Seconds
17 Minutes
22.3 Hours
1.25 Days
3.5 Weeks
2 Months
4.25 Years

Alternately, I'd like to express the difference like this:

4 years, 6 months, 3 weeks, 4 days, 6 hours 21 minutes and 15 seconds

I don't think I can use strptime, since I'm working with the difference of two epoch dates. I could write something to do this, but I'm quite sure that there's something already written that I could use.

What module would be appropriate? Am I just missing something in time? My journey into Python is just really beginning, if this is indeed a duplicate it's because I failed to figure out what to search for.

Addendum

For accuracy, I really care most about the current year's calendar.

Python Solutions


Solution 1 - Python

You can use the wonderful dateutil module and its relativedelta class:

import datetime
import dateutil.relativedelta

dt1 = datetime.datetime.fromtimestamp(123456789) # 1973-11-29 22:33:09
dt2 = datetime.datetime.fromtimestamp(234567890) # 1977-06-07 23:44:50
rd = dateutil.relativedelta.relativedelta (dt2, dt1)

print "%d years, %d months, %d days, %d hours, %d minutes and %d seconds" % (rd.years, rd.months, rd.days, rd.hours, rd.minutes, rd.seconds)
# 3 years, 6 months, 9 days, 1 hours, 11 minutes and 41 seconds

It doesn't count weeks, but that shouldn't be too hard to add.

Solution 2 - Python

A little improvement over @Schnouki's solution with a single line list comprehension. Also displays the plural in case of plural entities (like hours)

Import relativedelta

>>> from dateutil.relativedelta import relativedelta

A lambda function

>>> attrs = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']
>>> human_readable = lambda delta: ['%d %s' % (getattr(delta, attr), attr if getattr(delta, attr) > 1 else attr[:-1]) 
...     for attr in attrs if getattr(delta, attr)]

Example usage:

>>> human_readable(relativedelta(minutes=125))
['2 hours', '5 minutes']
>>> human_readable(relativedelta(hours=(24 * 365) + 1))
['365 days', '1 hour']

Solution 3 - Python

I had that exact same problem earlier today and I couldn't find anything in the standard libraries that I could use, so this is what I wrote:

[humanize_time.py][1]

    #!/usr/bin/env python

    INTERVALS = [1, 60, 3600, 86400, 604800, 2419200, 29030400]
    NAMES = [('second', 'seconds'),             ('minute', 'minutes'),             ('hour', 'hours'),             ('day', 'days'),             ('week', 'weeks'),             ('month', 'months'),             ('year', 'years')]
    
    def humanize_time(amount, units):
    """
    Divide `amount` in time periods.
    Useful for making time intervals more human readable.
    
    >>> humanize_time(173, 'hours')
    [(1, 'week'), (5, 'hours')]
    >>> humanize_time(17313, 'seconds')
    [(4, 'hours'), (48, 'minutes'), (33, 'seconds')]
    >>> humanize_time(90, 'weeks')
    [(1, 'year'), (10, 'months'), (2, 'weeks')]
    >>> humanize_time(42, 'months')
    [(3, 'years'), (6, 'months')]
    >>> humanize_time(500, 'days')
    [(1, 'year'), (5, 'months'), (3, 'weeks'), (3, 'days')]
    """
       result = []
    
       unit = map(lambda a: a[1], NAMES).index(units)
       # Convert to seconds
       amount = amount * INTERVALS[unit]
    
       for i in range(len(NAMES)-1, -1, -1):
          a = amount / INTERVALS[i]
          if a > 0:
             result.append( (a, NAMES[i][1 % a]) )
             amount -= a * INTERVALS[i]
    
       return result
    
    if __name__ == "__main__":
        import doctest
        doctest.testmod()

[1]: https://github.com/liudmil-mitev/experiments/blob/master/time/humanize_time.py "Code on GitHub"

You can use dateutil.relativedelta() to calculate the accurate time delta and humanize it with this script.

Solution 4 - Python

def humanize_time(amount, units = 'seconds'):    
        
    def process_time(amount, units):
        
        INTERVALS = [   1, 60, 
                        60*60, 
                        60*60*24, 
                        60*60*24*7, 
                        60*60*24*7*4, 
                        60*60*24*7*4*12, 
                        60*60*24*7*4*12*100,
                        60*60*24*7*4*12*100*10]
        NAMES = [('second', 'seconds'),
                 ('minute', 'minutes'),
                 ('hour', 'hours'),
                 ('day', 'days'),
                 ('week', 'weeks'),
                 ('month', 'months'),
                 ('year', 'years'),
                 ('century', 'centuries'),
                 ('millennium', 'millennia')]
        
        result = []
        
        unit = map(lambda a: a[1], NAMES).index(units)
        # Convert to seconds
        amount = amount * INTERVALS[unit]
        
        for i in range(len(NAMES)-1, -1, -1):
            a = amount // INTERVALS[i]
            if a > 0: 
                result.append( (a, NAMES[i][1 % a]) )
                amount -= a * INTERVALS[i]
        
        return result
    
    rd = process_time(int(amount), units)
    cont = 0
    for u in rd:
        if u[0] > 0:
            cont += 1
    
    buf = ''
    i = 0
    for u in rd:
        if u[0] > 0:
            buf += "%d %s" % (u[0], u[1])
            cont -= 1
        
        if i < (len(rd)-1):
            if cont > 1:
                buf += ", "
            else:
                buf += " and "
        
        i += 1
    
    return buf

Example of use:

>>> print humanize_time(234567890 - 123456789)
3 years, 9 months, 3 weeks, 5 days, 11 minutes and 41 seconds
>>> humanize_time(9, 'weeks')
2 months and 1 week

Advantage (You don't need third parties!).

Improved from "Liudmil Mitev" algorithm. (Thanks!)

Solution 5 - Python

Check out the humanize package

https://github.com/jmoiron/humanize

import datetime

humanize.naturaltime(datetime.datetime.now() - datetime.timedelta(seconds=1))

'a second ago'

humanize.naturaltime(datetime.datetime.now() - datetime.timedelta(seconds=3600))

'an hour ago'

Solution 6 - Python

Old question, but I personally like this approach most:

import datetime
import math

def human_time(*args, **kwargs):
    secs  = float(datetime.timedelta(*args, **kwargs).total_seconds())
    units = [("day", 86400), ("hour", 3600), ("minute", 60), ("second", 1)]
    parts = []
    for unit, mul in units:
        if secs / mul >= 1 or mul == 1:
            if mul > 1:
                n = int(math.floor(secs / mul))
                secs -= n * mul
            else:
                n = secs if secs != int(secs) else int(secs)
            parts.append("%s %s%s" % (n, unit, "" if n == 1 else "s"))
    return ", ".join(parts)

human_time(seconds=3721)
# -> "1 hour, 2 minutes, 1 second"

If you want to separate the seconds part with an "and" do:

"%s and %s" % tuple(human_time(seconds=3721).rsplit(", ", 1))
# -> "1 hour, 2 minutes and 1 second"

Solution 7 - Python

Here's a shorter one for interval in seconds and within a day (t<86400). Useful if you work with unix timestamps (seconds since epoch, UTC).

t = 45678
print('%d hours, %d minutes, %d seconds' % (t//3600, t%3600//60, t%60))

May be extended further (t//86400, ...).

Solution 8 - Python

A very old question but I found this solution which seems to be very simple in Python3:

print(datetime.timedelta(seconds=3600))
# output: 1:00:00
print(datetime.timedelta(hours=360.1245))
# output: 15 days, 0:07:28.200000

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
QuestionTim PostView Question on Stackoverflow
Solution 1 - PythonSchnoukiView Answer on Stackoverflow
Solution 2 - PythonSharoon ThomasView Answer on Stackoverflow
Solution 3 - PythonLiudmil MitevView Answer on Stackoverflow
Solution 4 - PythonmakioloView Answer on Stackoverflow
Solution 5 - PythonJoshuaView Answer on Stackoverflow
Solution 6 - PythonRigaView Answer on Stackoverflow
Solution 7 - PythonAndorView Answer on Stackoverflow
Solution 8 - PythonElendilView Answer on Stackoverflow