¿Cómo puedo producir una diferencia legible para el ser humano al restar dos marcas de tiempo UNIX mediante Python?

Esta pregunta es similar a esta pregunta sobre restar fechas con Python , pero no es idéntica. No estoy tratando con cadenas, tengo que descubrir la diferencia entre dos sellos de tiempo de época y producir la diferencia en un formato legible por humanos.

Por ejemplo:

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

Alternativamente, me gustaría express la diferencia de esta manera:

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

No creo que pueda usar strptime , ya que estoy trabajando con la diferencia de dos fechas de época. Podría escribir algo para hacer esto, pero estoy seguro de que hay algo ya escrito que podría usar.

¿Qué módulo sería apropiado? ¿Me estoy perdiendo algo en el time ? Mi viaje a Python es realmente el comienzo, si este es un duplicado porque no pude averiguar qué buscar.

Apéndice

Para mayor precisión, realmente me importa el calendario del año actual.

Puedes usar el maravilloso módulo dateutil y su clase relativedelta :

 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 

No cuenta semanas, pero eso no debería ser demasiado difícil de agregar.

Una pequeña mejora con respecto a la solución de @ Schnouki con una única lista de líneas de comprensión. También muestra el plural en caso de entidades plurales (como horas)

Importar relativedelta

 >>> from dateutil.relativedelta import relativedelta 

Una funcion lambda

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

Ejemplo de uso:

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

Tuve el mismo problema al principio de hoy y no pude encontrar nada en las bibliotecas estándar que pudiera usar, así que esto es lo que escribí:

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() 

Puede usar dateutil.relativedelta() para calcular el delta de tiempo exacto y humanizarlo con este script.

 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 

Ejemplo de uso:

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

Ventaja (¡No necesitas terceros!).

Mejorado del algoritmo “Liudmil Mitev”. (¡Gracias!)

Antigua pregunta, pero personalmente me gusta más este enfoque:

 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" 

Si desea separar la parte de segundos con un “y” haga:

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

Aquí hay uno más corto para el intervalo en segundos y dentro de un día (t <86400). Útil si trabajas con marcas de tiempo de Unix (segundos desde la época, UTC).

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

Puede ampliarse más (t // 86400, …).