Qual é a maneira padrão de adicionar N segundos ao datetime.time no Python?

369

Dado um datetime.timevalor em Python, existe uma maneira padrão de adicionar um número inteiro de segundos a ele, de modo que 11:34:59+ 3 = 11:35:02, por exemplo?

Essas idéias óbvias não funcionam:

>>> datetime.time(11, 34, 59) + 3
TypeError: unsupported operand type(s) for +: 'datetime.time' and 'int'
>>> datetime.time(11, 34, 59) + datetime.timedelta(0, 3)
TypeError: unsupported operand type(s) for +: 'datetime.time' and 'datetime.timedelta'
>>> datetime.time(11, 34, 59) + datetime.time(0, 0, 3)
TypeError: unsupported operand type(s) for +: 'datetime.time' and 'datetime.time'

No final, escrevi funções como esta:

def add_secs_to_time(timeval, secs_to_add):
    secs = timeval.hour * 3600 + timeval.minute * 60 + timeval.second
    secs += secs_to_add
    return datetime.time(secs // 3600, (secs % 3600) // 60, secs % 60)

Não posso deixar de pensar que estou perdendo uma maneira mais fácil de fazer isso.

Relacionado

Paul Stephenson
fonte
problema relacionado ao Python: suporte datetime.time para '+' e '-'
jfs

Respostas:

499

Você pode usar datetimevariáveis completas com timedelta, e fornecendo uma data fictícia e usando timeapenas para obter o valor do tempo.

Por exemplo:

import datetime
a = datetime.datetime(100,1,1,11,34,59)
b = a + datetime.timedelta(0,3) # days, seconds, then other fields.
print(a.time())
print(b.time())

resulta nos dois valores, com três segundos de diferença:

11:34:59
11:35:02

Você também pode optar pelo mais legível

b = a + datetime.timedelta(seconds=3)

se você é tão inclinado.


Se você procura uma função que pode fazer isso, pode usar o addSecsseguinte:

import datetime

def addSecs(tm, secs):
    fulldate = datetime.datetime(100, 1, 1, tm.hour, tm.minute, tm.second)
    fulldate = fulldate + datetime.timedelta(seconds=secs)
    return fulldate.time()

a = datetime.datetime.now().time()
b = addSecs(a, 300)
print(a)
print(b)

Isso gera:

 09:11:55.775695
 09:16:55
paxdiablo
fonte
6
Para evitar OverflowErrors, eu recomendaria o uso de uma data fictícia diferente, por exemplo, alguns anos depois: datetime (101,1,1,11,34,59). Se você tentar subtrair uma grande timedelta a partir da data acima, você vai ter um "OverflowError: valor de data fora do intervalo" erro como o ano para um objeto de data e hora não pode ser menor do que 1
pheelicks
2
@ wheelicks, pronto, embora um pouco atrasado, não exatamente com tempos de resposta ágeis :-) Como tive que corrigir outro bug no meu código, pensei em incorporar sua sugestão ao mesmo tempo.
precisa saber é
53

Como outros aqui declararam, você pode simplesmente usar objetos completos de data e hora em todo o evento:

from datetime import datetime, date, time, timedelta
sometime = time(8,00) # 8am
later = (datetime.combine(date.today(), sometime) + timedelta(seconds=3)).time()

No entanto, acho que vale a pena explicar por que os objetos completos de data e hora são necessários. Considere o que aconteceria se eu adicionasse duas horas às 23h. Qual é o comportamento correto? Uma exceção, porque você não pode ter um tempo maior que 23:59? Deveria voltar?

Programadores diferentes esperam coisas diferentes; portanto, qualquer resultado que eles escolherem surpreenderá muitas pessoas. Pior ainda, os programadores escreveriam código que funcionou bem quando o testaram inicialmente e depois o interromperam fazendo algo inesperado. Isso é muito ruim, e é por isso que você não tem permissão para adicionar objetos timedelta a objetos de tempo.

Eli Courtwright
fonte
22

Uma pequena coisa, pode adicionar clareza para substituir o valor padrão por segundos

>>> b = a + datetime.timedelta(seconds=3000)
>>> b
datetime.datetime(1, 1, 1, 12, 24, 59)
desmontado
fonte
4
Eu gosto deste. Agradável e claro com o argumento especificado.
Vigrond
12

Agradecemos a @Pax Diablo, @bvmou e @Arachnid pela sugestão de usar datetime completos em todo o site. Se eu tiver que aceitar objetos datetime.time de uma fonte externa, isso parece ser uma add_secs_to_time()função alternativa :

def add_secs_to_time(timeval, secs_to_add):
    dummy_date = datetime.date(1, 1, 1)
    full_datetime = datetime.datetime.combine(dummy_date, timeval)
    added_datetime = full_datetime + datetime.timedelta(seconds=secs_to_add)
    return added_datetime.time()

Este código detalhado pode ser compactado para esta linha:

(datetime.datetime.combine(datetime.date(1, 1, 1), timeval) + datetime.timedelta(seconds=secs_to_add)).time()

mas acho que gostaria de encerrar isso em uma função para maior clareza do código.

Paul Stephenson
fonte
Obrigado - ótima idéia
Anupam
9

Se vale a pena adicionar outro arquivo / dependência ao seu projeto, acabei de escrever uma pequena classe que se estende datetime.timecom a capacidade de fazer aritmética. Quando você passa da meia-noite, ele fica em torno de zero. Agora, "que horas serão daqui a 24 horas" tem muitos casos de canto, incluindo horário de verão, segundos bissextos, alterações históricas do fuso horário e assim por diante. Mas às vezes você realmente precisa do caso simples, e é isso que isso fará.

Seu exemplo seria escrito:

>>> import datetime
>>> import nptime
>>> nptime.nptime(11, 34, 59) + datetime.timedelta(0, 3)
nptime(11, 35, 2)

nptimeherda de datetime.time, portanto, qualquer um desses métodos também deve ser usado.

Está disponível no PyPi como nptime("horário não pedante") ou no GitHub: https://github.com/tgs/nptime

rescdsk
fonte
6

Você não pode simplesmente adicionar um número datetimeporque não está claro qual unidade é usada: segundos, horas, semanas ...

timedeltaaula para manipulações com data e hora. datetimemenos datetimetimedelta, datetimemais timedeltadatetime, dois datetimeobjetos não podem ser adicionados, embora dois timedeltapossam.

Crie um timedeltaobjeto com quantos segundos você deseja adicionar e adicione-o ao datetimeobjeto:

>>> from datetime import datetime, timedelta
>>> t = datetime.now() + timedelta(seconds=3000)
>>> print(t)
datetime.datetime(2018, 1, 17, 21, 47, 13, 90244)

Há mesmo conceito em C ++: std::chrono::duration.

user2387567
fonte
4
SEMPRE adicione explicações, os recém-chegados podem não ter idéia do que você está fazendo aqui.
Gerhard Barnard
3

Por uma questão de completude, eis o caminho para fazê-lo arrow(datas e horários melhores para o Python):

sometime = arrow.now()
abitlater = sometime.shift(seconds=3)
Bart Van Loon
fonte
1

Tente adicionar um datetime.datetimea um datetime.timedelta. Se você deseja apenas a parte do tempo, pode chamar o time()método no datetime.datetimeobjeto resultante para obtê-lo.

Nick Johnson
fonte
0

Pergunta antiga, mas achei que iria jogar uma função que lida com fusos horários. As partes principais estão passando o atributo datetime.timedo objeto tzinfopara combinar e, em seguida, usando-o em timetz()vez da time()data e hora fictícia resultante. Esta resposta foi parcialmente inspirada pelas outras respostas aqui.

def add_timedelta_to_time(t, td):
    """Add a timedelta object to a time object using a dummy datetime.

    :param t: datetime.time object.
    :param td: datetime.timedelta object.

    :returns: datetime.time object, representing the result of t + td.

    NOTE: Using a gigantic td may result in an overflow. You've been
    warned.
    """
    # Create a dummy date object.
    dummy_date = date(year=100, month=1, day=1)

    # Combine the dummy date with the given time.
    dummy_datetime = datetime.combine(date=dummy_date, time=t, tzinfo=t.tzinfo)

    # Add the timedelta to the dummy datetime.
    new_datetime = dummy_datetime + td

    # Return the resulting time, including timezone information.
    return new_datetime.timetz()

E aqui está uma classe de caso de teste realmente simples (usando o built-in unittest):

import unittest
from datetime import datetime, timezone, timedelta, time

class AddTimedeltaToTimeTestCase(unittest.TestCase):
    """Test add_timedelta_to_time."""

    def test_wraps(self):
        t = time(hour=23, minute=59)
        td = timedelta(minutes=2)
        t_expected = time(hour=0, minute=1)
        t_actual = add_timedelta_to_time(t=t, td=td)
        self.assertEqual(t_expected, t_actual)

    def test_tz(self):
        t = time(hour=4, minute=16, tzinfo=timezone.utc)
        td = timedelta(hours=10, minutes=4)
        t_expected = time(hour=14, minute=20, tzinfo=timezone.utc)
        t_actual = add_timedelta_to_time(t=t, td=td)
        self.assertEqual(t_expected, t_actual)


if __name__ == '__main__':
    unittest.main()
blthayer
fonte