Làm cách nào để phân tích ngày theo định dạng ISO 8601?


642

Tôi cần phân tích các chuỗi RFC 3339 như kiểu "2008-09-03T20:56:35.450686Z"Python datetime.

Tôi đã tìm thấy strptimetrong thư viện chuẩn Python, nhưng nó không thuận tiện lắm.

Cách tốt nhất để làm việc này là gì?




3
Để rõ ràng: ISO 8601 là tiêu chuẩn chính. RFC 3339 là một hồ sơ cá nhân tự xưng là ISO của ISO 8601, làm cho một số ghi đè không theo quy tắc của ISO 8601.
Basil Bourque

3
Đừng bỏ lỡ giải pháp python3.7 + bên dưới để đảo ngược isoformat ()
Brad M

2
Câu hỏi này không nên được đóng lại dưới dạng dupe cho bài đăng được liên kết. Vì cái này yêu cầu phân tích chuỗi thời gian ISO 8601 (vốn không được hỗ trợ bởi python trước 3.7) và cái còn lại là định dạng một đối tượng datetime thành một chuỗi epoch bằng phương thức lỗi thời.
abccd

Câu trả lời:


462

Gói python-dateutil có thể phân tích không chỉ các chuỗi thời gian RFC 3339 như chuỗi trong câu hỏi, mà cả các chuỗi ngày và thời gian ISO 8601 khác không tuân thủ RFC 3339 (chẳng hạn như các chuỗi không có bù UTC hoặc các chuỗi đại diện chỉ một ngày).

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

Lưu ý rằng dateutil.parser.isoparsecó lẽ nghiêm ngặt hơn so với hacky dateutil.parser.parse, nhưng cả hai đều khá tha thứ và sẽ cố gắng diễn giải chuỗi mà bạn truyền vào. Nếu bạn muốn loại bỏ khả năng đọc sai, bạn cần sử dụng một cái gì đó chặt chẽ hơn một trong hai chức năng.

Tên Pypi là python-dateutil, không dateutil(cảm ơn code3monk3y ):

pip install python-dateutil

Nếu bạn đang sử dụng Python 3.7, có một cái nhìn tại câu trả lời này về datetime.datetime.fromisoformat.


75
Đối với những người lười biếng, nó được cài đặt qua python-dateutilkhông dateutil, vì vậy : pip install python-dateutil.
cod3monk3y

29
Được cảnh báo rằng đó dateutil.parserlà cố ý hacky: nó cố gắng đoán định dạng và đưa ra các giả định không thể tránh khỏi (chỉ có thể tùy chỉnh bằng tay) trong các trường hợp mơ hồ. Vì vậy, CHỈ sử dụng nó nếu bạn cần phân tích cú pháp đầu vào của định dạng không xác định và không sao để chấp nhận việc đọc sai.
ivan_pozdeev

2
Đã đồng ý. Một ví dụ đang vượt qua "ngày" là 9999. Điều này sẽ trả về giống như datetime (9999, tháng hiện tại, ngày hiện tại). Không phải là một ngày hợp lệ trong quan điểm của tôi.
timbo

1
@ivan_pozdeev bạn muốn giới thiệu gói nào để phân tích cú pháp không đoán?
bgusach

2
@ivan_pozdeev có bản cập nhật cho mô-đun đọc ngày iso8601
theEpsilon

197

Mới trong Python 3.7+


Các datetimethư viện chuẩn giới thiệu một chức năng cho đảo ngược datetime.isoformat().

classmethod datetime.fromisoformat(date_string):

Trả về một datetimetương ứng với một date_stringtrong một trong các định dạng được phát ra bởi date.isoformat()datetime.isoformat().

Cụ thể, hàm này hỗ trợ các chuỗi ở định dạng:

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

nơi *có thể phù hợp với bất kỳ nhân vật duy nhất.

Thận trọng : Điều này không hỗ trợ phân tích các chuỗi ISO 8601 tùy ý - nó chỉ nhằm mục đích hoạt động nghịch đảo của datetime.isoformat().

Ví dụ sử dụng:

from datetime import datetime

date = datetime.fromisoformat('2017-01-01T12:30:59.000000')

6
Lạ nhỉ. Bởi vì a datetimecó thể chứa a tzinfo, và do đó tạo ra múi giờ, nhưng datetime.fromisoformat()không phân tích được tzinfo? có vẻ như là một lỗi ..
Hendy IINA

20
Đừng bỏ lỡ ghi chú đó trong tài liệu, điều này không chấp nhận tất cả các chuỗi ISO 8601 hợp lệ, chỉ những chuỗi được tạo bởi isoformat. Nó không chấp nhận ví dụ trong câu hỏi "2008-09-03T20:56:35.450686Z"vì dấu vết Z, nhưng nó chấp nhận "2008-09-03T20:56:35.450686".
Flimm

26
Để hỗ trợ chính xác các Zkịch bản đầu vào có thể được sửa đổi với date_string.replace("Z", "+00:00").
Jox

7
Lưu ý rằng trong vài giây, nó chỉ xử lý chính xác 0, 3 hoặc 6 chữ số thập phân. Nếu dữ liệu đầu vào có 1, 2, 4, 5, 7 hoặc nhiều vị trí thập phân hơn, phân tích cú pháp sẽ thất bại!
Felk

1
@JDOaktown Ví dụ này sử dụng thư viện datetime của Python nguyên gốc, không phải trình phân tích cú pháp dateutil. Nó thực sự sẽ thất bại nếu các vị trí thập phân không phải là 0, 3 hoặc 6 với phương pháp này.
abccd

174

Lưu ý trong Python 2.6+ và Py3K, ký tự% f bắt được micro giây.

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

Xem vấn đề ở đây


4
Lưu ý - nếu sử dụng datetimes Naive - Tôi nghĩ rằng bạn không có TZ nào cả - Z có thể không khớp với bất cứ điều gì.
Daniel Staple

24
Câu trả lời này (ở dạng hiện tại, đã được chỉnh sửa) dựa trên mã hóa cứng một phần bù UTC cụ thể (cụ thể là "Z", có nghĩa là: 00: 00) vào chuỗi định dạng. Đây là một ý tưởng tồi vì nó sẽ không phân tích được bất kỳ thời gian nào với phần bù UTC khác và đưa ra một ngoại lệ. Xem câu trả lời của tôi mô tả cách phân tích RFC 3339 với strptimethực tế là không thể.
Đánh dấu Amery

1
trong trường hợp của tôi,% f đã bắt được micro giây chứ không phải Z, datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f') vì vậy điều này đã tạo ra mánh khóe
ashim888

Py3K có nghĩa là Python 3000?!?
Robino

2
@Robino IIRC, "Python 3000" là tên cũ của ngày nay được gọi là Python 3.
Vứt bỏ tài khoản

161

Một số câu trả lời ở đây đề nghị sử dụng datetime.datetime.strptimeđể phân tích dữ liệu thời gian RFC 3339 hoặc ISO 8601 bằng các múi giờ, giống như câu hỏi được trình bày trong câu hỏi:

2008-09-03T20:56:35.450686Z

Đây là một ý tưởng tồi.

Giả sử rằng bạn muốn hỗ trợ định dạng RFC 3339 đầy đủ, bao gồm hỗ trợ cho các độ lệch UTC khác 0, thì mã mà các câu trả lời này gợi ý không hoạt động. Thật vậy, nó không thể hoạt động, vì phân tích cú pháp RFC 3339 bằng cách sử dụng strptimelà không thể. Các chuỗi định dạng được sử dụng bởi mô-đun datetime của Python không có khả năng mô tả cú pháp RFC 3339.

Vấn đề là bù đắp UTC. Các RFC 3339 Internet Date / Time Format yêu cầu mọi ngày thời gian bao gồm một UTC bù đắp, và rằng những offsets hoặc có thể Z(viết tắt của "Zulu thời gian") hoặc trong +HH:MMhoặc -HH:MMđịnh dạng, như +05:00hay -10:30.

Do đó, đây là tất cả các mốc thời gian RFC 3339 hợp lệ:

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

Than ôi, các chuỗi định dạng được sử dụng bởi strptimestrftimekhông có chỉ thị tương ứng với các độ lệch UTC ở định dạng RFC 3339. Có thể tìm thấy danh sách đầy đủ các lệnh mà họ hỗ trợ tại https://docs.python.org/3/l Library / datetime.html # strftime- and-strptime-behavior và chỉ thị bù UTC duy nhất có trong danh sách là %z:

% z

UTC bù ở dạng + HHMM hoặc -HHMM (chuỗi trống nếu đối tượng là ngây thơ).

Ví dụ: (trống), +0000, -0400, +1030

Điều này không phù hợp với định dạng của phần bù RFC 3339 và thực sự nếu chúng ta cố gắng sử dụng %ztrong chuỗi định dạng và phân tích ngày RFC 3339, chúng ta sẽ thất bại:

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

(Trên thực tế, ở trên chỉ là những gì bạn sẽ thấy trong Python 3. Trong Python 2, chúng ta sẽ thất bại vì một lý do thậm chí đơn giản hơn, đó là strptimekhông thực hiện %zchỉ thị nào trong Python 2. )

Nhiều câu trả lời ở đây khuyến nghị strptimetất cả giải quyết vấn đề này bằng cách bao gồm một chữ Ztrong chuỗi định dạng của chúng, khớp với Zchuỗi thời gian ví dụ của người hỏi (và loại bỏ nó, tạo ra một datetimeđối tượng không có múi giờ):

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

Vì điều này loại bỏ thông tin múi giờ được bao gồm trong chuỗi thời gian gốc, nên chúng ta có nên xem kết quả này có chính xác hay không. Nhưng quan trọng hơn, vì cách tiếp cận này liên quan đến việc mã hóa cứng một phần bù UTC cụ thể vào chuỗi định dạng , nó sẽ bóp nghẹt khoảnh khắc nó cố phân tích bất kỳ thời gian RFC 3339 nào với phần bù UTC khác:

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

Trừ khi bạn chắc chắn rằng bạn chỉ cần hỗ trợ các mốc thời gian RFC 3339 trong thời gian Zulu, và không phải là những người có các múi giờ khác, không nên sử dụng strptime. Thay vào đó, sử dụng một trong nhiều cách tiếp cận khác được mô tả trong câu trả lời.


79
Thật khó hiểu vì sao strptime không có chỉ thị cho thông tin múi giờ định dạng ISO và tại sao nó không thể được phân tích cú pháp. Đáng kinh ngạc.
Csaba Toth

2
@CsabaToth Hoàn toàn đồng ý - nếu tôi có thời gian để giết, có lẽ tôi sẽ cố gắng thêm nó vào ngôn ngữ. Hoặc bạn có thể làm như vậy, nếu bạn rất có khuynh hướng - Tôi thấy bạn có một số kinh nghiệm C, không giống như tôi.
Đánh dấu Amery

1
@CsabaToth - Tại sao không thể tin được? Nó hoạt động đủ tốt cho hầu hết mọi người, hoặc họ tìm thấy cách giải quyết đủ dễ dàng. Nếu bạn cần tính năng này, nó là nguồn mở và bạn có thể thêm nó. Hoặc trả tiền cho ai đó để làm điều đó cho bạn. Tại sao ai đó nên tình nguyện thời gian rảnh của mình để giải quyết các vấn đề cụ thể của bạn? Hãy để nguồn được với bạn.
Peter M. - là viết tắt của Monica

2
@PeterMasiar Incredible vì thường người ta phát hiện ra rằng những thứ trong python đã được thực hiện chu đáo và đầy đủ. Chúng tôi đã bị làm hỏng bởi sự chú ý này đến từng chi tiết và vì vậy khi chúng tôi tình cờ phát hiện ra thứ gì đó trong ngôn ngữ "unpythonic", chúng tôi ném đồ chơi của mình ra khỏi xe, vì tôi sắp làm như vậy ngay bây giờ. Whaaaaaaaaaa Whaa wahaaaaa :-(
Robino

2
strptime()trong Python 3.7 hiện hỗ trợ mọi thứ được mô tả là không thể trong câu trả lời này ('Z' theo nghĩa đen và ':' trong phần bù múi giờ). Thật không may, có một trường hợp góc khác làm cho RFC 3339 về cơ bản không tương thích với ISO 8601, cụ thể là trường hợp trước cho phép bù múi giờ null âm -00: 00 và sau này thì không.
SergiyKolesnikov

75

Hãy thử mô-đun iso8601 ; nó làm chính xác điều này

Có một số tùy chọn khác được đề cập trên trang WorkWithTime trên wiki python.org.


Đơn giản nhưiso8601.parse_date("2008-09-03T20:56:35.450686Z")
Pakman

3
Câu hỏi không phải là "làm thế nào để tôi phân tích ngày tháng theo tiêu chuẩn ISO 8601", đó là "làm thế nào để tôi phân tích định dạng ngày chính xác này."
Nicholas Riley

3
@tiktak OP đã hỏi "Tôi cần phân tích các chuỗi như X" và câu trả lời của tôi cho việc đó, đã thử cả hai thư viện, là sử dụng một thư viện khác, vì iso8601 vẫn có vấn đề quan trọng. Sự tham gia của tôi hoặc thiếu nó trong một dự án như vậy là hoàn toàn không liên quan đến câu trả lời.
Tobia

2
Xin lưu ý rằng phiên bản pip của iso8601 đã không được cập nhật từ năm 2007 và có một số lỗi nghiêm trọng nổi bật. Tôi khuyên bạn nên tự mình áp dụng một số phần quan trọng của các bản vá hoặc tìm một trong nhiều dĩa github đã thực hiện để github.com/keithhackbarth/pyiso8601-strict
keithhackbarth

6
iso8601 , còn được gọi là pyiso8601 , đã được cập nhật gần đây vào tháng 2 năm 2014. Phiên bản mới nhất hỗ trợ bộ chuỗi ISO 8601 rộng hơn nhiều. Tôi đã sử dụng để có hiệu quả tốt trong một số dự án của tôi.
Dave Hein

34
nhập lại, datetime
s = "2008-09-03T20: 56: 35.450686Z"
d = datetime.datetime (* map (int, re.split ('[^ \ d]', s) [: - 1]))

73
Tôi không đồng ý, điều này thực tế không thể đọc được và theo như tôi có thể nói là không tính đến Zulu (Z) khiến cho thời gian này ngây thơ ngay cả khi dữ liệu múi giờ được cung cấp.
rốn

14
Tôi thấy nó khá dễ đọc. Trên thực tế, đây có lẽ là cách dễ nhất và hiệu quả nhất để thực hiện chuyển đổi mà không cần cài đặt các gói bổ sung.
Tobia

2
Điều này tương đương với d = datetime.datetime (* map (int, re.split ('\ D', s) [: - 1])) i giả sử.
Xuân

4
một biến thể:datetime.datetime(*map(int, re.findall('\d+', s))
jfs

3
Điều này dẫn đến một đối tượng datetime ngây thơ không có múi giờ, phải không? Vì vậy, bit UTC bị mất trong dịch thuật?
w00t

32

Lỗi chính xác bạn nhận được là gì? Có giống như sau không?

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

Nếu có, bạn có thể chia chuỗi đầu vào của mình thành ".", Và sau đó thêm micro giây vào thời gian bạn nhận được.

Thử cái này:

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

10
Bạn không thể tách .Z vì nó có nghĩa là múi giờ và có thể khác nhau. Tôi cần chuyển đổi ngày thành múi giờ UTC.
Alexander Artemenko

Một đối tượng datetime đơn giản không có khái niệm về múi giờ. Nếu tất cả thời gian của bạn kết thúc bằng "Z", tất cả thời gian bạn nhận được là UTC (thời gian Zulu).
tzot

nếu múi giờ là bất cứ thứ gì khác ngoài ""hoặc "Z", thì nó phải là phần bù theo giờ / phút, có thể được thêm trực tiếp vào / trừ khỏi đối tượng datetime. bạn có thể tạo một lớp con tzinfo để xử lý nó, nhưng điều đó có thể không được đề xuất.
Độc thân Khuyến khích

8
Ngoài ra, "% f" là công cụ xác định micrô giây, do đó, chuỗi chuỗi thời gian (ngây thơ) trông giống như: "% Y-% m-% dT% H:% M:% S.% f".
quodlibetor

1
Điều này sẽ đưa ra một ngoại lệ nếu chuỗi datetime đã cho có phần bù UTC khác với "Z". Nó không hỗ trợ toàn bộ định dạng RFC 3339 và là một câu trả lời kém hơn cho những người khác xử lý việc bù đắp UTC đúng cách.
Mark Amery

25

Bắt đầu từ Python 3.7, strptime hỗ trợ các dấu phân cách dấu hai chấm trong phần bù UTC ( nguồn ). Vì vậy, bạn có thể sử dụng:

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

BIÊN TẬP:

Như Martijn đã chỉ ra, nếu bạn đã tạo đối tượng datetime bằng isoformat (), bạn chỉ cần sử dụng datetime.fromisoformat ()


4
Nhưng trong 3.7, bạn cũng có thể datetime.fromisoformat()tự động xử lý các chuỗi như đầu vào của bạn : datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00').
Martijn Pieters

2
Điểm tốt. Tôi đồng ý, tôi khuyên bạn nên sử dụng datetime.fromisoformat()datetime.isoformat()
Andreas Profous

19

Trong những ngày này, Arrow cũng có thể được sử dụng như một giải pháp của bên thứ ba:

>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())

6
Mũi tên không hỗ trợ ISO8601 đúng cách: github.com/crsmithdev/arrow/issues/291
đóng hộp

1
Chỉ cần sử dụng python-dateutil - mũi tên yêu cầu python-dateutil.
danizen

Mũi tên hiện hỗ trợ ISO8601. Các vấn đề được tham chiếu hiện đang đóng cửa.
Altus

18

Chỉ cần sử dụng python-dateutilmô-đun:

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

Tài liệu


1
Đây không phải là chính xác câu trả lời @Flimms ở trên sao?
leo

1
Nơi nào bạn thấy anh ấy phân tích cú pháp trong vài giây? Tôi tìm thấy bài viết này bằng cách cố gắng để có được thời gian kỷ nguyên vì vậy tôi đoán người khác cũng sẽ như vậy.
Blairg23

1
Đây không phải là UTC trên hệ thống của tôi. Thay vào đó, đầu ra tính bằng giây là thời gian unix epoch như thể ngày nằm trong múi giờ địa phương của tôi.
Elliot

1
Câu trả lời này là lỗi và không nên được chấp nhận. Có lẽ toàn bộ câu hỏi nên được đánh dấu là một bản sao của stackoverflow.com/questions/11743019/
Kiếm

@tripleee Thật ra tôi chỉ kiểm tra mã và nó dường như trả về câu trả lời đúng: 455051100(đã kiểm tra tại epochconverter.com ) ,,, trừ khi tôi thiếu thứ gì?
Blairg23

13

Nếu bạn không muốn sử dụng dateutil, bạn có thể thử chức năng này:

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)

Kiểm tra:

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

Kết quả:

datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)

5
Câu trả lời này dựa trên việc mã hóa cứng một phần bù UTC cụ thể (cụ thể là "Z", có nghĩa là: 00: 00) vào chuỗi định dạng được truyền cho strptime. Đây là một ý tưởng tồi vì nó sẽ không phân tích được bất kỳ thời gian nào với phần bù UTC khác và đưa ra một ngoại lệ. Xem câu trả lời của tôi mô tả cách phân tích RFC 3339 với thời gian thực tế là không thể.
Mark Amery

1
Nó được mã hóa cứng nhưng nó đủ cho trường hợp khi bạn chỉ cần phân tích cú pháp zulu.
Sasha

1
@alexander có - có thể là trường hợp nếu, ví dụ, bạn biết rằng chuỗi ngày của bạn được tạo bằng toISOStringphương thức của JavaScript . Nhưng không có đề cập đến giới hạn về ngày giờ của Zulu trong câu trả lời này, câu hỏi cũng không chỉ ra rằng đó là tất cả những gì cần thiết, và chỉ sử dụng dateutilthường thuận tiện như nhau và ít hẹp hơn trong những gì nó có thể phân tích.
Đánh dấu Amery

11

Nếu bạn đang làm việc với Django, nó cung cấp mô-đun dateparse chấp nhận một loạt các định dạng tương tự định dạng ISO, bao gồm cả múi giờ.

Nếu bạn không sử dụng Django và bạn không muốn sử dụng một trong những thư viện khác được đề cập ở đây, có lẽ bạn có thể điều chỉnh mã nguồn Django cho ngày tháng cho dự án của bạn.


Django DateTimeFieldsử dụng điều này khi bạn đặt một giá trị chuỗi.
djvg

11

Tôi đã tìm thấy ciso8601 là cách nhanh nhất để phân tích dấu thời gian ISO 8601. Như tên cho thấy, nó được thực hiện trong C.

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

Các GitHub Repo README cho thấy họ> 10x tăng tốc so với tất cả các thư viện khác được liệt kê trong câu trả lời khác.

Dự án cá nhân của tôi liên quan đến rất nhiều phân tích cú pháp ISO 8601. Thật tuyệt khi có thể chuyển cuộc gọi và đi nhanh hơn gấp 10 lần. :)

Chỉnh sửa: Tôi đã trở thành một người duy trì ciso8601. Bây giờ nhanh hơn bao giờ hết!


Đây trông giống như một thư viện tuyệt vời! Đối với những người muốn tối ưu hóa phân tích cú pháp ISO8601 trên Google App Engine, đáng buồn thay, chúng tôi không thể sử dụng nó vì đó là thư viện C, nhưng điểm chuẩn của bạn là sâu sắc để cho thấy rằng bản địa datetime.strptime()là giải pháp nhanh nhất tiếp theo. Cảm ơn vì đã đặt tất cả thông tin đó lại với nhau!
hamx0r

3
@ hamx0r, lưu ý rằng đó datetime.strptime()không phải là thư viện phân tích cú pháp ISO 8601 đầy đủ. Nếu bạn đang dùng Python 3.7, bạn có thể sử dụng datetime.fromisoformat()phương thức này linh hoạt hơn một chút. Bạn có thể quan tâm đến danh sách các trình phân tích cú pháp đầy đủ hơn này sẽ sớm được hợp nhất vào ciso8601 README.
movermeyer

ciso8601 hoạt động khá tốt, nhưng trước tiên người ta phải thực hiện "pip install pytz", bởi vì người ta không thể phân tích dấu thời gian với thông tin múi giờ mà không phụ thuộc pytz. Ví dụ sẽ giống như: dob = ciso8601.parse_datetime (result ['dob'] ['date'])
Dirk

2
@Dirk, chỉ trong Python 2 . Nhưng ngay cả điều đó nên được loại bỏ trong phiên bản tiếp theo.
movermeyer

8

Điều này hoạt động cho stdlib trên Python 3.2 trở đi (giả sử tất cả các dấu thời gian là UTC):

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

Ví dụ,

>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)

2
Câu trả lời này dựa trên việc mã hóa cứng một phần bù UTC cụ thể (cụ thể là "Z", có nghĩa là: 00: 00) vào chuỗi định dạng được truyền cho strptime. Đây là một ý tưởng tồi vì nó sẽ không phân tích được bất kỳ thời gian nào với phần bù UTC khác và đưa ra một ngoại lệ. Xem câu trả lời của tôi mô tả cách phân tích RFC 3339 với thời gian thực tế là không thể.
Mark Amery

1
Về lý thuyết, vâng, điều này thất bại. Trong thực tế, tôi chưa bao giờ gặp phải một ngày có định dạng ISO 8601 không có trong thời gian Zulu. Đối với nhu cầu rất thường xuyên của tôi, điều này hoạt động rất tốt và không phụ thuộc vào một số thư viện bên ngoài.
Benjamin Riggs

4
bạn có thể sử dụng timezone.utcthay vì timezone(timedelta(0)). Ngoài ra, mã công trình bằng Python 2.6+ (ít nhất) nếu bạn cung cấp utctzinfo đối tượng
JFS

Không quan trọng nếu bạn gặp phải nó, nó không phù hợp với thông số kỹ thuật.
theannouncer

Bạn có thể sử dụng %Zmúi giờ cho các phiên bản Python mới nhất.
sventechie

7

Tôi là tác giả của iso8601 utils. Nó có thể được tìm thấy trên GitHub hoặc trên PyPI . Đây là cách bạn có thể phân tích ví dụ của bạn:

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

6

Một cách đơn giản để chuyển đổi chuỗi ngày giống như ISO 8601 sang dấu thời gian hoặc datetime.datetimeđối tượng UNIX trong tất cả các phiên bản Python được hỗ trợ mà không cần cài đặt các mô-đun của bên thứ ba là sử dụng trình phân tích cú pháp ngày của 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)

Đầu ra:

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

11
Cảm ơn. Điều này thật kinh tởm. Tôi thích nó.
wchargein

1
Thật là một hack đáng kinh ngạc, tuyệt vời, đẹp! Cảm ơn!
Havok

6

Tôi đã mã hóa trình phân tích cú pháp cho tiêu chuẩn ISO 8601 và đưa nó vào GitHub: https://github.com/boxed/iso8601 . Việc triển khai này hỗ trợ mọi thứ trong đặc tả ngoại trừ thời lượng, khoảng thời gian, khoảng thời gian định kỳ và ngày nằm ngoài phạm vi ngày được hỗ trợ của mô-đun thời gian của Python.

Các xét nghiệm được bao gồm! : P



6

Hàm parse_datetime () của Django hỗ trợ ngày với các lần bù UTC:

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

Vì vậy, nó có thể được sử dụng để phân tích ngày ISO 8601 trong các trường trong toàn bộ dự án:

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

4

Bởi vì về cơ bản, ISO 8601 cho phép nhiều biến thể của dấu hai chấm và dấu gạch ngang tùy chọn CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. Nếu bạn muốn sử dụng strptime, trước tiên bạn cần loại bỏ các biến thể đó.

Mục tiêu là tạo ra một đối tượng datetime utc.


Nếu bạn chỉ muốn một trường hợp cơ bản hoạt động cho UTC với hậu tố Z như 2016-06-29T19:36:29.3453Z:

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


Nếu bạn muốn xử lý các múi giờ như 2016-06-29T19:36:29.3453-0400hoặc 2008-09-03T20:56:35.450686+05:00sử dụng như sau. Chúng sẽ chuyển đổi tất cả các biến thể thành một cái gì đó mà không có các dấu phân cách biến như 20080903T205635.450686+0500làm cho nó phù hợp hơn / dễ phân tích hơn.

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


Nếu hệ thống của bạn không hỗ trợ %zchỉ thị ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'theo thời gian (bạn thấy một cái gì đó giống như vậy ) thì bạn cần phải tự bù thời gian từ Z(UTC). Lưu ý %zcó thể không hoạt động trên hệ thống của bạn trong các phiên bản python <3 vì nó phụ thuộc vào hỗ trợ thư viện c khác nhau tùy theo loại xây dựng hệ thống / python (ví dụ: Jython, Cython, v.v.).

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

2

Đối với một cái gì đó hoạt động với thư viện tiêu chuẩn 2.X hãy thử:

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

calendar.timegm là phiên bản gm còn thiếu của time.mktime.


1
Điều này chỉ bỏ qua múi giờ '2013-01-28T14: 01: 01.335612-08: 00' -> được phân tích thành UTC, không phải PDT
gatoatigrado

2

Python-dateutil sẽ đưa ra một ngoại lệ nếu phân tích chuỗi ngày không hợp lệ, vì vậy bạn có thể muốn bắt ngoại lệ.

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

2

Ngày nay có Maya: Datetimes for Humans ™ , từ tác giả của gói Yêu cầu phổ biến: 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>)

2

Một cách khác là sử dụng phân tích cú pháp chuyên ngành cho tiêu chuẩn ISO-8601 được sử dụng isoparse chức năng của dateutil phân tích cú pháp:

from dateutil import parser

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

Đầu ra:

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

Hàm này cũng được đề cập trong tài liệu về hàm Python chuẩn datetime.fromisoformat :

Trình phân tích cú pháp ISO 8601 đầy đủ tính năng hơn, dateutil.parser.isopude có sẵn trong gói dateutil của bên thứ ba.


1

Nhờ câu trả lời tuyệt vời của Mark Amery, tôi đã nghĩ ra hàm để tính tất cả các định dạng ISO có thể của datetime:

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

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)

Lưu ý rằng chúng ta nên xem nếu chuỗi không kết thúc Z, chúng ta có thể phân tích cú pháp bằng cách sử dụng %z.


0

Ban đầu tôi đã thử với:

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)

Nhưng điều đó đã không làm việc trên các múi giờ tiêu cực. Điều này tuy nhiên tôi đã làm việc tốt, trong 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)

Một số thử nghiệm, lưu ý rằng đầu ra chỉ khác nhau bởi độ chính xác của micro giây. Có 6 chữ số chính xác trên máy của tôi, nhưng 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)

Tôi có thể hỏi tại sao bạn làm frozenset(('+', '-'))? Không phải một tuple bình thường như ('+', '-')có thể hoàn thành điều tương tự?
Prahlad Yeri

Chắc chắn, nhưng không phải là quét tuyến tính chứ không phải là một tra cứu băm hoàn hảo?
AT
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.