Cómo redondear el minuto de un objeto de fecha y hora

102

I have a datetime object produced using strptime ().

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

Lo que tengo que hacer es redondear el minuto al décimo minuto más cercano. Lo que he estado haciendo hasta este punto fue tomar el valor de los minutos y usar round () en él.

min = round(tm.minute, -1)

Sin embargo, como en el ejemplo anterior, da un tiempo no válido cuando el valor de los minutos es mayor que 56. es decir: 3:60

¿Cuál es una mejor manera de hacer esto? ¿ datetimeApoya esto?

Lucas Manco
fuente

Respuestas:

134

Esto hará que el 'piso' de un datetimeobjeto almacenado en tm se redondee a la marca de 10 minutos anterior tm.

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

Si desea un redondeo clásico a la marca de 10 minutos más cercana, haga lo siguiente:

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)

o esto:

tm += datetime.timedelta(minutes=5)
tm -= datetime.timedelta(minutes=tm.minute % 10,
                         seconds=tm.second,
                         microseconds=tm.microsecond)
De todo género
fuente
94

Función general para redondear una fecha y hora en cualquier lapso de tiempo en segundos:

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)

Muestras con redondeo de 1 hora y redondeo de 30 minutos:

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
fuente
10
Desafortunadamente, esto no funciona con datetime compatible con tz. Uno debería usar en dt.replace(hour=0, minute=0, second=0)lugar de dt.min.
skoval00
2
@ skoval00 + druska Editado siguiendo sus consejos para admitir la fecha y hora compatibles con tz. ¡Gracias!
Le Droid
Gracias @ skoval00 - me tomó un tiempo descubrir por qué la función no funcionaba con mis datos
Mike Santos
1
Esto no me funciona en absoluto durante períodos prolongados. p. ej. 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
Vea esto para entender el problema:datetime.timedelta(100,1,2,3).seconds == 1
CPBL
15

Desde la mejor respuesta que modifiqué a una versión adaptada usando solo objetos de fecha y hora, esto evita tener que hacer la conversión a segundos y hace que el código de llamada sea más legible:

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)

Muestras con redondeo de 1 hora y redondeo de 15 minutos:

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
fuente
1
Tampoco es bueno: print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.timedelta(days=15)) 2012-12-20 00:00:00whileprint roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.timedelta(days=15)) 2012-12-21 00:00:00
CPBL
3
Seguimiento de lo anterior: Solo señala que no funciona para deltas de tiempo arbitrarios, por ejemplo, los de más de 1 día. Esta pregunta se trata de redondear los minutos, por lo que es una restricción adecuada, pero podría ser más clara en la forma en que está escrito el código.
CPBL
15

Usé el código de Stijn Nevens (gracias Stijn) y tengo un pequeño complemento para compartir. Redondeo hacia arriba, hacia abajo y redondeando al más cercano.

actualización 2019-03-09 = comentario Spinxz incorporado; gracias.

actualización 2019-12-27 = comentario Bart incorporado; gracias.

Probado para date_delta de "X horas" o "X minutos" o "X segundos".

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
fuente
Eso me ayudó. Quiero agregar que si lo usas en PySpark, para analizar la fecha y hora como una cadena en lugar de un objeto de fecha y hora.
Máximo
4
El redondeo "al alza" quizás no esté haciendo lo que la mayoría de la gente espera. Debería redondear al siguiente date_delta incluso si dt no necesita redondeo: por ejemplo, 15: 30: 00.000 con round_to = 60 se convertiría en 15: 31: 00.000
spinxz
De uptodos modos, el redondeo es inexacto con esta función; 2019-11-07 14:39:00.776980con date_deltaigual, por ejemplo, 30 segundos y to='up'da como resultado 2019-11-07 14:39:00.
Bart
1
¡¡Muchas gracias!! Aunque el upredondeo puede no ser un caso de uso común, es necesario cuando se trata de aplicaciones que comienzan en el límite de un minuto
Rahul Bharadwaj
5

Pandas tiene una función de ronda de fecha y hora, pero como con la mayoría de las cosas en Pandas, debe estar en formato de serie.

>>> 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 : cambie la cadena de frecuencia según sea necesario.

Erock618
fuente
Como referencia, Timestampcontiene floory ceiltambién
poulter7
3

si no desea usar la condición, puede usar el modulooperador:

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

ACTUALIZAR

¿querías algo como esto?

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 quieres resultado como cadena. para obtener el resultado de fecha y hora, es mejor usar timedelta - ver otras respuestas;)

mykhal
fuente
Ah, pero el problema aquí es que la hora también debe aumentar
Lucas Manco
1
@Lucas Manco - Mi solución también funciona bien y creo que tiene más sentido.
Omnifarious
2

estoy usando esto. tiene la ventaja de trabajar con fechas y horas conscientes de 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

tiene la desventaja de que solo trabaja por tiempos de menos de una hora.

David Chan
fuente
2

Aquí hay una solución generalizada más simple sin problemas de precisión de punto flotante y dependencias de bibliotecas externas:

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)

En tu caso:

>>> tm
datetime.datetime(2010, 6, 10, 3, 56, 23)
>>> time_round(tm, dt.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 4, 0)
ofo
fuente
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
fuente
0

Basado en Stijn Nevens y modificado para el uso de Django para redondear la hora actual a los 15 minutos más cercanos.

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 necesita la fecha y la hora completas, simplemente elimine el .strftime('%H:%M:%S')

Muy atras
fuente
0

No es lo mejor para la velocidad cuando se detecta la excepción, sin embargo, esto funcionaría.

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

Tiempos

%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
fuente
0

Una solución intuitiva de dos líneas para redondear a una unidad de tiempo dada, aquí segundos, para un datetimeobjeto t:

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

Si desea redondear a una unidad diferente, simplemente modifique format_str.

Este enfoque no se redondea a cantidades de tiempo arbitrarias como los métodos anteriores, sino que es una forma muy pitónica de redondear a una hora, minuto o segundo determinados.

barnhillec
fuente
0

Otra solucion:

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

¡Espero que esto ayude!

julio Cesar
fuente
0

El camino más corto que conozco

min = tm.minuto // 10 * 10

mLuzgin
fuente
0

Aquellos parecen demasiado complejos

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

Un enfoque sencillo:

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
slamm
fuente
0

sí, si sus datos pertenecen a una columna DateTime en una serie de pandas, puede redondearlos usando la función integrada pandas.Series.dt.round. Consulte la documentación aquí sobre pandas.Series.dt.round . En su caso de redondeo a 10min, será Series.dt.round ('10min') o Series.dt.round ('600s') así:

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

Edite para agregar código de ejemplo:

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
fuente
¿Puede mostrar cómo usar lo que sugirió?
DanielM
No estoy seguro de que esta respuesta agregue algo nuevo o útil. Ya había una respuesta que explicaba lo mismo: stackoverflow.com/a/56010357/7851470
Georgy
sí, gracias por señalarme esto. Es mi error no incluir código de muestra en mi respuesta y tampoco verificar las respuestas de todas las demás personas. Intentaré mejorar este aspecto.
Nguyen Bryan