How can I produce a human readable difference when subtracting two UNIX timestamps using 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.


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

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

    #!/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()

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