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