Apa cara standar untuk menambahkan N detik ke datetime.time dengan Python?

369

Diberi datetime.timenilai dalam Python, apakah ada cara standar untuk menambahkan bilangan bulat detik ke dalamnya, sehingga 11:34:59+ 3 = 11:35:02, misalnya?

Gagasan yang jelas ini tidak berhasil:

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

Pada akhirnya saya memiliki fungsi tertulis seperti ini:

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)

Saya tidak dapat berhenti berpikir bahwa saya kehilangan cara yang lebih mudah untuk melakukan ini.

Terkait

Paul Stephenson
sumber
terkait masalah Python: dukungan datetime.time untuk '+' dan '-'
jfs

Jawaban:

499

Anda dapat menggunakan datetimevariabel penuh dengan timedelta, dan dengan memberikan tanggal dummy lalu gunakan timeuntuk hanya mendapatkan nilai waktu.

Sebagai contoh:

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

menghasilkan dua nilai, terpisah tiga detik:

11:34:59
11:35:02

Anda juga dapat memilih yang lebih mudah dibaca

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

jika Anda cenderung.


Jika Anda mencari fungsi yang dapat melakukan ini, Anda dapat melihat menggunakan di addSecsbawah ini:

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)

Output ini:

 09:11:55.775695
 09:16:55
paxdiablo
sumber
6
Untuk menghindari OverflowErrors, saya akan merekomendasikan menggunakan tanggal dummy yang berbeda, misalnya beberapa tahun kemudian: datetime (101,1,1,11,34,59). Jika Anda mencoba mengurangi timedelta besar dari tanggal di atas, Anda akan mendapatkan kesalahan "OverflowError: nilai tanggal di luar kisaran" karena tahun untuk objek waktu tidak boleh lebih kecil dari 1
pheelicks
2
@pheelicks, selesai, walaupun sedikit terlambat, waktu respons yang tidak terlalu gesit :-) Karena saya harus memperbaiki bug lain dalam kode saya, saya pikir saya akan memasukkan saran Anda pada saat yang sama.
paxdiablo
53

Seperti yang dinyatakan orang lain di sini, Anda bisa menggunakan objek datetime penuh di seluruh:

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

Namun, saya pikir itu layak menjelaskan mengapa objek datetime penuh diperlukan. Pertimbangkan apa yang akan terjadi jika saya menambahkan 2 jam hingga 11 malam. Apa perilaku yang benar? Pengecualian, karena Anda tidak dapat memiliki waktu lebih dari 11:59 malam? Haruskah membungkus kembali?

Pemrogram yang berbeda akan mengharapkan hal yang berbeda, sehingga hasil mana pun yang mereka pilih akan mengejutkan banyak orang. Lebih buruk lagi, programmer akan menulis kode yang bekerja dengan baik ketika mereka mengujinya pada awalnya, dan kemudian istirahat dengan melakukan sesuatu yang tidak terduga. Ini sangat buruk, itulah sebabnya Anda tidak diizinkan menambahkan objek timedelta ke objek waktu.

Eli Courtwright
sumber
22

Satu hal kecil, mungkin menambah kejelasan untuk mengganti nilai default untuk detik

>>> b = a + datetime.timedelta(seconds=3000)
>>> b
datetime.datetime(1, 1, 1, 12, 24, 59)
lepas
sumber
4
Saya suka yang ini. Bagus dan jelas dengan argumen yang ditentukan.
Vigrond
12

Terima kasih kepada @Pax Diablo, @bvmou, dan @Arachnid untuk saran penggunaan datetime penuh. Jika saya harus menerima objek datetime.time dari sumber eksternal, maka ini tampaknya menjadi add_secs_to_time()fungsi alternatif :

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

Kode verbose ini dapat dikompres ke lapisan satu ini:

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

tapi saya pikir saya ingin membungkusnya dalam suatu fungsi untuk kejelasan kode.

Paul Stephenson
sumber
Terima kasih - ide bagus
Anupam
9

Jika perlu menambahkan file lain / ketergantungan pada proyek Anda, saya baru saja menulis sebuah kelas kecil yang diperluas datetime.timedengan kemampuan untuk melakukan aritmatika. Ketika Anda melewati tengah malam, itu membungkus sekitar nol. Sekarang, "Jam berapa sekarang, 24 jam dari sekarang" memiliki banyak kotak sudut, termasuk waktu musim panas, detik kabisat, perubahan zona waktu historis, dan sebagainya. Tetapi kadang-kadang Anda benar-benar membutuhkan kasing sederhana, dan itulah yang akan dilakukan.

Contoh Anda akan ditulis:

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

nptimemewarisi dari datetime.time, jadi salah satu dari metode tersebut harus dapat digunakan juga.

Ini tersedia dari PyPi sebagai nptime("waktu non-pedantic"), atau di GitHub: https://github.com/tgs/nptime

rescdsk
sumber
6

Anda tidak dapat hanya menambahkan nomor datetimekarena tidak jelas satuan apa yang digunakan: detik, jam, minggu ...

Ada timedeltakelas untuk manipulasi dengan tanggal dan waktu. datetimeminus datetimememberi timedelta, datetimeditambah timedeltamemberi datetime, dua datetimeobjek tidak dapat ditambahkan meskipun dua timedeltabisa.

Buat timedeltaobjek dengan berapa detik Anda ingin menambahkan dan menambahkannya ke datetimeobjek:

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

Ada konsep yang sama di C ++: std::chrono::duration.

pengguna2387567
sumber
4
tolong SELALU tambahkan penjelasan, pendatang baru mungkin tidak tahu apa yang Anda lakukan di sini.
Gerhard Barnard
3

Demi kelengkapan, berikut cara melakukannya arrow(tanggal dan waktu yang lebih baik untuk Python):

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

Coba tambahkan a datetime.datetimeke datetime.timedelta. Jika Anda hanya menginginkan porsi waktu, Anda dapat memanggil time()metode pada datetime.datetimeobjek yang dihasilkan untuk mendapatkannya.

Nick Johnson
sumber
0

Pertanyaan lama, tapi saya pikir saya akan melempar fungsi yang menangani zona waktu. Bagian-bagian kunci yang melewati datetime.timeobjek tzinfoatribut dalam menggabungkan, dan kemudian menggunakan timetz()bukannya time()pada datetime boneka yang dihasilkan. Jawaban ini sebagian diinspirasi oleh jawaban lain di sini.

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

Dan inilah kelas case test yang sangat sederhana (menggunakan 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
sumber