Bagaimana cara mem-parsing tanggal yang diformat ISO 8601?

643

Saya perlu mengurai string RFC 3339 seperti "2008-09-03T20:56:35.450686Z"ke datetimetipe Python .

Saya telah menemukan strptimedi perpustakaan standar Python, tetapi tidak terlalu nyaman.

Apa cara terbaik untuk melakukan ini?

Alexander Artemenko
sumber
3
Agar jelas: ISO 8601 adalah standar utama. RFC 3339 adalah "profil" memproklamirkan diri dari ISO 8601 yang membuat beberapa aturan ISO 8601 mengabaikan secara tidak bijaksana .
Basil Bourque
3
Jangan lewatkan solusi python3.7 + di bawah ini untuk membalikkan isoformat ()
Brad M
2
Pertanyaan ini seharusnya tidak ditutup sebagai penipuan ke posting yang ditautkan. Karena yang satu ini meminta untuk mengurai string waktu ISO 8601 (yang tidak didukung secara native oleh python sebelum ke 3.7) dan yang lainnya adalah memformat objek datetime menjadi string zaman menggunakan metode usang.
abccd

Jawaban:

462

Paket python-dateutil dapat mengurai tidak hanya string datetime RFC 3339 seperti yang ada dalam pertanyaan, tetapi juga string tanggal dan waktu ISO 8601 lainnya yang tidak mematuhi RFC 3339 (seperti yang tanpa offset UTC, atau yang mewakili hanya kencan).

>>> import dateutil.parser
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686Z') # RFC 3339 format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
>>> dateutil.parser.isoparse('2008-09-03T20:56:35.450686') # ISO 8601 extended format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903T205635.450686') # ISO 8601 basic format
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
>>> dateutil.parser.isoparse('20080903') # ISO 8601 basic format, date only
datetime.datetime(2008, 9, 3, 0, 0)

Catatan yang dateutil.parser.isoparsemungkin lebih ketat daripada yang lebih rumit dateutil.parser.parse, tetapi keduanya cukup memaafkan dan akan berusaha menafsirkan string yang Anda lewati. Jika Anda ingin menghilangkan kemungkinan salah baca, Anda perlu menggunakan sesuatu yang lebih ketat daripada salah satu dari ini. fungsi.

Nama Pypi adalah python-dateutil, bukan dateutil(terima kasih code3monk3y ):

pip install python-dateutil

Jika Anda menggunakan Python 3.7, lihat jawaban ini tentang datetime.datetime.fromisoformat.

Flimm
sumber
75
Untuk malas, itu diinstal melalui python-dateutiltidak dateutil, sehingga: pip install python-dateutil.
cod3monk3y
29
Berhati-hatilah karena dateutil.parserini sengaja diretas: ia mencoba menebak format dan membuat asumsi yang tak terhindarkan (hanya dapat disesuaikan dengan tangan) dalam kasus yang ambigu. Jadi HANYA menggunakannya jika Anda perlu mem-parsing input dari format yang tidak dikenal dan boleh saja mentolerir kesalahan membaca sesekali.
ivan_pozdeev
2
Sepakat. Contohnya adalah melewati "tanggal" 9999. Ini akan mengembalikan sama dengan datetime (9999, bulan berjalan, hari ini). Bukan tanggal yang valid dalam pandangan saya.
timbo
1
@ivan_pozdeev paket apa yang akan Anda rekomendasikan untuk parsing yang tidak menebak?
bgusach
2
@ivan_pozdeev ada pembaruan untuk modul yang bertuliskan iso8601 tanggal: dateutil.readthedocs.io/en/stable/…
theEpsilon
198

Baru dalam Python 3.7+


The datetimeperpustakaan standar diperkenalkan fungsi untuk pembalik datetime.isoformat().

classmethod datetime.fromisoformat(date_string):

Kembalikan yang datetimesesuai ke date_stringdalam salah satu format yang dipancarkan oleh date.isoformat()dan datetime.isoformat().

Secara khusus, fungsi ini mendukung string dalam format:

YYYY-MM-DD[*HH[:MM[:SS[.mmm[mmm]]]][+HH:MM[:SS[.ffffff]]]]

di mana *bisa cocok dengan satu karakter.

Perhatian : Ini tidak mendukung parsing string ISO 8601 yang sewenang-wenang - ini hanya dimaksudkan sebagai operasi terbalik dari datetime.isoformat().

Contoh penggunaan:

from datetime import datetime

date = datetime.fromisoformat('2017-01-01T12:30:59.000000')
abccd
sumber
6
Itu aneh. Karena a datetimedapat berisi a tzinfo, dan dengan demikian menampilkan zona waktu, tetapi datetime.fromisoformat()tidak menguraikan tzinfo? sepertinya bug ..
Hendy Irawan
20
Jangan lewatkan catatan itu dalam dokumentasi, ini tidak menerima semua string ISO 8601 yang valid, hanya yang dihasilkan oleh isoformat. Itu tidak menerima contoh dalam pertanyaan "2008-09-03T20:56:35.450686Z"karena trailing Z, tetapi itu menerima "2008-09-03T20:56:35.450686".
Flimm
26
Untuk mendukung Zskrip input dengan benar dapat dimodifikasi date_string.replace("Z", "+00:00").
Jox
7
Perhatikan bahwa untuk detik ini hanya menangani 0, 3 atau 6 tempat desimal. Jika data input memiliki tempat desimal 1, 2, 4, 5, 7 atau lebih, penguraian akan gagal!
Felk
1
@JDOaktown Contoh ini menggunakan pustaka datetime Python asli, bukan parser dateutil. Ini sebenarnya akan gagal jika tempat desimal tidak 0, 3, atau 6 dengan pendekatan ini.
abccd
174

Catatan dalam Python 2.6+ dan Py3K, karakter% f menangkap microseconds.

>>> datetime.datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")

Lihat masalah di sini

sethbc
sumber
4
Catatan - jika menggunakan datetimes Naif - Saya pikir Anda tidak mendapatkan TZ sama sekali - Z mungkin tidak cocok dengan apa pun.
Danny Staple
24
Jawaban ini (dalam bentuk yang diedit saat ini) bergantung pada pengkodean keras offset UTC tertentu (yaitu "Z", yang berarti +00: 00) ke dalam format string. Ini adalah ide yang buruk karena itu akan gagal untuk mem-parsing setiap datetime dengan offset UTC yang berbeda dan menimbulkan pengecualian. Lihat jawaban saya yang menjelaskan bagaimana parsing RFC 3339 strptimesebenarnya tidak mungkin.
Mark Amery
1
dalam kasus saya% f menangkap mikrodetik daripada Z, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') jadi ini triknya
ashim888
Apakah Py3K berarti Python 3000?!?
Robino
2
@Robino IIRC, "Python 3000" adalah nama lama untuk apa yang sekarang dikenal sebagai Python 3.
Akun Throw Away
161

Beberapa jawaban di sini menyarankandatetime.datetime.strptime untuk menggunakan parse data RFC 3339 atau ISO 8601 dengan zona waktu, seperti yang diperlihatkan dalam pertanyaan:

2008-09-03T20:56:35.450686Z

Ini ide yang buruk.

Dengan asumsi bahwa Anda ingin mendukung format RFC 3339 lengkap, termasuk dukungan untuk offset UTC selain nol, maka kode yang disarankan oleh jawaban ini tidak berfungsi. Memang, itu tidak bisa bekerja, karena parsing RFC 3339 menggunakan sintaks strptimetidak mungkin. String format yang digunakan oleh modul datetime Python tidak mampu menggambarkan sintaksis RFC 3339.

Masalahnya adalah offset UTC. The RFC 3339 Tanggal Internet / Waktu Format mengharuskan setiap tanggal-waktu termasuk UTC offset, dan bahwa mereka offset baik dapat Z(singkatan dari "waktu Zulu") atau dalam +HH:MMatau -HH:MMFormat, seperti +05:00atau -10:30.

Oleh karena itu, ini semua data RFC 3339 valid:

  • 2008-09-03T20:56:35.450686Z
  • 2008-09-03T20:56:35.450686+05:00
  • 2008-09-03T20:56:35.450686-10:30

Sayangnya, string format digunakan oleh strptimedan strftimetidak memiliki arahan yang sesuai dengan offset UTC dalam format RFC 3339. Daftar lengkap arahan yang mereka dukung dapat ditemukan di https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior , dan satu-satunya arahan offset UTC yang termasuk dalam daftar adalah %z:

% z

UTC diimbangi dalam bentuk + HHMM atau -HHMM (string kosong jika objeknya naif).

Contoh: (kosong), +0000, -0400, +1030

Ini tidak cocok dengan format offset RFC 3339, dan memang jika kita mencoba menggunakan %zstring format dan menguraikan tanggal RFC 3339, kita akan gagal:

>>> from datetime import datetime
>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
    tt, fraction = _strptime(data_string, format)
  File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
    (data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686Z' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'
>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%f%z")
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
    tt, fraction = _strptime(data_string, format)
  File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
    (data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686+05:00' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'

(Sebenarnya, di atas hanya apa yang akan Anda lihat di Python 3. Dalam Python 2 kita akan gagal karena alasan yang lebih sederhana, yaitu bahwa strptimetidak menerapkan %zarahan sama sekali di Python 2. )

Beberapa jawaban di sini yang merekomendasikan strptimesemua bekerja di sekitar ini dengan memasukkan literal Zdalam string format mereka, yang cocok dengan Zdari string datetime contoh penanya pertanyaan (dan membuangnya, menghasilkan datetimeobjek tanpa zona waktu):

>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)

Karena ini membuang informasi zona waktu yang termasuk dalam string datetime asli, patut dipertanyakan apakah kita harus menganggap bahkan hasil ini sebagai benar. Tetapi yang lebih penting, karena pendekatan ini melibatkan pengodean keras offset UTC tertentu ke dalam format string , itu akan mencekik saat mencoba mem-parse setiap waktu RFC 3339 dengan offset UTC yang berbeda:

>>> datetime.strptime("2008-09-03T20:56:35.450686+05:00", "%Y-%m-%dT%H:%M:%S.%fZ")
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib/python3.4/_strptime.py", line 500, in _strptime_datetime
    tt, fraction = _strptime(data_string, format)
  File "/usr/lib/python3.4/_strptime.py", line 337, in _strptime
    (data_string, format))
ValueError: time data '2008-09-03T20:56:35.450686+05:00' does not match format '%Y-%m-%dT%H:%M:%S.%fZ'

Kecuali Anda yakin bahwa Anda hanya perlu mendukung data RFC 3339 dalam waktu Zulu, dan bukan yang dengan offset zona waktu lainnya, jangan gunakan strptime. Gunakan salah satu dari banyak pendekatan lain yang dijelaskan dalam jawaban di sini sebagai gantinya.

Mark Amery
sumber
79
Sangat membingungkan mengapa strptime tidak memiliki arahan untuk info zona waktu format ISO, dan mengapa strptime tidak dapat diuraikan. Luar biasa.
Csaba Toth
2
@CsabaToth Sepenuhnya setuju - jika saya punya waktu untuk membunuh, mungkin saya akan mencoba menambahkannya ke dalam bahasa. Atau Anda bisa melakukannya, jika Anda memang cenderung - Saya melihat Anda memiliki pengalaman C, tidak seperti saya.
Mark Amery
1
@CsabaToth - Kenapa luar biasa? Ini bekerja cukup baik untuk kebanyakan orang, atau mereka menemukan solusi yang cukup mudah. Jika Anda memerlukan fitur, itu adalah opensource dan Anda dapat menambahkannya. Atau bayar seseorang untuk melakukannya untuk Anda. Mengapa seseorang harus menyumbangkan waktu luangnya sendiri untuk menyelesaikan masalah spesifik Anda? Biarkan sumber bersamamu.
Peter M. - singkatan dari Monica
2
@PeterMasiar Luar Biasa karena biasanya orang menemukan bahwa hal-hal dalam python telah dilaksanakan dengan penuh pertimbangan dan sepenuhnya. Kami telah dimanjakan oleh perhatian terhadap detail dan jadi ketika kami menemukan sesuatu dalam bahasa yang "unpythonic" kami mengeluarkan mainan kami, karena saya akan melakukannya sekarang. Whaaaaaaaaaa Whaa wahaaaaa :-(
Robino
2
strptime()dalam Python 3.7 sekarang mendukung semua yang digambarkan sebagai tidak mungkin dalam jawaban ini ('Z' literal dan ':' dalam offset zona waktu). Sayangnya, ada kasus sudut lain yang membuat RFC 3339 pada dasarnya tidak kompatibel dengan ISO 8601, yaitu, yang pertama memungkinkan offset zona waktu negatif negatif -00: 00 dan kemudian tidak.
SergiyKolesnikov
75

Coba modul iso8601 ; itu persis seperti ini.

Ada beberapa pilihan lain disebutkan pada WorkingWithTime halaman pada python.org wiki.

Nicholas Riley
sumber
Sesederhanaiso8601.parse_date("2008-09-03T20:56:35.450686Z")
Pakman
3
Pertanyaannya bukan "bagaimana cara menguraikan tanggal ISO 8601", melainkan "bagaimana cara menguraikan format tanggal yang tepat ini."
Nicholas Riley
3
@tiktak OP bertanya "Saya perlu mengurai string seperti X" dan balasan saya untuk itu, setelah mencoba kedua perpustakaan, adalah menggunakan yang lain, karena iso8601 masih memiliki masalah penting yang masih terbuka. Keterlibatan atau kekurangan saya dalam proyek semacam itu sama sekali tidak terkait dengan jawabannya.
Tobia
2
Perlu diketahui bahwa versi pip iso8601 belum diperbarui sejak 2007 dan memiliki beberapa bug serius yang luar biasa. Saya sarankan menerapkan beberapa kritik dari tambalan sendiri atau menemukan salah satu dari banyak garpu github yang telah melakukannya github.com/keithhackbarth/pyiso8601-strict
keithhackbarth
6
iso8601 , alias pyiso8601 , telah diperbarui baru-baru ini sebagai Februari 2014. Versi terbaru mendukung serangkaian yang lebih luas dari string ISO 8601. Saya telah menggunakan efek yang baik di beberapa proyek saya.
Dave Hein
34
impor ulang, datetime
s = "2008-09-03T20: 56: 35.450686Z"
d = datetime.datetime (* peta (int, re.split ('[^ \ d]', s) [: - 1]))
Ted
sumber
73
Saya tidak setuju, ini praktis tidak bisa dibaca dan sejauh yang saya tahu tidak memperhitungkan Zulu (Z) yang membuat data ini naif walaupun data zona waktu disediakan.
umbrae
14
Saya merasa itu cukup mudah dibaca. Bahkan, itu mungkin cara termudah dan paling berkinerja untuk melakukan konversi tanpa menginstal paket tambahan.
Tobia
2
Ini sama dengan d = datetime.datetime (* map (int, re.split ('\ D', s) [: - 1])) saya kira.
Xuan
4
variasi:datetime.datetime(*map(int, re.findall('\d+', s))
jfs
3
Ini menghasilkan objek datetime naif tanpa zona waktu, kan? Jadi bit UTC hilang dalam terjemahan?
w00t
32

Apa kesalahan sebenarnya yang Anda dapatkan? Apakah ini seperti yang berikut ini?

>>> datetime.datetime.strptime("2008-08-12T12:20:30.656234Z", "%Y-%m-%dT%H:%M:%S.Z")
ValueError: time data did not match format:  data=2008-08-12T12:20:30.656234Z  fmt=%Y-%m-%dT%H:%M:%S.Z

Jika ya, Anda dapat membagi string input Anda pada ".", Dan kemudian menambahkan mikrodetik ke datetime yang Anda dapatkan.

Coba ini:

>>> def gt(dt_str):
        dt, _, us= dt_str.partition(".")
        dt= datetime.datetime.strptime(dt, "%Y-%m-%dT%H:%M:%S")
        us= int(us.rstrip("Z"), 10)
        return dt + datetime.timedelta(microseconds=us)

>>> gt("2008-08-12T12:20:30.656234Z")
datetime.datetime(2008, 8, 12, 12, 20, 30, 656234)
tzot
sumber
10
Anda tidak bisa hanya menghapus .Z karena itu berarti zona waktu dan bisa berbeda. Saya perlu mengonversi tanggal ke zona waktu UTC.
Alexander Artemenko
Objek datetime polos tidak memiliki konsep zona waktu. Jika semua waktu Anda berakhir dengan "Z", semua waktu yang Anda dapatkan adalah UTC (waktu Zulu).
tzot
jika zona waktu adalah sesuatu selain ""atau "Z", maka itu harus diimbangi dalam jam / menit, yang dapat langsung ditambahkan ke / dikurangi dari objek datetime. Anda bisa membuat subclass tzinfo untuk menanganinya, tapi itu mungkin tidak disarankan.
SingleNegationElimination
8
Selain itu, "% f" adalah penentu mikrodetik, sehingga string strptime (naif-waktu) terlihat seperti: "% Y-% m-% dT% H:% M:% S.% f".
quodlibetor
1
Ini akan menimbulkan pengecualian jika string datetime yang diberikan memiliki offset UTC selain dari "Z". Itu tidak mendukung seluruh format RFC 3339 dan merupakan jawaban yang lebih rendah untuk orang lain yang menangani offset UTC dengan benar.
Mark Amery
25

Mulai dari Python 3.7, strptime mendukung pembatas titik dua dalam offset UTC ( sumber ). Jadi Anda dapat menggunakan:

import datetime
datetime.datetime.strptime('2018-01-31T09:24:31.488670+00:00', '%Y-%m-%dT%H:%M:%S.%f%z')

EDIT:

Seperti yang ditunjukkan oleh Martijn, jika Anda membuat objek datetime menggunakan isoformat (), Anda bisa menggunakan datetime.fromisoformat ()

Andreas Profous
sumber
4
Tapi dalam 3,7, Anda juga memiliki datetime.fromisoformat()yang menangani string seperti masukan Anda secara otomatis: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Martijn Pieters
2
Poin yang bagus. Saya setuju, saya sarankan untuk menggunakan datetime.fromisoformat()dandatetime.isoformat()
Andreas Profous
19

Di hari-hari ini, Arrow juga dapat digunakan sebagai solusi pihak ketiga:

>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
Ilker Kesen
sumber
6
Arrow tidak mendukung ISO8601 dengan benar: github.com/crsmithdev/arrow/issues/291
kemas
1
Cukup gunakan python-dateutil - panah membutuhkan python-dateutil.
danizen
Arrow sekarang mendukung ISO8601. Masalah yang dirujuk sekarang ditutup.
Altus
18

Cukup gunakan python-dateutilmodul:

>>> import dateutil.parser as dp
>>> t = '1984-06-02T19:05:00.000Z'
>>> parsed_t = dp.parse(t)
>>> print(parsed_t)
datetime.datetime(1984, 6, 2, 19, 5, tzinfo=tzutc())

Dokumentasi

Blairg23
sumber
1
Bukankah ini jawaban tepat @Flimms di atas?
leo
1
Di mana Anda melihatnya parsing dalam hitungan detik? Saya menemukan artikel ini dengan mencoba mendapatkan waktu, jadi saya pikir orang lain juga akan melakukannya.
Blairg23
1
Ini bukan UTC di sistem saya. Sebaliknya, output dalam detik adalah waktu unix, seolah-olah tanggalnya di zona waktu lokal saya.
Elliot
1
Jawaban ini buggy dan tidak boleh diterima. Mungkin seluruh pertanyaan harus ditandai sebagai duplikat dari stackoverflow.com/questions/11743019/…
tripleee
@ tripleee Sebenarnya saya baru saja memeriksa kode dan tampaknya mengembalikan jawaban yang benar: 455051100(diperiksa di epochconverter.com ) ,,, kecuali saya kehilangan sesuatu?
Blairg23
13

Jika Anda tidak ingin menggunakan dateutil, Anda dapat mencoba fungsi ini:

def from_utc(utcTime,fmt="%Y-%m-%dT%H:%M:%S.%fZ"):
    """
    Convert UTC time string to time.struct_time
    """
    # change datetime.datetime to time, return time.struct_time type
    return datetime.datetime.strptime(utcTime, fmt)

Uji:

from_utc("2007-03-04T21:08:12.123Z")

Hasil:

datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)
ahli sihir
sumber
5
Jawaban ini bergantung pada pengodean offset UTC tertentu (yaitu "Z", yang berarti +00: 00) ke dalam format string yang diteruskan strptime. Ini adalah ide yang buruk karena itu akan gagal untuk mem-parsing setiap datetime dengan offset UTC yang berbeda dan menimbulkan pengecualian. Lihat jawaban saya yang menjelaskan bagaimana penguraian RFC 3339 dengan strptime sebenarnya tidak mungkin.
Mark Amery
1
Hard-kode tetapi cukup untuk kasus ketika Anda perlu mengurai zulu saja.
Sasha
1
@alexander ya - yang mungkin terjadi jika, misalnya, Anda tahu bahwa string tanggal Anda dibuat dengan toISOStringmetode JavaScript . Tetapi tidak disebutkan batasan tanggal waktu Zulu dalam jawaban ini, tidak juga pertanyaan menunjukkan bahwa hanya itu yang diperlukan, dan hanya menggunakan dateutilbiasanya sama nyaman dan tidak terlalu sempit dalam apa yang dapat diuraikan.
Mark Amery
11

Jika Anda bekerja dengan Django, ia menyediakan modul dateparse yang menerima banyak format yang mirip dengan format ISO, termasuk zona waktu.

Jika Anda tidak menggunakan Django dan Anda tidak ingin menggunakan salah satu perpustakaan lain yang disebutkan di sini, Anda mungkin bisa mengadaptasi kode sumber Django untuk dateparse ke proyek Anda.

Don Kirkby
sumber
Django DateTimeFieldmenggunakan ini ketika Anda menetapkan nilai string.
djvg
11

Saya telah menemukan ciso8601 sebagai cara tercepat untuk mengurai cap waktu ISO 8601. Seperti namanya, ini diimplementasikan dalam C.

import ciso8601
ciso8601.parse_datetime('2014-01-09T21:48:00.921000+05:30')

The GitHub Repo README menunjukkan mereka> 10x speedup terhadap semua perpustakaan lainnya yang tercantum dalam jawaban lainnya.

Proyek pribadi saya melibatkan banyak penguraian ISO 8601. Senang rasanya bisa hanya beralih panggilan dan pergi 10x lebih cepat. :)

Sunting: Saya sejak itu menjadi pengelola ciso8601. Sekarang lebih cepat dari sebelumnya!

movermeyer
sumber
Ini terlihat seperti perpustakaan yang hebat! Bagi mereka yang ingin mengoptimalkan parsing ISO8601 di Google App Engine, sayangnya, kami tidak dapat menggunakannya karena ini adalah pustaka C, tetapi tolok ukur Anda sangat berguna untuk menunjukkan bahwa asli datetime.strptime()adalah solusi tercepat berikutnya. Terima kasih telah mengumpulkan semua info itu!
hamx0r
3
@ hamx0r, ketahuilah bahwa datetime.strptime()ini bukan parsing library ISO 8601 penuh. Jika Anda menggunakan Python 3.7, Anda bisa menggunakan datetime.fromisoformat()metode ini, yang sedikit lebih fleksibel. Anda mungkin tertarik pada daftar parser yang lebih lengkap ini yang harus segera digabung ke dalam ciso8601 README.
movermeyer
ciso8601 berfungsi dengan sangat baik, tetapi kita harus terlebih dahulu melakukan "pip install pytz", karena kita tidak dapat mengurai timestamp dengan informasi zona waktu tanpa ketergantungan pytz. Contoh akan terlihat seperti: dob = ciso8601.parse_datetime (hasil ['dob'] ['date'])
Dirk
2
@ Malang, hanya dalam Python 2 . Tetapi bahkan itu harus dihapus dalam rilis berikutnya.
movermeyer
8

Ini berfungsi untuk stdlib pada Python 3.2 dan seterusnya (dengan anggapan semua cap waktu adalah UTC):

from datetime import datetime, timezone, timedelta
datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").replace(
    tzinfo=timezone(timedelta(0)))

Sebagai contoh,

>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)
Benjamin Riggs
sumber
2
Jawaban ini bergantung pada pengodean offset UTC tertentu (yaitu "Z", yang berarti +00: 00) ke dalam format string yang diteruskan strptime. Ini adalah ide yang buruk karena itu akan gagal untuk mem-parsing setiap datetime dengan offset UTC yang berbeda dan menimbulkan pengecualian. Lihat jawaban saya yang menjelaskan bagaimana penguraian RFC 3339 dengan strptime sebenarnya tidak mungkin.
Mark Amery
1
Secara teori, ya, ini gagal. Dalam prakteknya, saya belum pernah menemukan tanggal yang diformat ISO 8601 yang tidak dalam waktu Zulu. Untuk kebutuhan saya yang sesekali, ini berfungsi dengan baik dan tidak bergantung pada beberapa perpustakaan eksternal.
Benjamin Riggs
4
Anda bisa menggunakan timezone.utcbukan timezone(timedelta(0)). Juga, kode ini bekerja di Python 2.6+ (setidaknya) jika Anda menyediakan utcobjek tzinfo
jfs
Tidak masalah jika Anda mengalaminya, itu tidak cocok dengan spek.
theannouncer
Anda dapat menggunakan %Zzona waktu untuk di versi Python terbaru.
sventechie
7

Saya penulis uto8601 utils. Itu dapat ditemukan di GitHub atau di PyPI . Berikut ini cara menguraikan contoh Anda:

>>> from iso8601utils import parsers
>>> parsers.datetime('2008-09-03T20:56:35.450686Z')
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
Marc Wilson
sumber
6

Salah satu cara mudah untuk mengubah string tanggal mirip ISO 8601 ke stempel waktu UNIX atau datetime.datetimeobjek di semua versi Python yang didukung tanpa menginstal modul pihak ketiga adalah dengan menggunakan pengurai tanggal SQLite .

#!/usr/bin/env python
from __future__ import with_statement, division, print_function
import sqlite3
import datetime

testtimes = [
    "2016-08-25T16:01:26.123456Z",
    "2016-08-25T16:01:29",
]
db = sqlite3.connect(":memory:")
c = db.cursor()
for timestring in testtimes:
    c.execute("SELECT strftime('%s', ?)", (timestring,))
    converted = c.fetchone()[0]
    print("%s is %s after epoch" % (timestring, converted))
    dt = datetime.datetime.fromtimestamp(int(converted))
    print("datetime is %s" % dt)

Keluaran:

2016-08-25T16:01:26.123456Z is 1472140886 after epoch
datetime is 2016-08-25 12:01:26
2016-08-25T16:01:29 is 1472140889 after epoch
datetime is 2016-08-25 12:01:29
Damian Yerrick
sumber
11
Terima kasih. Ini menjijikkan. Aku menyukainya.
wchargin
1
Betapa luar biasa, luar biasa, hack yang indah! Terima kasih!
Havok
6

Saya telah membuat kode parser untuk standar ISO 8601 dan menaruhnya di GitHub: https://github.com/boxed/iso8601 . Implementasi ini mendukung semua yang ada dalam spesifikasi kecuali untuk durasi, interval, interval periodik, dan tanggal di luar rentang tanggal yang didukung dari modul datetime Python.

Tes sudah termasuk! : P

kemas
sumber
6

Fungsi Django parse_datetime () mendukung tanggal dengan offset UTC:

parse_datetime('2016-08-09T15:12:03.65478Z') =
datetime.datetime(2016, 8, 9, 15, 12, 3, 654780, tzinfo=<UTC>)

Jadi itu dapat digunakan untuk parsing tanggal ISO 8601 di bidang dalam seluruh proyek:

from django.utils import formats
from django.forms.fields import DateTimeField
from django.utils.dateparse import parse_datetime

class DateTimeFieldFixed(DateTimeField):
    def strptime(self, value, format):
        if format == 'iso-8601':
            return parse_datetime(value)
        return super().strptime(value, format)

DateTimeField.strptime = DateTimeFieldFixed.strptime
formats.ISO_INPUT_FORMATS['DATETIME_INPUT_FORMATS'].insert(0, 'iso-8601')
Artem Vasilev
sumber
4

Karena ISO 8601 memungkinkan banyak variasi titik dua dan garis opsional hadir, pada dasarnya CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. Jika Anda ingin menggunakan strptime, Anda harus menghapus variasi itu terlebih dahulu.

Tujuannya adalah untuk menghasilkan objek datetime utc.


Jika Anda hanya ingin case dasar yang berfungsi untuk UTC dengan akhiran Z seperti 2016-06-29T19:36:29.3453Z:

datetime.datetime.strptime(timestamp.translate(None, ':-'), "%Y%m%dT%H%M%S.%fZ")


Jika Anda ingin menangani zona waktu suka 2016-06-29T19:36:29.3453-0400atau 2008-09-03T20:56:35.450686+05:00gunakan yang berikut ini. Ini akan mengkonversi semua variasi menjadi sesuatu tanpa pembatas variabel seperti 20080903T205635.450686+0500membuatnya lebih konsisten / mudah diurai.

import re
# this regex removes all colons and all 
# dashes EXCEPT for the dash indicating + or - utc offset for the timezone
conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', timestamp)
datetime.datetime.strptime(conformed_timestamp, "%Y%m%dT%H%M%S.%f%z" )


Jika sistem Anda tidak mendukung %zarahan strptime (Anda melihat sesuatu seperti ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z') maka Anda perlu secara manual mengimbangi waktu dari Z(UTC). Catatan %zmungkin tidak berfungsi pada sistem Anda dalam versi python <3 karena bergantung pada dukungan pustaka c yang bervariasi di seluruh tipe build sistem / python (yaitu Jython, Cython, dll.).

import re
import datetime

# this regex removes all colons and all 
# dashes EXCEPT for the dash indicating + or - utc offset for the timezone
conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', timestamp)

# split on the offset to remove it. use a capture group to keep the delimiter
split_timestamp = re.split(r"[+|-]",conformed_timestamp)
main_timestamp = split_timestamp[0]
if len(split_timestamp) == 3:
    sign = split_timestamp[1]
    offset = split_timestamp[2]
else:
    sign = None
    offset = None

# generate the datetime object without the offset at UTC time
output_datetime = datetime.datetime.strptime(main_timestamp +"Z", "%Y%m%dT%H%M%S.%fZ" )
if offset:
    # create timedelta based on offset
    offset_delta = datetime.timedelta(hours=int(sign+offset[:-2]), minutes=int(sign+offset[-2:]))
    # offset datetime with timedelta
    output_datetime = output_datetime + offset_delta
theannouncer
sumber
2

Untuk sesuatu yang berfungsi dengan pustaka 2.X standar coba:

calendar.timegm(time.strptime(date.split(".")[0]+"UTC", "%Y-%m-%dT%H:%M:%S%Z"))

calendar.timegm adalah versi timemkm gm yang hilang.

Gordon Wrigley
sumber
1
Ini hanya mengabaikan zona waktu '2013-01-28T14: 01: 01.335612-08: 00' -> diuraikan sebagai UTC, bukan PDT
gatoatigrado
2

Python-dateutil akan memunculkan eksepsi jika mem-parsing string tanggal tidak valid, jadi Anda mungkin ingin menangkap pengecualian.

from dateutil import parser
ds = '2012-60-31'
try:
  dt = parser.parse(ds)
except ValueError, e:
  print '"%s" is an invalid date' % ds
pengguna2646026
sumber
2

Saat ini ada Maya: Datetimes for Humans ™ , dari penulis Permintaan populer: paket HTTP for Humans ™:

>>> import maya
>>> str = '2008-09-03T20:56:35.450686Z'
>>> maya.MayaDT.from_rfc3339(str).datetime()
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=<UTC>)
jrc
sumber
2

Cara lain adalah dengan menggunakan parser khusus untuk ISO-8601 adalah dengan menggunakan isoparse fungsi parser dateutil:

from dateutil import parser

date = parser.isoparse("2008-09-03T20:56:35.450686+01:00")
print(date)

Keluaran:

2008-09-03 20:56:35.450686+01:00

Fungsi ini juga disebutkan dalam dokumentasi untuk fungsi Python standar datetime.fromisoformat :

Parser ISO 8601 berfitur lengkap, dateutil.parser.isoparse tersedia di dateutil paket pihak ketiga.

zawuza
sumber
1

Terima kasih atas jawaban Mark Amery yang luar biasa. Saya merancang fungsi untuk memperhitungkan semua format ISO mungkin pada waktu:

class FixedOffset(tzinfo):
    """Fixed offset in minutes: `time = utc_time + utc_offset`."""
    def __init__(self, offset):
        self.__offset = timedelta(minutes=offset)
        hours, minutes = divmod(offset, 60)
        #NOTE: the last part is to remind about deprecated POSIX GMT+h timezones
        #  that have the opposite sign in the name;
        #  the corresponding numeric value is not used e.g., no minutes
        self.__name = '<%+03d%02d>%+d' % (hours, minutes, -hours)
    def utcoffset(self, dt=None):
        return self.__offset
    def tzname(self, dt=None):
        return self.__name
    def dst(self, dt=None):
        return timedelta(0)
    def __repr__(self):
        return 'FixedOffset(%d)' % (self.utcoffset().total_seconds() / 60)
    def __getinitargs__(self):
        return (self.__offset.total_seconds()/60,)

def parse_isoformat_datetime(isodatetime):
    try:
        return datetime.strptime(isodatetime, '%Y-%m-%dT%H:%M:%S.%f')
    except ValueError:
        pass
    try:
        return datetime.strptime(isodatetime, '%Y-%m-%dT%H:%M:%S')
    except ValueError:
        pass
    pat = r'(.*?[+-]\d{2}):(\d{2})'
    temp = re.sub(pat, r'\1\2', isodatetime)
    naive_date_str = temp[:-5]
    offset_str = temp[-5:]
    naive_dt = datetime.strptime(naive_date_str, '%Y-%m-%dT%H:%M:%S.%f')
    offset = int(offset_str[-4:-2])*60 + int(offset_str[-2:])
    if offset_str[0] == "-":
        offset = -offset
    return naive_dt.replace(tzinfo=FixedOffset(offset))
omikron
sumber
0
def parseISO8601DateTime(datetimeStr):
    import time
    from datetime import datetime, timedelta

    def log_date_string(when):
        gmt = time.gmtime(when)
        if time.daylight and gmt[8]:
            tz = time.altzone
        else:
            tz = time.timezone
        if tz > 0:
            neg = 1
        else:
            neg = 0
            tz = -tz
        h, rem = divmod(tz, 3600)
        m, rem = divmod(rem, 60)
        if neg:
            offset = '-%02d%02d' % (h, m)
        else:
            offset = '+%02d%02d' % (h, m)

        return time.strftime('%d/%b/%Y:%H:%M:%S ', gmt) + offset

    dt = datetime.strptime(datetimeStr, '%Y-%m-%dT%H:%M:%S.%fZ')
    timestamp = dt.timestamp()
    return dt + timedelta(hours=dt.hour-time.gmtime(timestamp).tm_hour)

Perhatikan bahwa kita harus melihat jika string tidak diakhiri dengan Z, kita dapat menguraikan penggunaan %z.

Denny Weinberg
sumber
0

Awalnya saya mencoba dengan:

from operator import neg, pos
from time import strptime, mktime
from datetime import datetime, tzinfo, timedelta

class MyUTCOffsetTimezone(tzinfo):
    @staticmethod
    def with_offset(offset_no_signal, signal):  # type: (str, str) -> MyUTCOffsetTimezone
        return MyUTCOffsetTimezone((pos if signal == '+' else neg)(
            (datetime.strptime(offset_no_signal, '%H:%M') - datetime(1900, 1, 1))
          .total_seconds()))

    def __init__(self, offset, name=None):
        self.offset = timedelta(seconds=offset)
        self.name = name or self.__class__.__name__

    def utcoffset(self, dt):
        return self.offset

    def tzname(self, dt):
        return self.name

    def dst(self, dt):
        return timedelta(0)


def to_datetime_tz(dt):  # type: (str) -> datetime
    fmt = '%Y-%m-%dT%H:%M:%S.%f'
    if dt[-6] in frozenset(('+', '-')):
        dt, sign, offset = strptime(dt[:-6], fmt), dt[-6], dt[-5:]
        return datetime.fromtimestamp(mktime(dt),
                                      tz=MyUTCOffsetTimezone.with_offset(offset, sign))
    elif dt[-1] == 'Z':
        return datetime.strptime(dt, fmt + 'Z')
    return datetime.strptime(dt, fmt)

Tetapi itu tidak berhasil pada zona waktu negatif. Namun ini saya bekerja dengan baik, dengan Python 3.7.3:

from datetime import datetime


def to_datetime_tz(dt):  # type: (str) -> datetime
    fmt = '%Y-%m-%dT%H:%M:%S.%f'
    if dt[-6] in frozenset(('+', '-')):
        return datetime.strptime(dt, fmt + '%z')
    elif dt[-1] == 'Z':
        return datetime.strptime(dt, fmt + 'Z')
    return datetime.strptime(dt, fmt)

Beberapa tes, perhatikan bahwa keluaran hanya berbeda dengan ketepatan mikrodetik. Hingga 6 digit presisi pada mesin saya, tetapi YMMV:

for dt_in, dt_out in (
        ('2019-03-11T08:00:00.000Z', '2019-03-11T08:00:00'),
        ('2019-03-11T08:00:00.000+11:00', '2019-03-11T08:00:00+11:00'),
        ('2019-03-11T08:00:00.000-11:00', '2019-03-11T08:00:00-11:00')
    ):
    isoformat = to_datetime_tz(dt_in).isoformat()
    assert isoformat == dt_out, '{} != {}'.format(isoformat, dt_out)
DI
sumber
Bolehkah saya bertanya mengapa Anda melakukannya frozenset(('+', '-'))? Tidakkah seharusnya tuple normal seperti ('+', '-')dapat melakukan hal yang sama?
Prahlad Yeri
Tentu, tetapi bukankah itu pemindaian linear daripada pencarian hash sempurna?
AT