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 strptime
trong 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ì?
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 strptime
trong 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ì?
Câu trả lời:
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.isoparse
có 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
.
python-dateutil
không dateutil
, vì vậy : pip install python-dateutil
.
dateutil.parser
là 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.
Các datetime
thư 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
datetime
tương ứng với mộtdate_string
trong một trong các định dạng được phát ra bởidate.isoformat()
và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')
datetime
có 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 ..
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"
.
Z
kịch bản đầu vào có thể được sửa đổi với date_string.replace("Z", "+00:00")
.
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
strptime
thực tế là không thể.
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
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 strptime
là 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:MM
hoặc -HH:MM
định dạng, như +05:00
hay -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 strptime
và strftime
khô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 %z
trong 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à strptime
không thực hiện %z
chỉ thị nào trong Python 2. )
Nhiều câu trả lời ở đây khuyến nghị strptime
tất cả giải quyết vấn đề này bằng cách bao gồm một chữ Z
trong chuỗi định dạng của chúng, khớp với Z
chuỗ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.
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.
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.
iso8601.parse_date("2008-09-03T20:56:35.450686Z")
nhập lại, datetime s = "2008-09-03T20: 56: 35.450686Z" d = datetime.datetime (* map (int, re.split ('[^ \ d]', s) [: - 1]))
datetime.datetime(*map(int, re.findall('\d+', s))
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)
""
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.
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 ()
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')
.
datetime.fromisoformat()
vàdatetime.isoformat()
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())
Chỉ cần sử dụng python-dateutil
mô-đ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())
455051100
(đã kiểm tra tại epochconverter.com ) ,,, trừ khi tôi thiếu thứ gì?
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)
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ể.
toISOString
phươ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 dateutil
thường thuận tiện như nhau và ít hẹp hơn trong những gì nó có thể phân tích.
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.
DateTimeField
sử dụng điều này khi bạn đặt một giá trị chuỗi.
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!
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!
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.
Đ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)
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ể.
timezone.utc
thay 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 utc
tzinfo đối tượng
%Z
múi giờ cho các phiên bản Python mới nhất.
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)
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
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
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')
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.
2016-06-29T19:36:29.3453Z
:
datetime.datetime.strptime(timestamp.translate(None, ':-'), "%Y%m%dT%H%M%S.%fZ")
2016-06-29T19:36:29.3453-0400
hoặc 2008-09-03T20:56:35.450686+05:00
sử 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+0500
là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" )
%z
chỉ 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 ý %z
có 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
Đố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.
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
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>)
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.
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))
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
.
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)
frozenset(('+', '-'))
? Không phải một tuple bình thường như ('+', '-')
có thể hoàn thành điều tương tự?