web-dev-qa-db-fra.com

Comment produire une différence lisible par l'homme lors de la soustraction de deux horodatages UNIX à l'aide de Python?

Cette question est similaire à cette question sur la soustraction de dates avec Python , mais pas identique. Je ne traite pas de chaînes, je dois comprendre la différence entre deux horodatages Epoch et produire la différence dans un format lisible par l'homme.

Par exemple:

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

Alternativement, je voudrais exprimer la différence comme ceci:

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

Je ne pense pas pouvoir utiliser strptime, car je travaille avec la différence de deux dates. Je pourrais écrire quelque chose pour faire cela, mais je suis à peu près sûr qu'il y a déjà quelque chose d'écrit que je pourrais utiliser.

Quel module serait approprié? Est-ce que je manque juste quelque chose dans time? Mon voyage en Python ne fait que commencer, s'il s'agit bien d'un doublon, c'est que je n'ai pas trouvé ce que je devais rechercher.

Addenda

Pour des raisons de précision, le calendrier de l’année en cours m’intéresse beaucoup.

43
Tim Post

Vous pouvez utiliser le merveilleux dateutil module et son 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

Cela ne compte pas les semaines, mais cela ne devrait pas être trop difficile à ajouter.

46
Schnouki

Une petite amélioration par rapport à la solution de @ Schnouki avec une compréhension en une seule ligne. Affiche également le pluriel en cas d'entités plurielles (comme heures)

Import relativedelta

>>> from dateutil.relativedelta import relativedelta

Une fonction 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)]

Exemple d'utilisation:

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

J'ai eu exactement le même problème un peu plus tôt aujourd'hui et je ne pouvais rien trouver dans les bibliothèques standard que je pouvais utiliser. C'est ce que j'ai écrit:

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 __== "__main__":
        import doctest
        doctest.testmod()

Vous pouvez utiliser dateutil.relativedelta() pour calculer le delta temporel précis et l’humaniser avec ce script.

22
Liudmil Mitev
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

Exemple d'utilisation:

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

Avantage (vous n'avez pas besoin de tiers!).

Amélioration de l'algorithme "Liudmil Mitev". (Merci!)

12
makiolo

Vieille question, mais personnellement, j'aime beaucoup cette approche:

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 vous voulez séparer la partie secondes avec un "et" faire:

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

En voici un plus court pour l'intervalle en secondes et dans la journée (t <86400). Utile si vous travaillez avec des horodatages Unix (secondes depuis Epoch, UTC).

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

Peut être étendu davantage (t // 86400, ...).

0
Andor