Jaki jest standardowy sposób dodawania N sekund do datetime.time w Pythonie?

369

Biorąc pod uwagę datetime.timewartość w Pythonie, czy istnieje standardowy sposób dodania do niej liczby całkowitej w sekundach, tak aby na przykład 11:34:59+ 3 = 11:35:02?

Te oczywiste pomysły nie działają:

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

Na koniec napisałem takie funkcje:

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)

Nie mogę jednak nie myśleć, że brakuje mi łatwiejszego sposobu na zrobienie tego.

Związane z

Paul Stephenson
źródło
powiązany problem w Pythonie: obsługa datetime.time dla „+” i „-”
jfs

Odpowiedzi:

499

Możesz używać pełnych datetimezmiennych z timedeltai, podając fikcyjną datę, a następnie używając, timeaby uzyskać wartość czasu.

Na przykład:

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

daje dwie wartości w odstępie trzech sekund:

11:34:59
11:35:02

Możesz również wybrać bardziej czytelny

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

jeśli jesteś taki skłonny.


Jeśli szukasz funkcji, która może to zrobić, możesz skorzystać z addSecsponiższych:

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)

To daje:

 09:11:55.775695
 09:16:55
paxdiablo
źródło
6
Aby uniknąć błędów przepełnienia, zaleciłbym użycie innej fałszywej daty, np. Kilka lat później: datetime (101,1,1,11,34,59). Jeśli spróbujesz odjąć dużą timedelta od powyższej daty, pojawi się błąd „OverflowError: date date out of range”, ponieważ rok dla obiektu datetime nie może być mniejszy niż 1
pheelicks 29.11.11
2
@ pheelicks, gotowe, choć trochę spóźnione, niezbyt zwinne czasy odpowiedzi :-) Ponieważ musiałem naprawić kolejny błąd w moim kodzie, pomyślałem, że uwzględnię twoją sugestię w tym samym czasie.
paxdiablo
53

Jak powiedzieli inni tutaj, możesz po prostu używać pełnych obiektów data-godzina w:

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

Myślę jednak, że warto wyjaśnić, dlaczego wymagane są pełne obiekty typu data-godzina. Zastanów się, co by się stało, gdybym dodał 2 godziny do 23:00. Jakie jest prawidłowe zachowanie? Wyjątek, ponieważ nie możesz mieć czasu dłuższego niż 23:59? Czy powinien się zawinąć?

Różni programiści będą oczekiwać różnych rzeczy, więc każdy wybrany przez nich wynik zaskoczy wielu ludzi. Co gorsza, programiści pisali kod, który działał dobrze, gdy testowali go początkowo, a następnie mieli zepsuć się, robiąc coś nieoczekiwanego. Jest to bardzo złe, dlatego nie wolno dodawać obiektów timedelta do obiektów czasu.

Eli Courtwright
źródło
22

Jedna drobiazg, może dodać jasności, aby zastąpić domyślną wartość na kilka sekund

>>> b = a + datetime.timedelta(seconds=3000)
>>> b
datetime.datetime(1, 1, 1, 12, 24, 59)
nieoprawny
źródło
4
Ten mi się podoba. Ładne i jasne z podanym argumentem.
Vigrond
12

Dzięki @Pax Diablo, @bvmou i @Arachnid za sugestię korzystania z pełnych czasów danych w całym tekście. Jeśli muszę zaakceptować obiekty datetime.time z zewnętrznego źródła, wydaje się, że jest to add_secs_to_time()funkcja alternatywna :

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

Ten pełny kod można skompresować do tego jednowierszowego:

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

ale myślę, że i tak chciałbym to zawrzeć w funkcji zapewniającej przejrzystość kodu.

Paul Stephenson
źródło
Dzięki - świetny pomysł
Anupam
9

Jeśli warto dodać kolejny plik / zależność do twojego projektu, właśnie napisałem małą małą klasę, która rozszerza się datetime.timeo umiejętność wykonywania arytmetyki. Kiedy minie północ, zawija się wokół zera. Teraz, „Która będzie godzina, za 24 godziny” ma wiele narożnych skrzynek, w tym czas letni, sekundy przestępne, historyczne zmiany stref czasowych i tak dalej. Ale czasami naprawdę potrzebujesz prostej skrzynki i tak właśnie się stanie.

Twój przykład zostałby napisany:

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

nptimedziedziczy po datetime.time, więc każda z tych metod również powinna być użyteczna.

Jest dostępny w PyPi jako nptime(„czas nie pedantyczny”) lub w GitHub: https://github.com/tgs/nptime

rescdsk
źródło
6

Nie można po prostu dodać liczby, datetimeponieważ nie jest jasne, która jednostka jest używana: sekundy, godziny, tygodnie ...

Istnieje timedeltaklasa manipulacji z datą i godziną. datetimeminus datetimedaje timedelta, datetimeplus timedeltadaje datetime, datetimenie można dodać dwóch obiektów, chociaż dwa timedeltamogą.

Utwórz timedeltaobiekt za pomocą ilu sekund chcesz dodać i dodaj go do datetimeobiektu:

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

Jest sama koncepcja w C ++ std::chrono::duration.

użytkownik2387567
źródło
4
proszę ZAWSZE dodawać wyjaśnienia, nowicjusze mogą nie mieć pojęcia, co tu robicie.
Gerhard Barnard
3

Dla kompletności, oto sposób, aby to zrobić arrow(lepsze daty i godziny dla Pythona):

sometime = arrow.now()
abitlater = sometime.shift(seconds=3)
Bart Van Loon
źródło
1

Spróbuj dodać a datetime.datetimedo datetime.timedelta. Jeśli chcesz tylko przedziału czasu, możesz wywołać time()metodę na wynikowym datetime.datetimeobiekcie, aby go uzyskać.

Nick Johnson
źródło
0

Stare pytanie, ale pomyślałem, że wprowadzę funkcję obsługującą strefy czasowe. Kluczowe części przekazują atrybut datetime.timeobiektu tzinfodo łączenia, a następnie używają timetz()zamiast time()wynikowego atrapy danych. Ta odpowiedź częściowo zainspirowana innymi odpowiedziami tutaj.

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

A oto bardzo prosta klasa przypadków testowych (przy użyciu wbudowanego 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
źródło