Como arredondar o minuto de um objeto datetime


102

I have a datetime object produced using strptime ().

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

O que eu preciso fazer é arredondar o minuto para o 10º minuto mais próximo. O que tenho feito até este ponto é pegar o valor do minuto e usar round () nele.

min = round(tm.minute, -1)

No entanto, como no exemplo acima, ele fornece um tempo inválido quando o valor do minuto é maior que 56. ou seja: 3:60

Qual é a melhor maneira de fazer isso? Será que datetimesuportar isso?

Respostas:


134

Isso fará com que o 'piso' de um datetimeobjeto armazenado em tm seja arredondado para a marca de 10 minutos anteriores tm.

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

Se você quiser um arredondamento clássico para a marca de 10 minutos mais próxima, faça o seguinte:

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

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

94

Função geral para arredondar uma data e hora em qualquer lapso de tempo em 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)

Amostras com arredondamento de 1 hora e arredondamento 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

10
Infelizmente, isso não funciona com datetime com reconhecimento de tz. Deve-se usar em dt.replace(hour=0, minute=0, second=0)vez de dt.min.
skoval00

2
@ skoval00 + druska Editado de acordo com seus conselhos para suportar datetime tz-aware. Obrigado!
Le Droid

Obrigado @ skoval00 - demorei um pouco para entender porque a função não estava funcionando com meus dados
Mike Santos

1
Isso não funciona para mim por longos períodos. por exemplo, 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

Veja isto para entender o problema:datetime.timedelta(100,1,2,3).seconds == 1
CPBL

15

Da melhor resposta que modifiquei para uma versão adaptada usando apenas objetos datetime, isso evita ter que fazer a conversão para segundos e torna o código de chamada mais legível:

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)

Amostras com arredondamento de 1 hora e arredondamento 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

1
Também não é bom: print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.timedelta(days=15)) 2012-12-20 00:00:00enquantoprint roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.timedelta(days=15)) 2012-12-21 00:00:00
CPBL

3
Acompanhamento do acima: Apenas observando que não funciona para deltas de tempo arbitrários, por exemplo, aqueles com mais de 1 dia. Esta questão é sobre arredondamento de minutos, então essa é uma restrição apropriada, mas poderia ser mais clara na forma como o código está escrito.
CPBL

15

Usei o código Stijn Nevens (obrigado Stijn) e tenho um pequeno add-on para compartilhar. Arredondando para cima, para baixo e para o mais próximo.

atualização 09/03/2019 = comentário Spinxz incorporado; obrigado.

atualização 27-12-2019 = comentário Bart incorporado; obrigado.

Testado para date_delta de "X horas" ou "X minutos" ou "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)))

Isso me ajudou. Quero acrescentar que, se usá-lo no PySpark, para analisar a data e hora como uma string em vez de um objeto de data e hora.
Máximo de

4
O arredondamento 'para cima' talvez não esteja fazendo o que a maioria das pessoas espera. Você arredondaria para o próximo date_delta mesmo se dt não precisasse de arredondamento: por exemplo, 15: 30: 00.000 com round_to = 60 se tornaria 15: 31: 00.000
spinxz

De upqualquer forma, o arredondamento é impreciso com esta função; 2019-11-07 14:39:00.776980com date_deltaigual a, por exemplo, 30 seg e to='up'resulta em 2019-11-07 14:39:00.
Bart

1
Muito obrigado!! Embora o uparredondamento possa não ser um caso de uso comum, ele é necessário quando você está lidando com aplicativos que começam no limite de minuto
Rahul Bharadwaj

5

Pandas tem um recurso de data e hora, mas como a maioria das coisas no Pandas, ele precisa estar no formato de 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 - Altere a sequência de frequência conforme necessário.


Para referência, Timestampcontém floore ceiltambém
poulter7

3

se não quiser usar a condição, você pode usar o modulooperador:

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

ATUALIZAR

você queria algo assim?

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

.. se você quiser o resultado como string. para obter o resultado datetime, é melhor usar timedelta - veja outras respostas;)


Ah, mas o problema aqui é que a hora deve aumentar também
Lucas Manco

1
@Lucas Manco - Minha solução também funciona bem e acho que faz mais sentido.
Onifário

2

eu estou usando isso. tem a vantagem de trabalhar com datetimes cientes 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

tem a desvantagem de trabalhar por períodos de tempo inferiores a uma hora.


2

Aqui está uma solução generalizada mais simples, sem problemas de precisão de ponto flutuante e dependências de biblioteca externa:

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)

No seu caso:

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

Baseado em Stijn Nevens e modificado para Django, use para arredondar a hora atual para os 15 minutos mais próximos.

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

se precisar de data e hora completas, basta remover o .strftime('%H:%M:%S')


0

Não é o melhor para velocidade quando a exceção é detectada, no entanto, isso funcionaria.

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

Horários

%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

Uma solução intuitiva de duas linhas para arredondar para uma determinada unidade de tempo, aqui segundos, para um datetimeobjeto t:

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

Se desejar arredondar para uma unidade diferente, basta alterar format_str.

Essa abordagem não arredonda para quantias de tempo arbitrárias como os métodos acima, mas é uma maneira agradavelmente pitônica de arredondar para uma determinada hora, minuto ou segundo.


0

Outra solução:

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 isto ajude!


0

O caminho mais curto que eu conheço

min = tm.minute // 10 * 10


0

Aqueles parecem excessivamente complexos

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

0

Uma abordagem direta:

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

sim, se seus dados pertencerem a uma coluna DateTime em uma série de pandas, você pode arredondá-los usando a função interna pandas.Series.dt.round. Veja a documentação aqui em pandas.Series.dt.round . No seu caso de arredondamento para 10min, será Series.dt.round ('10min') ou Series.dt.round ('600s') assim:

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

Edite para adicionar o código de exemplo:

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]

Você pode mostrar como usar o que sugeriu?
DanielM

Não tenho certeza se essa resposta acrescenta algo novo ou útil. Já havia uma resposta que explicava a mesma coisa: stackoverflow.com/a/56010357/7851470
Georgy

sim, obrigado por apontar isso para mim. É meu erro não incluir códigos de amostra em minha resposta e também não verificar as respostas de todas as outras pessoas. Vou tentar melhorar nesse aspecto.
Nguyen Bryan
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.