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?

Lucas Manco
quelle

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)
Allgegenwärtig
quelle
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
Le Droid
quelle
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
Stijn Nevens
quelle
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)))
MZA
quelle
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.

Erock618
quelle
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;)

Mykhal
quelle
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.

David Chan
quelle
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)
ofo
quelle
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
quelle
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')

WayBehind
quelle
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
James
quelle
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.

Barnhillec
quelle
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!

Julius Caesar
quelle
0

Der kürzeste Weg, den ich kenne

min = tm.minute // 10 * 10

mLuzgin
quelle
0

Diese scheinen zu komplex

def round_down_to():
    num = int(datetime.utcnow().replace(second=0, microsecond=0).minute)
    return num - (num%10)
Ashton Honnecke
quelle
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
zuschlagen
quelle
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]
Nguyen Bryan
quelle
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