Comment arrondir la minute d'un objet datetime

102

I have a datetime object produced using strptime ().

>>> tm
datetime.datetime(2010, 6, 10, 3, 56, 23)

Ce que je dois faire, c'est arrondir la minute à la 10e minute la plus proche. Ce que j'ai fait jusqu'à présent, c'est de prendre la valeur minute et d'utiliser round () dessus.

min = round(tm.minute, -1)

Cependant, comme dans l'exemple ci-dessus, il donne une heure invalide lorsque la valeur des minutes est supérieure à 56. c'est-à-dire: 3:60

Quelle est la meilleure façon de procéder? Supporte-t-il datetimecela?

Lucas Manco
la source

Réponses:

134

Cela obtiendra le «sol» d'un datetimeobjet stocké dans tm arrondi à la marque de 10 minutes avant tm.

tm = tm - datetime.timedelta(minutes=tm.minute % 10,
                             seconds=tm.second,
                             microseconds=tm.microsecond)

Si vous voulez un arrondi classique à la marque de 10 minutes la plus proche, procédez comme suit:

discard = datetime.timedelta(minutes=tm.minute % 10,
                             seconds=tm.second,
                             microseconds=tm.microsecond)
tm -= discard
if discard >= datetime.timedelta(minutes=5):
    tm += datetime.timedelta(minutes=10)

ou ca:

tm += datetime.timedelta(minutes=5)
tm -= datetime.timedelta(minutes=tm.minute % 10,
                         seconds=tm.second,
                         microseconds=tm.microsecond)
Très varié
la source
94

Fonction générale pour arrondir une date / heure à tout moment en secondes:

def roundTime(dt=None, roundTo=60):
   """Round a datetime object to any time lapse in seconds
   dt : datetime.datetime object, default now.
   roundTo : Closest number of seconds to round to, default 1 minute.
   Author: Thierry Husson 2012 - Use it as you want but don't blame me.
   """
   if dt == None : dt = datetime.datetime.now()
   seconds = (dt.replace(tzinfo=None) - dt.min).seconds
   rounding = (seconds+roundTo/2) // roundTo * roundTo
   return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)

Échantillons avec arrondi 1 heure et arrondi 30 minutes:

print roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=60*60)
2013-01-01 00:00:00

print roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=30*60)
2012-12-31 23:30:00
Le Droid
la source
10
Malheureusement, cela ne fonctionne pas avec le datetime compatible tz. On devrait utiliser à la dt.replace(hour=0, minute=0, second=0)place de dt.min.
skoval00
2
@ skoval00 + druska Modifié suite à vos conseils pour prendre en charge le datetime compatible tz. Merci!
Le Droid
Merci @ skoval00 - il m'a fallu un certain temps pour comprendre pourquoi la fonction ne fonctionnait pas avec mes données
Mike Santos
1
Cela ne fonctionne pas du tout pour moi pendant de longues périodes. eg roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=60*60*24*7)vsroundTime(datetime.datetime(2012,12,30,23,44,59,1234),roundTo=60*60*24*7)
CPBL
Voir ceci pour comprendre le problème:datetime.timedelta(100,1,2,3).seconds == 1
CPBL
15

De la meilleure réponse que j'ai modifiée à une version adaptée utilisant uniquement des objets datetime, cela évite d'avoir à faire la conversion en secondes et rend le code d'appel plus lisible:

def roundTime(dt=None, dateDelta=datetime.timedelta(minutes=1)):
    """Round a datetime object to a multiple of a timedelta
    dt : datetime.datetime object, default now.
    dateDelta : timedelta object, we round to a multiple of this, default 1 minute.
    Author: Thierry Husson 2012 - Use it as you want but don't blame me.
            Stijn Nevens 2014 - Changed to use only datetime objects as variables
    """
    roundTo = dateDelta.total_seconds()

    if dt == None : dt = datetime.datetime.now()
    seconds = (dt - dt.min).seconds
    # // is a floor division, not a comment on following line:
    rounding = (seconds+roundTo/2) // roundTo * roundTo
    return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)

Échantillons avec arrondi d'une heure et arrondi de 15 minutes:

print roundTime(datetime.datetime(2012,12,31,23,44,59),datetime.timedelta(hour=1))
2013-01-01 00:00:00

print roundTime(datetime.datetime(2012,12,31,23,44,49),datetime.timedelta(minutes=15))
2012-12-31 23:30:00
Stijn Nevens
la source
1
Aussi pas bon: print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.timedelta(days=15)) 2012-12-20 00:00:00tandis queprint roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.timedelta(days=15)) 2012-12-21 00:00:00
CPBL
3
Suivi de ci-dessus: il suffit de souligner que cela ne fonctionne pas pour les deltas de temps arbitraires, par exemple ceux sur 1 jour. Cette question concerne l'arrondi des minutes, c'est donc une restriction appropriée, mais elle pourrait être plus claire dans la façon dont le code est écrit.
CPBL
15

J'ai utilisé le code Stijn Nevens (merci Stijn) et j'ai un petit add-on à partager. Arrondir vers le haut, vers le bas et arrondir au plus proche.

mise à jour 09/03/2019 = commentaire Spinxz incorporé; Merci.

mise à jour 2019-12-27 = commentaire Bart incorporé; Merci.

Testé pour date_delta de «X heures» ou «X minutes» ou «X secondes».

import datetime

def round_time(dt=None, date_delta=datetime.timedelta(minutes=1), to='average'):
    """
    Round a datetime object to a multiple of a timedelta
    dt : datetime.datetime object, default now.
    dateDelta : timedelta object, we round to a multiple of this, default 1 minute.
    from:  http://stackoverflow.com/questions/3463930/how-to-round-the-minute-of-a-datetime-object-python
    """
    round_to = date_delta.total_seconds()
    if dt is None:
        dt = datetime.now()
    seconds = (dt - dt.min).seconds

    if seconds % round_to == 0 and dt.microsecond == 0:
        rounding = (seconds + round_to / 2) // round_to * round_to
    else:
        if to == 'up':
            # // is a floor division, not a comment on following line (like in javascript):
            rounding = (seconds + dt.microsecond/1000000 + round_to) // round_to * round_to
        elif to == 'down':
            rounding = seconds // round_to * round_to
        else:
            rounding = (seconds + round_to / 2) // round_to * round_to

    return dt + datetime.timedelta(0, rounding - seconds, - dt.microsecond)

# test data
print(round_time(datetime.datetime(2019,11,1,14,39,00), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2019,11,2,14,39,00,1), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2019,11,3,14,39,00,776980), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2019,11,4,14,39,29,776980), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2018,11,5,14,39,00,776980), date_delta=datetime.timedelta(seconds=30), to='down'))
print(round_time(datetime.datetime(2018,11,6,14,38,59,776980), date_delta=datetime.timedelta(seconds=30), to='down'))
print(round_time(datetime.datetime(2017,11,7,14,39,15), date_delta=datetime.timedelta(seconds=30), to='average'))
print(round_time(datetime.datetime(2017,11,8,14,39,14,999999), date_delta=datetime.timedelta(seconds=30), to='average'))
print(round_time(datetime.datetime(2019,11,9,14,39,14,999999), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2012,12,10,23,44,59,7769),to='average'))
print(round_time(datetime.datetime(2012,12,11,23,44,59,7769),to='up'))
print(round_time(datetime.datetime(2010,12,12,23,44,59,7769),to='down',date_delta=datetime.timedelta(seconds=1)))
print(round_time(datetime.datetime(2011,12,13,23,44,59,7769),to='up',date_delta=datetime.timedelta(seconds=1)))
print(round_time(datetime.datetime(2012,12,14,23,44,59),date_delta=datetime.timedelta(hours=1),to='down'))
print(round_time(datetime.datetime(2012,12,15,23,44,59),date_delta=datetime.timedelta(hours=1),to='up'))
print(round_time(datetime.datetime(2012,12,16,23,44,59),date_delta=datetime.timedelta(hours=1)))
print(round_time(datetime.datetime(2012,12,17,23,00,00),date_delta=datetime.timedelta(hours=1),to='down'))
print(round_time(datetime.datetime(2012,12,18,23,00,00),date_delta=datetime.timedelta(hours=1),to='up'))
print(round_time(datetime.datetime(2012,12,19,23,00,00),date_delta=datetime.timedelta(hours=1)))
MZA
la source
Cela m'a aidé. Je veux ajouter que si vous l'utilisez dans PySpark, pour analyser la date et l'heure sous la forme d'une chaîne plutôt que d'un objet date-heure.
Max
4
L'arrondissement «supérieur» ne fait peut-être pas ce que la plupart des gens attendent. Vous arrondiriez à la prochaine date_delta même si dt n'aurait pas besoin d'arrondi: par exemple, 15: 30: 00.000 avec round_to = 60 deviendrait 15: 31: 00.000
spinxz
L' uparrondi est de toute façon inexact avec cette fonction; 2019-11-07 14:39:00.776980avec date_deltaégale par exemple 30 sec etto='up' donne 2019-11-07 14:39:00.
Bart
1
Merci beaucoup!! Bien que l' uparrondi ne soit pas un cas d'utilisation courant, il est nécessaire lorsque vous traitez avec des applications qui commencent à la limite des minutes
Rahul Bharadwaj
5

Pandas a une fonction de ronde datetime, mais comme pour la plupart des choses dans Pandas, il doit être au format Série.

>>> ts = pd.Series(pd.date_range(Dt(2019,1,1,1,1),Dt(2019,1,1,1,4),periods=8))
>>> print(ts)
0   2019-01-01 01:01:00.000000000
1   2019-01-01 01:01:25.714285714
2   2019-01-01 01:01:51.428571428
3   2019-01-01 01:02:17.142857142
4   2019-01-01 01:02:42.857142857
5   2019-01-01 01:03:08.571428571
6   2019-01-01 01:03:34.285714285
7   2019-01-01 01:04:00.000000000
dtype: datetime64[ns]

>>> ts.dt.round('1min')
0   2019-01-01 01:01:00
1   2019-01-01 01:01:00
2   2019-01-01 01:02:00
3   2019-01-01 01:02:00
4   2019-01-01 01:03:00
5   2019-01-01 01:03:00
6   2019-01-01 01:04:00
7   2019-01-01 01:04:00
dtype: datetime64[ns]

Docs - Modifiez la chaîne de fréquence selon vos besoins.

Erock618
la source
Pour référence, Timestampcontient flooret ceilaussi
poulter7
3

si vous ne souhaitez pas utiliser de condition, vous pouvez utiliser l' moduloopérateur:

minutes = int(round(tm.minute, -1)) % 60

METTRE À JOUR

vouliez-vous quelque chose comme ça?

def timeround10(dt):
    a, b = divmod(round(dt.minute, -1), 60)
    return '%i:%02i' % ((dt.hour + a) % 24, b)

timeround10(datetime.datetime(2010, 1, 1, 0, 56, 0)) # 0:56
# -> 1:00

timeround10(datetime.datetime(2010, 1, 1, 23, 56, 0)) # 23:56
# -> 0:00

.. si vous voulez un résultat sous forme de chaîne. pour obtenir le résultat datetime, il est préférable d'utiliser timedelta - voir les autres réponses;)

mykhal
la source
Ah mais alors le problème ici est que l'heure doit aussi augmenter
Lucas Manco
1
@Lucas Manco - Ma solution fonctionne également très bien et je pense qu'elle a plus de sens.
Omnifarious
2

j'utilise ça. il a l'avantage de travailler avec des datetimes sensibles à tz.

def round_minutes(some_datetime: datetime, step: int):
    """ round up to nearest step-minutes """
    if step > 60:
        raise AttrbuteError("step must be less than 60")

    change = timedelta(
        minutes= some_datetime.minute % step,
        seconds=some_datetime.second,
        microseconds=some_datetime.microsecond
    )

    if change > timedelta():
        change -= timedelta(minutes=step)

    return some_datetime - change

il a l'inconvénient de ne fonctionner que pour des tranches horaires de moins d'une heure.

David Chan
la source
2

Voici une solution généralisée plus simple sans problèmes de précision en virgule flottante et dépendances de bibliothèques externes:

import datetime as dt

def time_mod(time, delta, epoch=None):
    if epoch is None:
        epoch = dt.datetime(1970, 1, 1, tzinfo=time.tzinfo)
    return (time - epoch) % delta

def time_round(time, delta, epoch=None):
    mod = time_mod(time, delta, epoch)
    if mod < (delta / 2):
       return time - mod
    return time + (delta - mod)

Dans ton cas:

>>> tm
datetime.datetime(2010, 6, 10, 3, 56, 23)
>>> time_round(tm, dt.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 4, 0)
ofo
la source
0
def get_rounded_datetime(self, dt, freq, nearest_type='inf'):

    if freq.lower() == '1h':
        round_to = 3600
    elif freq.lower() == '3h':
        round_to = 3 * 3600
    elif freq.lower() == '6h':
        round_to = 6 * 3600
    else:
        raise NotImplementedError("Freq %s is not handled yet" % freq)

    # // is a floor division, not a comment on following line:
    seconds_from_midnight = dt.hour * 3600 + dt.minute * 60 + dt.second
    if nearest_type == 'inf':
        rounded_sec = int(seconds_from_midnight / round_to) * round_to
    elif nearest_type == 'sup':
        rounded_sec = (int(seconds_from_midnight / round_to) + 1) * round_to
    else:
        raise IllegalArgumentException("nearest_type should be  'inf' or 'sup'")

    dt_midnight = datetime.datetime(dt.year, dt.month, dt.day)

    return dt_midnight + datetime.timedelta(0, rounded_sec)
JulienV
la source
0

Basé sur Stijn Nevens et modifié pour l'utilisation de Django pour arrondir l'heure actuelle aux 15 minutes les plus proches.

from datetime import date, timedelta, datetime, time

    def roundTime(dt=None, dateDelta=timedelta(minutes=1)):

        roundTo = dateDelta.total_seconds()

        if dt == None : dt = datetime.now()
        seconds = (dt - dt.min).seconds
        # // is a floor division, not a comment on following line:
        rounding = (seconds+roundTo/2) // roundTo * roundTo
        return dt + timedelta(0,rounding-seconds,-dt.microsecond)

    dt = roundTime(datetime.now(),timedelta(minutes=15)).strftime('%H:%M:%S')

 dt = 11:45:00

si vous avez besoin d'une date et d'une heure complètes, supprimez simplement le .strftime('%H:%M:%S')

Chemin derrière
la source
0

Pas le meilleur pour la vitesse lorsque l'exception est interceptée, mais cela fonctionnerait.

def _minute10(dt=datetime.utcnow()):
    try:
        return dt.replace(minute=round(dt.minute, -1))
    except ValueError:
        return dt.replace(minute=0) + timedelta(hours=1)

Timings

%timeit _minute10(datetime(2016, 12, 31, 23, 55))
100000 loops, best of 3: 5.12 µs per loop

%timeit _minute10(datetime(2016, 12, 31, 23, 31))
100000 loops, best of 3: 2.21 µs per loop
James
la source
0

Une solution intuitive sur deux lignes pour arrondir à une unité de temps donnée, ici secondes, pour un datetimeobjet t:

format_str = '%Y-%m-%d %H:%M:%S'
t_rounded = datetime.strptime(datetime.strftime(t, format_str), format_str)

Si vous souhaitez arrondir à une autre unité, modifiez simplement format_str.

Cette approche ne s'arrondit pas à des durées arbitraires comme les méthodes ci-dessus, mais est une manière bien pythonique d'arrondir à une heure, une minute ou une seconde donnée.

Barnhillec
la source
0

Autre solution:

def round_time(timestamp=None, lapse=0):
    """
    Round a timestamp to a lapse according to specified minutes

    Usage:

    >>> import datetime, math
    >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 0)
    datetime.datetime(2010, 6, 10, 3, 56)
    >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 1)
    datetime.datetime(2010, 6, 10, 3, 57)
    >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), -1)
    datetime.datetime(2010, 6, 10, 3, 55)
    >>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3)
    datetime.datetime(2019, 3, 11, 9, 24)
    >>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3*60)
    datetime.datetime(2019, 3, 11, 12, 0)
    >>> round_time(datetime.datetime(2019, 3, 11, 10, 0, 0), 3)
    datetime.datetime(2019, 3, 11, 10, 0)

    :param timestamp: Timestamp to round (default: now)
    :param lapse: Lapse to round in minutes (default: 0)
    """
    t = timestamp or datetime.datetime.now()  # type: Union[datetime, Any]
    surplus = datetime.timedelta(seconds=t.second, microseconds=t.microsecond)
    t -= surplus
    try:
        mod = t.minute % lapse
    except ZeroDivisionError:
        return t
    if mod:  # minutes % lapse != 0
        t += datetime.timedelta(minutes=math.ceil(t.minute / lapse) * lapse - t.minute)
    elif surplus != datetime.timedelta() or lapse < 0:
        t += datetime.timedelta(minutes=(t.minute / lapse + 1) * lapse - t.minute)
    return t

J'espère que cela t'aides!

Jules César
la source
0

Le moyen le plus court que je connaisse

min = tm.minute // 10 * 10

mLuzgin
la source
0

Celles-ci semblent trop complexes

def round_down_to():
    num = int(datetime.utcnow().replace(second=0, microsecond=0).minute)
    return num - (num%10)
Ashton Honnecke
la source
0

Une approche simple:

def round_time(dt, round_to_seconds=60):
    """Round a datetime object to any number of seconds
    dt: datetime.datetime object
    round_to_seconds: closest number of seconds for rounding, Default 1 minute.
    """
    rounded_epoch = round(dt.timestamp() / round_to_seconds) * round_to_seconds
    rounded_dt = datetime.datetime.fromtimestamp(rounded_epoch).astimezone(dt.tzinfo)
    return rounded_dt
claquer
la source
0

oui, si vos données appartiennent à une colonne DateTime d'une série pandas, vous pouvez les arrondir à l'aide de la fonction pandas.Series.dt.round intégrée. Voir la documentation ici sur pandas.Series.dt.round . Dans votre cas d'arrondi à 10min, ce sera Series.dt.round ('10min') ou Series.dt.round ('600s') comme ceci:

pandas.Series(tm).dt.round('10min')

Modifier pour ajouter un exemple de code:

import datetime
import pandas

tm = datetime.datetime(2010, 6, 10, 3, 56, 23)
tm_rounded = pandas.Series(tm).dt.round('10min')
print(tm_rounded)

>>> 0   2010-06-10 04:00:00
dtype: datetime64[ns]
Nguyen Bryan
la source
Pouvez-vous montrer comment utiliser ce que vous avez suggéré?
DanielM
Je ne suis pas sûr que cette réponse ajoute quelque chose de nouveau ou d'utile. Il y avait déjà une réponse qui expliquait la même chose: stackoverflow.com/a/56010357/7851470
Georgy
oui, merci de me les avoir signalées. C'est mon erreur de ne pas inclure des exemples de code dans ma réponse et de ne pas vérifier les réponses de toutes les autres personnes. J'essaierai d'améliorer cet aspect.
Nguyen Bryan