Який стандартний спосіб додати N секунд до datetime.time в Python?


369

Враховуючи datetime.timeзначення в Python, чи є стандартний спосіб додати до нього ціле число секунд, так що 11:34:59+ 3 = 11:35:02, наприклад?

Ці очевидні ідеї не спрацьовують:

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

Зрештою, я записав такі функції:

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)

Я не можу не думати, що я пропускаю простіший спосіб зробити це.

Пов'язані


пов'язаний випуск Python: підтримка datetime.time для '+' та '-'
jfs

Відповіді:


499

Ви можете використовувати повні datetimeзмінні з timedeltaі надавати фіктивну дату, а потім timeпросто отримувати значення часу.

Наприклад:

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

виходить два значення, три секунди один від одного:

11:34:59
11:35:02

Ви також можете вибрати більше для читання

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

якщо ти так схильний.


Якщо ви користуєтеся функцією, яка може це зробити, ви можете скористатися addSecsнижче:

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)

Цей результат:

 09:11:55.775695
 09:16:55

6
Щоб уникнути OverflowErrors, я рекомендую використовувати іншу фіктивну дату, наприклад, через пару років: datetime (101,1,1,11,34,59). Якщо ви спробуєте відняти велику тиммеделю від вищевказаної дати, ви отримаєте помилку "OverflowError: значення дати поза діапазоном", оскільки рік для об'єкта дата не може бути меншим за 1
фееліки

2
@pheelicks, зроблено, хоч і трохи пізно, не дуже спритний час відгуку :-) Оскільки мені довелося виправити ще одну помилку в своєму коді, я подумав, що одночасно буду включати вашу пропозицію.
paxdiablo

53

Як вже тут заявили інші, ви можете просто використовувати об'єкти повних дат протягом усього часу:

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

Однак я вважаю, що варто пояснити, чому потрібні повні об'єкти дати. Поміркуйте, що буде, якби я додав 2 години до 23 вечора. Яка правильна поведінка? Виняток, адже ви не можете мати час, більший за 23:59? Чи повинен він обернутися назад?

Різні програмісти очікують різних речей, тому який би результат вони не вибрали, здивує багато людей. Що ще гірше, програмісти напишуть код, який працював чудово, коли вони його тестували спочатку, а потім перервали його пізніше, зробивши щось несподіване. Це дуже погано, через що вам не дозволяється додавати об'єкти timedelta до часових об'єктів.


22

Одна дрібниця, може додати чіткість, щоб замінити значення за замовчуванням на секунди

>>> b = a + datetime.timedelta(seconds=3000)
>>> b
datetime.datetime(1, 1, 1, 12, 24, 59)

4
Мені це подобається. Приємно і ясно з вказаним аргументом.
Vigrond

12

Дякуємо @Pax Diablo, @bvmou та @Arachnid за пропозицію використовувати повні дати в цілому. Якщо мені доведеться приймати об'єкти datetime.time з зовнішнього джерела, це здається альтернативною add_secs_to_time()функцією:

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

Цей багатослівний код можна стиснути до цього одноклапника:

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

але я думаю, я хотів би все-таки перетворити це на функцію для ясності коду.


Дякую - чудова ідея
Анупам

9

Якщо варто додати ще один файл / залежність до свого проекту, я щойно написав крихітний маленький клас, який розширює datetime.timeможливість арифметики. Коли минаєш півночі, вона обертається навколо нуля. Зараз "Який час буде, 24 години відтепер" є безліч кутових випадків, включаючи час літнього часу, стрибкові секунди, історичні зміни часового поясу тощо. Але іноді вам справді потрібен простий випадок, і саме це буде робити.

Ваш приклад буде написаний:

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

nptimeуспадковує від datetime.time, тому будь-який із цих методів теж повинен бути корисним.

Він доступний у PyPi як nptime("не педантичний час") або в GitHub: https://github.com/tgs/nptime


6

Ви не можете просто додати число, datetimeтому що незрозуміло, яку одиницю використовують: секунди, години, тижні ...

Існує timedeltaклас для маніпуляцій з датою та часом. datetimeмінус datetimeдає timedelta, datetimeплюс timedeltaдає datetime, два datetimeоб’єкти не можна додати, хоча два timedeltaможуть.

Створіть timedeltaоб’єкт за скільки секунд ви хочете додати та додайте його до datetimeоб’єкта:

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

Існує так само поняття в C ++: std::chrono::duration.


4
будь-коли додайте пояснення, новачки можуть не мати поняття, що ви тут робите.
Герхард Барнард

3

Для повноти, ось як це зробити arrow(кращі дати та часи для Python):

sometime = arrow.now()
abitlater = sometime.shift(seconds=3)

1

Спробуйте додати datetime.datetimeдо datetime.timedelta. Якщо ви хочете лише часовий проміжок, ви можете викликати time()метод на результуючому datetime.datetimeоб'єкті, щоб отримати його.


0

Старе питання, але я подумав, що буду кидати функцію, яка обробляє часові зони. Ключові частини передають атрибут datetime.timeоб'єкта tzinfoв комбінацію, а потім використовують timetz()замість time()отриманого фіктивного часу. Ця відповідь частково натхнена іншими відповідями тут.

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

А ось дійсно простий клас тестового випадку (за допомогою вбудованого 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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.