¿Cuál es la forma estándar de agregar N segundos a datetime.time en Python?

369

Dado un datetime.timevalor en Python, ¿hay una forma estándar de agregarle un número entero de segundos, de modo que 11:34:59+ 3 = 11:35:02, por ejemplo?

Estas ideas obvias no funcionan:

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

Al final he escrito funciones 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)

Sin embargo, no puedo evitar pensar que me falta una forma más fácil de hacer esto.

Relacionado

Paul Stephenson
fuente
problema relacionado con Python: soporte de datetime.time para '+' y '-'
jfs

Respuestas:

499

Puede usar datetimevariables completas con timedelta, y al proporcionar una fecha ficticia y luego usar timepara obtener el valor de tiempo.

Por ejemplo:

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

da como resultado los dos valores, separados por tres segundos:

11:34:59
11:35:02

También puedes optar por los más legibles

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

si eres tan inclinado


Si está buscando una función que pueda hacer esto, puede buscar el uso a addSecscontinuación:

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)

Esto produce:

 09:11:55.775695
 09:16:55
paxdiablo
fuente
66
Para evitar OverflowErrors, recomendaría usar una fecha ficticia diferente, por ejemplo, un par de años después: datetime (101,1,1,11,34,59). Si intenta restar una gran diferencia horaria de la fecha anterior, obtendrá un error "Error de desbordamiento: valor de fecha fuera de rango", ya que el año para un objeto de fecha y hora no puede ser menor a 1
pheelicks
2
@pheelicks, hecho, aunque un poco tarde, no exactamente tiempos de respuesta ágiles :-) Como tuve que corregir otro error en mi código, pensé que incorporaría su sugerencia al mismo tiempo.
paxdiablo
53

Como han dicho otros aquí, puede usar objetos de fecha y hora completos en todo:

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

Sin embargo, creo que vale la pena explicar por qué se requieren objetos de fecha y hora completos. Considere lo que sucedería si agregara 2 horas a las 11 p.m. ¿Cuál es el comportamiento correcto? ¿Una excepción, porque no puede tener un tiempo mayor a las 11:59 pm? ¿Debería envolverse de nuevo?

Los diferentes programadores esperarán cosas diferentes, por lo que cualquier resultado que elijan sorprendería a mucha gente. Peor aún, los programadores escribirían código que funcionó bien cuando lo probaron inicialmente, y luego lo romperían más tarde haciendo algo inesperado. Esto es muy malo, por lo que no puede agregar objetos timedelta a objetos de tiempo.

Eli Courtwright
fuente
22

Una pequeña cosa, podría agregar claridad para anular el valor predeterminado por segundos

>>> b = a + datetime.timedelta(seconds=3000)
>>> b
datetime.datetime(1, 1, 1, 12, 24, 59)
desmontado
fuente
44
Me gusta este. Agradable y claro con el argumento especificado.
Vigrond
12

Gracias a @Pax Diablo, @bvmou y @Arachnid por la sugerencia de usar fechas completas en todo momento. Si tengo que aceptar objetos datetime.time de una fuente externa, entonces esta parece ser una add_secs_to_time()función 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 detallado se puede comprimir en esta línea:

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

pero creo que de todos modos me gustaría resumirlo en una función para la claridad del código.

Paul Stephenson
fuente
Gracias - gran idea
Anupam
9

Si vale la pena agregar otro archivo / dependencia a su proyecto, acabo de escribir una pequeña clase que se extiende datetime.timecon la capacidad de hacer aritmética. Cuando pasas la medianoche, se ajusta a cero. Ahora, "¿Qué hora será, dentro de 24 horas?" Tiene muchos casos de esquina, incluido el horario de verano, los segundos bisiestos, los cambios históricos de la zona horaria, etc. Pero a veces realmente necesitas el caso simple, y eso es lo que hará.

Su ejemplo se escribiría:

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

nptimehereda de datetime.time, por lo que cualquiera de esos métodos también debería ser utilizable.

Está disponible en PyPi como nptime("tiempo no pedante"), o en GitHub: https://github.com/tgs/nptime

rescdsk
fuente
6

No puede simplemente agregar un número datetimeporque no está claro qué unidad se utiliza: segundos, horas, semanas ...

Hay timedeltaclase para manipulaciones con fecha y hora. datetimemenos datetimeda timedelta, datetimemás timedeltada datetime, dos datetimeobjetos no se pueden agregar aunque dos timedeltasí.

Cree un timedeltaobjeto con la cantidad de segundos que desea agregar y agréguelo al datetimeobjeto:

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

Hay mismo concepto en C ++: std::chrono::duration.

usuario2387567
fuente
44
SIEMPRE agregue explicaciones, los recién llegados pueden no tener idea de lo que está haciendo aquí.
Gerhard Barnard
3

Para completar, esta es la forma de hacerlo arrow(mejores fechas y horarios para Python):

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

Intenta agregar datetime.datetimea a datetime.timedelta. Si solo desea la porción de tiempo, puede llamar al time()método en el datetime.datetimeobjeto resultante para obtenerlo.

Nick Johnson
fuente
0

Antigua pregunta, pero pensé que incluiría una función que maneje zonas horarias. Las partes clave pasan el atributo datetime.timedel objeto tzinfoa combinar y luego se usan en timetz()lugar de time()en la fecha y hora ficticia resultante. Esta respuesta en parte inspirada por las otras respuestas aquí.

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

Y aquí hay una clase de caso de prueba realmente simple (usando incorporado 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
fuente