So runden Sie die Minute eines Datetime-Objekts


101

I have a datetime object produced using strptime ().

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

Was ich tun muss, ist rund um die Minute auf die nächste 10. Minute. Bis jetzt habe ich den Minutenwert genommen und round () verwendet.

min = round(tm.minute, -1)

Wie im obigen Beispiel gibt es jedoch eine ungültige Zeit an, wenn der Minutenwert größer als 56 ist. Dh: 3:60

Was ist ein besserer Weg, dies zu tun? Hat datetimeunterstützt das?

Antworten:


134

Dadurch wird der 'Boden' eines datetimein tm gespeicherten Objekts auf die 10-Minuten-Marke gerundet tm.

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

Wenn Sie eine klassische Rundung auf die nächste 10-Minuten-Marke wünschen, gehen Sie folgendermaßen vor:

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)

oder dieses:

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

94

Allgemeine Funktion zum Runden einer Datums- / Uhrzeitangabe zu einem beliebigen Zeitpunkt in Sekunden:

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)

Proben mit 1 Stunde Rundung & 30 Minuten Rundung:

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

10
Leider funktioniert dies nicht mit tz-fähiger datetime. Man sollte dt.replace(hour=0, minute=0, second=0)statt verwenden dt.min.
skoval00

2
@ skoval00 + druska Bearbeitet gemäß Ihren Ratschlägen, um tz-fähige datetime zu unterstützen. Vielen Dank!
Le Droid

Danke @ skoval00 - ich habe eine Weile gebraucht, um herauszufinden, warum die Funktion nicht mit meinen Daten funktioniert
Mike Santos

1
Das funktioniert bei mir für längere Zeit überhaupt nicht. zB 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

Sehen Sie dies, um das Problem zu verstehen:datetime.timedelta(100,1,2,3).seconds == 1
CPBL

15

Von der besten Antwort, die ich auf eine angepasste Version geändert habe, bei der nur Datetime-Objekte verwendet wurden, muss die Konvertierung in Sekunden vermieden werden, und der aufrufende Code wird besser lesbar:

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)

Proben mit 1 Stunde Rundung & 15 Minuten Rundung:

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

1
Auch nicht gut: print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.timedelta(days=15)) 2012-12-20 00:00:00währendprint roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.timedelta(days=15)) 2012-12-21 00:00:00
CPBL

3
Follow-up zu oben: Nur darauf hinweisen, dass es nicht für beliebige Zeitdeltas funktioniert, z. B. für Deltas über 1 Tag. Bei dieser Frage geht es um das Runden von Minuten. Dies ist also eine angemessene Einschränkung, aber es könnte klarer sein, wie der Code geschrieben wird.
CPBL

13

Ich habe Stijn Nevens Code verwendet (danke Stijn) und habe ein kleines Add-On zum Teilen. Auf- und Abrunden und auf den nächsten runden.

Update 2019-03-09 = Kommentar Spinxz aufgenommen; Danke.

Update 27.12.2019 = Kommentar Bart aufgenommen; Danke.

Getestet auf date_delta von "X Stunden" oder "X Minuten" oder "X Sekunden".

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

Das hat mir geholfen. Ich möchte hinzufügen, dass bei Verwendung in PySpark die Datums- und Uhrzeitangabe als Zeichenfolge und nicht als Datums- / Uhrzeitobjekt analysiert wird.
Max

4
Die Aufrundung macht vielleicht nicht das, was die meisten Leute erwarten. Sie würden auf das nächste date_delta aufrunden, selbst wenn dt nicht gerundet werden müsste: zB 15: 30: 00.000 mit round_to = 60 würde 15: 31: 00.000
spinxz

Die upRundung ist mit dieser Funktion ohnehin ungenau; 2019-11-07 14:39:00.776980mit date_deltagleich zB 30 sek und to='up'ergibt 2019-11-07 14:39:00.
Bart

Vielen Dank!! Obwohl das upRunden möglicherweise kein häufiger Anwendungsfall ist, ist es erforderlich, wenn Sie Anwendungen bearbeiten, die an der Minutengrenze beginnen
Rahul Bharadwaj,

5

Pandas hat eine Datetime-Round-Funktion, aber wie bei den meisten Dingen in Pandas muss es im Serienformat sein.

>>> 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 - Ändern Sie die Frequenzzeichenfolge nach Bedarf.


Als Referenz Timestampenthält floorund ceilauch
Poulter7

3

Wenn Sie die Bedingung nicht verwenden möchten, können Sie den moduloOperator verwenden:

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

AKTUALISIEREN

Wolltest du so etwas?

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

.. wenn Sie Ergebnis als Zeichenfolge möchten. Um ein Datum / Uhrzeit-Ergebnis zu erhalten, ist es besser, timedelta zu verwenden - siehe andere Antworten;)


Ah, aber dann ist das Problem hier, dass die Stunde auch steigen muss
Lucas Manco

1
@ Lucas Manco - Meine Lösung funktioniert auch gut und ich denke, macht mehr Sinn.
Omnifarious

2

Ich benutze das. Es hat den Vorteil, mit tz-fähigen Datenzeiten zu arbeiten.

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

Es hat den Nachteil, nur für Zeitscheiben von weniger als einer Stunde zu arbeiten.


2

Hier ist eine einfachere allgemeine Lösung ohne Gleitkommapräzisionsprobleme und externe Bibliotheksabhängigkeiten:

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)

In deinem Fall:

>>> tm
datetime.datetime(2010, 6, 10, 3, 56, 23)
>>> time_round(tm, dt.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 4, 0)

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)

0

Basierend auf Stijn Nevens und modifiziert für Django, um die aktuelle Zeit auf die nächsten 15 Minuten zu runden.

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

Wenn Sie Datum und Uhrzeit benötigen, entfernen Sie einfach das .strftime('%H:%M:%S')


0

Nicht die beste Geschwindigkeit, wenn die Ausnahme abgefangen wird, dies würde jedoch funktionieren.

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

0

Eine zweizeilige intuitive Lösung zum Runden auf eine bestimmte Zeiteinheit, hier Sekunden, für ein datetimeObjekt t:

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

Wenn Sie zu einer anderen Einheit runden möchten, ändern Sie einfach format_str.

Dieser Ansatz rundet nicht wie oben beschrieben auf beliebige Zeitbeträge, sondern ist eine gut pythonische Methode, um auf eine bestimmte Stunde, Minute oder Sekunde zu runden.


0

Andere Lösung:

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

Hoffe das hilft!


0

Der kürzeste Weg, den ich kenne

min = tm.minute // 10 * 10


0

Diese scheinen zu komplex

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

0

Ein unkomplizierter Ansatz:

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

0

Ja, wenn Ihre Daten zu einer DateTime-Spalte in einer Pandas-Reihe gehören, können Sie sie mit der integrierten Funktion pandas.Series.dt.round aufrunden. Siehe Dokumentation hier auf pandas.Series.dt.round . Wenn Sie auf 10 Minuten runden, ist dies Series.dt.round ('10min') oder Series.dt.round ('600s') wie folgt:

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

Bearbeiten, um Beispielcode hinzuzufügen:

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]

Können Sie zeigen, wie Sie das verwenden, was Sie vorgeschlagen haben?
DanielM

Ich bin nicht sicher, ob diese Antwort etwas Neues oder Nützliches hinzufügt. Es gab bereits eine Antwort, die dasselbe erklärte: stackoverflow.com/a/56010357/7851470
Georgy

Ja, danke, dass Sie mich darauf hingewiesen haben. Es ist mein Fehler, keinen Beispielcode in meine Antwort aufzunehmen und auch nicht alle Antworten anderer Leute zu überprüfen. Ich werde versuchen, diesen Aspekt zu verbessern.
Nguyen Bryan
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.