如何解析ISO 8601格式的日期? [重复]

时间:2022-07-11 21:45:15

This question already has an answer here:

这个问题在这里已有答案:

I need to parse RFC 3339 strings like "2008-09-03T20:56:35.450686Z" into Python's datetime type.

我需要将RFC 3339字符串如“2008-09-03T20:56:35.450686Z”解析为Python的日期时间类型。

I have found strptime in the Python standard library, but it is not very convenient.

我在Python标准库中找到了strptime,但它不是很方便。

What is the best way to do this?

做这个的最好方式是什么?

24 个解决方案

#1


340  

The python-dateutil package can parse not only RFC 3339 datetime strings like the one in the question, but also other ISO 8601 date and time strings that don't comply with RFC 3339 (such as ones with no UTC offset, or ones that represent only a date).

python-dateutil包不仅可以解析RFC 3339日期时间字符串,例如问题中的字符串,还可以解析不符合RFC 3339的其他ISO 8601日期和时间字符串(例如没有UTC偏移的字符串,或者代表只有约会)。

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

Be warned that the dateutil.parser is intentionally hacky: it tries to guess the format and makes inevitable assumptions (customizable by hand only) in ambiguous cases. So ONLY use it if you need to parse input of unknown format and are okay to tolerate occasional misreads. (thanks ivan_pozdeev)

请注意,dateutil.parser故意是hacky:它试图猜测格式并在不明确的情况下做出不可避免的假设(仅可手工定制)。因此,如果您需要解析未知格式的输入并且可以容忍偶尔的误读,那么只能使用它。 (感谢ivan_pozdeev)

The Pypi name is python-dateutil, not dateutil (thanks code3monk3y):

Pypi名称是python-dateutil,而不是dateutil(感谢code3monk3y):

pip install python-dateutil

If you're using Python 3.7, have a look at this answer about datetime.datetime.fromisoformat.

如果您使用的是Python 3.7,请查看有关datetime.datetime.fromisoformat的答案。

#2


130  

Note in Python 2.6+ and Py3K, the %f character catches microseconds.

注意在Python 2.6+和Py3K中,%f字符捕获微秒。

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

See issue here

请参阅此处的问题

#3


118  

Several answers here suggest using datetime.datetime.strptime to parse RFC 3339 or ISO 8601 datetimes with timezones, like the one exhibited in the question:

这里有几个答案建议使用datetime.datetime.strptime来解析带有时区的RFC 3339或ISO 8601日期时间,就像问题中展示的那样:

2008-09-03T20:56:35.450686Z

This is a bad idea.

这是一个坏主意。

Assuming that you want to support the full RFC 3339 format, including support for UTC offsets other than zero, then the code these answers suggest does not work. Indeed, it cannot work, because parsing RFC 3339 syntax using strptime is impossible. The format strings used by Python's datetime module are incapable of describing RFC 3339 syntax.

假设您想要支持完整的RFC 3339格式,包括支持除零以外的UTC偏移,那么这些答案建议的代码不起作用。实际上,它无法工作,因为使用strptime解析RFC 3339语法是不可能的。 Python的datetime模块使用的格式字符串无法描述RFC 3339语法。

The problem is UTC offsets. The RFC 3339 Internet Date/Time Format requires that every date-time includes a UTC offset, and that those offsets can either be Z (short for "Zulu time") or in +HH:MM or -HH:MM format, like +05:00 or -10:30.

问题是UTC偏移。 RFC 3339 Internet日期/时间格式要求每个日期时间包括UTC偏移量,并且这些偏移量可以是Z(“祖鲁时间”的缩写)或+ HH:MM或-HH:MM格式,如+ 05:00或-10:30

Consequently, these are all valid RFC 3339 datetimes:

因此,这些都是有效的RFC 3339日期时间:

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

Alas, the format strings used by strptime and strftime have no directive that corresponds to UTC offsets in RFC 3339 format. A complete list of the directives they support can be found at https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior, and the only UTC offset directive included in the list is %z:

唉,strptime和strftime使用的格式字符串没有与RFC 3339格式的UTC偏移相对应的指令。可以在https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior找到它们支持的指令的完整列表,列表中包含的唯一UTC偏移指令是% Z:

%z

UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive).

UTC偏移量,格式为+ HHMM或-HHMM(如果对象是幼稚的,则为空字符串)。

Example: (empty), +0000, -0400, +1030

示例:(空),+ 0000,-0400,+ 1030

This doesn't match the format of an RFC 3339 offset, and indeed if we try to use %z in the format string and parse an RFC 3339 date, we'll fail:

这与RFC 3339偏移的格式不匹配,实际上如果我们尝试在格式字符串中使用%z并解析RFC 3339日期,我们将失败:

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

(Actually, the above is just what you'll see in Python 3. In Python 2 we'll fail for an even simpler reason, which is that strptime does not implement the %z directive at all in Python 2.)

(实际上,上面就是你在Python 3中看到的内容。在Python 2中,我们会因为一个更简单的原因而失败,这就是strptime在Python 2中根本没有实现%z指令。)

The multiple answers here that recommend strptime all work around this by including a literal Z in their format string, which matches the Z from the question asker's example datetime string (and discards it, producing a datetime object without a timezone):

这里推荐strptime的多个答案都是通过在其格式字符串中包含一个文字Z来解决这个问题,该字符串与问题提供者的示例日期时间字符串中的Z匹配(并丢弃它,生成没有时区的日期时间对象):

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

Since this discards timezone information that was included in the original datetime string, it's questionable whether we should regard even this result as correct. But more importantly, because this approach involves hard-coding a particular UTC offset into the format string, it will choke the moment it tries to parse any RFC 3339 datetime with a different UTC offset:

由于这会丢弃原始日期时间字符串中包含的时区信息,因此我们是否应该将此结果视为正确是值得怀疑的。但更重要的是,因为这种方法涉及将特定UTC偏移硬编码到格式字符串中,所以它会在尝试使用不同的UTC偏移量解析任何RFC 3339日期时间时阻塞:

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

Unless you're certain that you only need to support RFC 3339 datetimes in Zulu time, and not ones with other timezone offsets, don't use strptime. Use one of the many other approaches described in answers here instead.

除非你确定你只需要在祖鲁时间支持RFC 3339日期时间,而不是那些具有其他时区偏移的日期时间,否则不要使用strptime。请使用此处答案中描述的许多其他方法之一。

#4


67  

Try the iso8601 module; it does exactly this.

试试iso8601模块;它正是这样做的。

There are several other options mentioned on the WorkingWithTime page on the python.org wiki.

python.org wiki上的WorkingWithTime页面上提到了其他几个选项。

#5


40  

New in Python 3.7+


The datetime standard library introduced a function for inverting datetime.isoformat().

datetime标准库引入了一个用于反转datetime.isoformat()的函数。

classmethod datetime.fromisoformat(date_string):

Return a datetime corresponding to a date_string in one of the formats emitted by date.isoformat() and datetime.isoformat().

以date.isoformat()和datetime.isoformat()发出的格式之一返回与date_string对应的日期时间。

Specifically, this function supports strings in the format(s):

具体来说,此函数支持格式的字符串:

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

where * can match any single character.

其中*可以匹配任何单个字符。

Caution: This does not support parsing arbitrary ISO 8601 strings - it is only intended as the inverse operation of datetime.isoformat().

注意:这不支持解析任意ISO 8601字符串 - 它仅用作datetime.isoformat()的逆操作。

Example of use:

使用示例:

from datetime import datetime

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

#6


35  

import re,datetime
s="2008-09-03T20:56:35.450686Z"
d=datetime.datetime(*map(int, re.split('[^\d]', s)[:-1]))

#7


25  

What is the exact error you get? Is it like the following?

你得到的确切错误是什么?它像下面这样吗?

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

If yes, you can split your input string on ".", and then add the microseconds to the datetime you got.

如果是,您可以将输入字符串拆分为“。”,然后将微秒添加到您获得的日期时间。

Try this:

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

#8


19  

Starting from Python 3.7, strptime supports colon delimiters in UTC offsets (source). So you can then use:

从Python 3.7开始,strptime支持UTC偏移(源)中的冒号分隔符。所以你可以使用:

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

#9


19  

In these days, Arrow also can be used as a third-party solution:

在这些日子里,Arrow也可以用作第三方解决方案:

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

#10


12  

If you don't want to use dateutil, you can try this function:

如果您不想使用dateutil,可以尝试以下功能:

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)

Test:

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

Result:

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

#11


10  

If you are working with Django, it provides the dateparse module that accepts a bunch of formats similar to ISO format, including the time zone.

如果你正在使用Django,它提供了dateparse模块,它接受一系列类似于ISO格式的格式,包括时区。

If you are not using Django and you don't want to use one of the other libraries mentioned here, you could probably adapt the Django source code for dateparse to your project.

如果您不使用Django而您不想使用此处提到的其他库,则可以将dateparse的Django源代码调整到您的项目中。

#12


8  

It is so much simpler than you all are making it.

它比你们所有人都要简单得多。

If you want to get the seconds since epoch, you can use python-dateutil to convert it to a datetime object and then convert it so seconds using the strftime method. Like so:

如果你想获得epoch以来的秒数,你可以使用python-dateutil将它转换为datetime对象,然后使用strftime方法将其转换为秒。像这样:

>>> import dateutil.parser as dp
>>> t = '1984-06-02T19:05:00.000Z'
>>> parsed_t = dp.parse(t)
>>> t_in_seconds = parsed_t.strftime('%s')
>>> t_in_seconds
'455051100'

Source

Note: This will convert the given datetime into epoch time. But you can use the strftime() function to convert that datetime into any format. The parsed_t object here is of type datetime at this point.

注意:这会将给定的日期时间转换为纪元时间。但是您可以使用strftime()函数将该日期时间转换为任何格式。此处的parsed_t对象此时的类型为datetime。

#13


7  

I'm the author of iso8601 utils. It can be found on GitHub or on PyPI. Here's how you can parse your example:

我是iso8601 utils的作者。它可以在GitHub或PyPI上找到。以下是解析示例的方法:

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

#14


6  

I have found ciso8601 to be the fastest way to parse ISO 8601 timestamps. As the name suggests, it is implemented in C.

我发现ciso8601是解析ISO 8601时间戳的最快方法。顾名思义,它是用C实现的。

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

The GitHub Repo README shows their >10x speedup versus all of the other libraries listed in the other answers.

与其他答案中列出的所有其他库相比,GitHub Repo README显示其速度提高了10倍。

My personal project involved a lot of ISO 8601 parsing. It was nice to be able to just switch the call and go 10x faster. :)

我的个人项目涉及很多ISO 8601解析。很高兴能够只是拨打电话并快10倍。 :)

Edit: I have since become a maintainer of ciso8601. It's now faster than ever!

编辑:我已成为ciso8601的维护者。现在比以往任何时候都快!

#15


6  

I've coded up a parser for the ISO 8601 standard and put it on GitHub: https://github.com/boxed/iso8601. This implementation supports everything in the specification except for durations, intervals, periodic intervals, and dates outside the supported date range of Python's datetime module.

我为ISO 8601标准编写了一个解析器并将其放在GitHub上:https://github.com/boxed/iso8601。此实现支持规范中的所有内容,但持续时间,间隔,周期性间隔以及Python日期时间模块支持的日期范围之外的日期除外。

Tests are included! :P

测试包括在内! :P

#16


6  

Django's parse_datetime() function supports dates with UTC offsets:

Django的parse_datetime()函数支持UTC偏移的日期:

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

So it could be used for parsing ISO 8601 dates in fields within entire project:

因此,它可用于在整个项目中的字段中解析ISO 8601日期:

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

#17


3  

One straightforward way to convert an ISO 8601-like date string to a UNIX timestamp or datetime.datetime object in all supported Python versions without installing third-party modules is to use the date parser of SQLite.

在不安装第三方模块的情况下,在所有支持的Python版本中将类似ISO 8601的日期字符串转换为UNIX时间戳或datetime.datetime对象的一种简单方法是使用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)

Output:

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

#18


3  

Because ISO 8601 allows many variations of optional colons and dashes being present, basically CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. If you want to use strptime, you need to strip out those variations first.

The goal is to generate a utc datetime object.

因为ISO 8601允许存在可选冒号和破折号的许多变体,所以基本上CCYY-MM-DDThh:mm:ss [Z |(+ | - )hh:mm]。如果你想使用strptime,你需要先删除这些变化。目标是生成一个utc datetime对象。


If you just want a basic case that work for UTC with the Z suffix like 2016-06-29T19:36:29.3453Z:

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


If you want to handle timezone offsets like 2016-06-29T19:36:29.3453-0400 or 2008-09-03T20:56:35.450686+05:00 use the following. These will convert all variations into something without variable delimiters like 20080903T205635.450686+0500 making it more consistent/easier to parse.

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


If your system does not support the %z strptime directive (you see something like ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z') then you need to manually offset the time from Z (UTC). Note %z may not work on your system in python versions < 3 as it depended on the c library support which varies across system/python build type (i.e. Jython, Cython, etc.).

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

#19


2  

For something that works with the 2.X standard library try:

对于适用于2.X标准库的内容,请尝试:

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

calendar.timegm is the missing gm version of time.mktime.

calendar.timegm是time.mktime缺少的gm版本。

#20


2  

The python-dateutil will throw an exception if parsing invalid date strings, so you may want to catch the exception.

如果解析无效的日期字符串,python-dateutil将抛出异常,因此您可能希望捕获异常。

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

#21


2  

Nowadays there's Maya: Datetimes for Humans™, from the author of the popular Requests: HTTP for Humans™ package:

现在有Maya:Humans™的日期时间,来自流行的请求: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>)

#22


2  

This works for stdlib on Python 3.2 onwards (assuming all the timestamps are UTC):

这适用于Python 3.2以上的stdlib(假设所有时间戳都是UTC):

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

For example,

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

#23


1  

Thanks to great Mark Amery's answer I devised function to account for all possible ISO formats of datetime:

感谢伟大的Mark Amery的回答,我设计了函数来解释日期时间的所有可能的ISO格式:

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

#24


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)

Note that we should look if the string doesn't ends with Z, we could parse using %z.

请注意,我们应该查看字符串是否以Z结尾,我们可以使用%z进行解析。

#1


340  

The python-dateutil package can parse not only RFC 3339 datetime strings like the one in the question, but also other ISO 8601 date and time strings that don't comply with RFC 3339 (such as ones with no UTC offset, or ones that represent only a date).

python-dateutil包不仅可以解析RFC 3339日期时间字符串,例如问题中的字符串,还可以解析不符合RFC 3339的其他ISO 8601日期和时间字符串(例如没有UTC偏移的字符串,或者代表只有约会)。

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

Be warned that the dateutil.parser is intentionally hacky: it tries to guess the format and makes inevitable assumptions (customizable by hand only) in ambiguous cases. So ONLY use it if you need to parse input of unknown format and are okay to tolerate occasional misreads. (thanks ivan_pozdeev)

请注意,dateutil.parser故意是hacky:它试图猜测格式并在不明确的情况下做出不可避免的假设(仅可手工定制)。因此,如果您需要解析未知格式的输入并且可以容忍偶尔的误读,那么只能使用它。 (感谢ivan_pozdeev)

The Pypi name is python-dateutil, not dateutil (thanks code3monk3y):

Pypi名称是python-dateutil,而不是dateutil(感谢code3monk3y):

pip install python-dateutil

If you're using Python 3.7, have a look at this answer about datetime.datetime.fromisoformat.

如果您使用的是Python 3.7,请查看有关datetime.datetime.fromisoformat的答案。

#2


130  

Note in Python 2.6+ and Py3K, the %f character catches microseconds.

注意在Python 2.6+和Py3K中,%f字符捕获微秒。

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

See issue here

请参阅此处的问题

#3


118  

Several answers here suggest using datetime.datetime.strptime to parse RFC 3339 or ISO 8601 datetimes with timezones, like the one exhibited in the question:

这里有几个答案建议使用datetime.datetime.strptime来解析带有时区的RFC 3339或ISO 8601日期时间,就像问题中展示的那样:

2008-09-03T20:56:35.450686Z

This is a bad idea.

这是一个坏主意。

Assuming that you want to support the full RFC 3339 format, including support for UTC offsets other than zero, then the code these answers suggest does not work. Indeed, it cannot work, because parsing RFC 3339 syntax using strptime is impossible. The format strings used by Python's datetime module are incapable of describing RFC 3339 syntax.

假设您想要支持完整的RFC 3339格式,包括支持除零以外的UTC偏移,那么这些答案建议的代码不起作用。实际上,它无法工作,因为使用strptime解析RFC 3339语法是不可能的。 Python的datetime模块使用的格式字符串无法描述RFC 3339语法。

The problem is UTC offsets. The RFC 3339 Internet Date/Time Format requires that every date-time includes a UTC offset, and that those offsets can either be Z (short for "Zulu time") or in +HH:MM or -HH:MM format, like +05:00 or -10:30.

问题是UTC偏移。 RFC 3339 Internet日期/时间格式要求每个日期时间包括UTC偏移量,并且这些偏移量可以是Z(“祖鲁时间”的缩写)或+ HH:MM或-HH:MM格式,如+ 05:00或-10:30

Consequently, these are all valid RFC 3339 datetimes:

因此,这些都是有效的RFC 3339日期时间:

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

Alas, the format strings used by strptime and strftime have no directive that corresponds to UTC offsets in RFC 3339 format. A complete list of the directives they support can be found at https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior, and the only UTC offset directive included in the list is %z:

唉,strptime和strftime使用的格式字符串没有与RFC 3339格式的UTC偏移相对应的指令。可以在https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior找到它们支持的指令的完整列表,列表中包含的唯一UTC偏移指令是% Z:

%z

UTC offset in the form +HHMM or -HHMM (empty string if the the object is naive).

UTC偏移量,格式为+ HHMM或-HHMM(如果对象是幼稚的,则为空字符串)。

Example: (empty), +0000, -0400, +1030

示例:(空),+ 0000,-0400,+ 1030

This doesn't match the format of an RFC 3339 offset, and indeed if we try to use %z in the format string and parse an RFC 3339 date, we'll fail:

这与RFC 3339偏移的格式不匹配,实际上如果我们尝试在格式字符串中使用%z并解析RFC 3339日期,我们将失败:

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

(Actually, the above is just what you'll see in Python 3. In Python 2 we'll fail for an even simpler reason, which is that strptime does not implement the %z directive at all in Python 2.)

(实际上,上面就是你在Python 3中看到的内容。在Python 2中,我们会因为一个更简单的原因而失败,这就是strptime在Python 2中根本没有实现%z指令。)

The multiple answers here that recommend strptime all work around this by including a literal Z in their format string, which matches the Z from the question asker's example datetime string (and discards it, producing a datetime object without a timezone):

这里推荐strptime的多个答案都是通过在其格式字符串中包含一个文字Z来解决这个问题,该字符串与问题提供者的示例日期时间字符串中的Z匹配(并丢弃它,生成没有时区的日期时间对象):

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

Since this discards timezone information that was included in the original datetime string, it's questionable whether we should regard even this result as correct. But more importantly, because this approach involves hard-coding a particular UTC offset into the format string, it will choke the moment it tries to parse any RFC 3339 datetime with a different UTC offset:

由于这会丢弃原始日期时间字符串中包含的时区信息,因此我们是否应该将此结果视为正确是值得怀疑的。但更重要的是,因为这种方法涉及将特定UTC偏移硬编码到格式字符串中,所以它会在尝试使用不同的UTC偏移量解析任何RFC 3339日期时间时阻塞:

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

Unless you're certain that you only need to support RFC 3339 datetimes in Zulu time, and not ones with other timezone offsets, don't use strptime. Use one of the many other approaches described in answers here instead.

除非你确定你只需要在祖鲁时间支持RFC 3339日期时间,而不是那些具有其他时区偏移的日期时间,否则不要使用strptime。请使用此处答案中描述的许多其他方法之一。

#4


67  

Try the iso8601 module; it does exactly this.

试试iso8601模块;它正是这样做的。

There are several other options mentioned on the WorkingWithTime page on the python.org wiki.

python.org wiki上的WorkingWithTime页面上提到了其他几个选项。

#5


40  

New in Python 3.7+


The datetime standard library introduced a function for inverting datetime.isoformat().

datetime标准库引入了一个用于反转datetime.isoformat()的函数。

classmethod datetime.fromisoformat(date_string):

Return a datetime corresponding to a date_string in one of the formats emitted by date.isoformat() and datetime.isoformat().

以date.isoformat()和datetime.isoformat()发出的格式之一返回与date_string对应的日期时间。

Specifically, this function supports strings in the format(s):

具体来说,此函数支持格式的字符串:

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

where * can match any single character.

其中*可以匹配任何单个字符。

Caution: This does not support parsing arbitrary ISO 8601 strings - it is only intended as the inverse operation of datetime.isoformat().

注意:这不支持解析任意ISO 8601字符串 - 它仅用作datetime.isoformat()的逆操作。

Example of use:

使用示例:

from datetime import datetime

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

#6


35  

import re,datetime
s="2008-09-03T20:56:35.450686Z"
d=datetime.datetime(*map(int, re.split('[^\d]', s)[:-1]))

#7


25  

What is the exact error you get? Is it like the following?

你得到的确切错误是什么?它像下面这样吗?

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

If yes, you can split your input string on ".", and then add the microseconds to the datetime you got.

如果是,您可以将输入字符串拆分为“。”,然后将微秒添加到您获得的日期时间。

Try this:

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

#8


19  

Starting from Python 3.7, strptime supports colon delimiters in UTC offsets (source). So you can then use:

从Python 3.7开始,strptime支持UTC偏移(源)中的冒号分隔符。所以你可以使用:

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

#9


19  

In these days, Arrow also can be used as a third-party solution:

在这些日子里,Arrow也可以用作第三方解决方案:

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

#10


12  

If you don't want to use dateutil, you can try this function:

如果您不想使用dateutil,可以尝试以下功能:

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)

Test:

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

Result:

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

#11


10  

If you are working with Django, it provides the dateparse module that accepts a bunch of formats similar to ISO format, including the time zone.

如果你正在使用Django,它提供了dateparse模块,它接受一系列类似于ISO格式的格式,包括时区。

If you are not using Django and you don't want to use one of the other libraries mentioned here, you could probably adapt the Django source code for dateparse to your project.

如果您不使用Django而您不想使用此处提到的其他库,则可以将dateparse的Django源代码调整到您的项目中。

#12


8  

It is so much simpler than you all are making it.

它比你们所有人都要简单得多。

If you want to get the seconds since epoch, you can use python-dateutil to convert it to a datetime object and then convert it so seconds using the strftime method. Like so:

如果你想获得epoch以来的秒数,你可以使用python-dateutil将它转换为datetime对象,然后使用strftime方法将其转换为秒。像这样:

>>> import dateutil.parser as dp
>>> t = '1984-06-02T19:05:00.000Z'
>>> parsed_t = dp.parse(t)
>>> t_in_seconds = parsed_t.strftime('%s')
>>> t_in_seconds
'455051100'

Source

Note: This will convert the given datetime into epoch time. But you can use the strftime() function to convert that datetime into any format. The parsed_t object here is of type datetime at this point.

注意:这会将给定的日期时间转换为纪元时间。但是您可以使用strftime()函数将该日期时间转换为任何格式。此处的parsed_t对象此时的类型为datetime。

#13


7  

I'm the author of iso8601 utils. It can be found on GitHub or on PyPI. Here's how you can parse your example:

我是iso8601 utils的作者。它可以在GitHub或PyPI上找到。以下是解析示例的方法:

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

#14


6  

I have found ciso8601 to be the fastest way to parse ISO 8601 timestamps. As the name suggests, it is implemented in C.

我发现ciso8601是解析ISO 8601时间戳的最快方法。顾名思义,它是用C实现的。

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

The GitHub Repo README shows their >10x speedup versus all of the other libraries listed in the other answers.

与其他答案中列出的所有其他库相比,GitHub Repo README显示其速度提高了10倍。

My personal project involved a lot of ISO 8601 parsing. It was nice to be able to just switch the call and go 10x faster. :)

我的个人项目涉及很多ISO 8601解析。很高兴能够只是拨打电话并快10倍。 :)

Edit: I have since become a maintainer of ciso8601. It's now faster than ever!

编辑:我已成为ciso8601的维护者。现在比以往任何时候都快!

#15


6  

I've coded up a parser for the ISO 8601 standard and put it on GitHub: https://github.com/boxed/iso8601. This implementation supports everything in the specification except for durations, intervals, periodic intervals, and dates outside the supported date range of Python's datetime module.

我为ISO 8601标准编写了一个解析器并将其放在GitHub上:https://github.com/boxed/iso8601。此实现支持规范中的所有内容,但持续时间,间隔,周期性间隔以及Python日期时间模块支持的日期范围之外的日期除外。

Tests are included! :P

测试包括在内! :P

#16


6  

Django's parse_datetime() function supports dates with UTC offsets:

Django的parse_datetime()函数支持UTC偏移的日期:

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

So it could be used for parsing ISO 8601 dates in fields within entire project:

因此,它可用于在整个项目中的字段中解析ISO 8601日期:

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

#17


3  

One straightforward way to convert an ISO 8601-like date string to a UNIX timestamp or datetime.datetime object in all supported Python versions without installing third-party modules is to use the date parser of SQLite.

在不安装第三方模块的情况下,在所有支持的Python版本中将类似ISO 8601的日期字符串转换为UNIX时间戳或datetime.datetime对象的一种简单方法是使用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)

Output:

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

#18


3  

Because ISO 8601 allows many variations of optional colons and dashes being present, basically CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]. If you want to use strptime, you need to strip out those variations first.

The goal is to generate a utc datetime object.

因为ISO 8601允许存在可选冒号和破折号的许多变体,所以基本上CCYY-MM-DDThh:mm:ss [Z |(+ | - )hh:mm]。如果你想使用strptime,你需要先删除这些变化。目标是生成一个utc datetime对象。


If you just want a basic case that work for UTC with the Z suffix like 2016-06-29T19:36:29.3453Z:

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


If you want to handle timezone offsets like 2016-06-29T19:36:29.3453-0400 or 2008-09-03T20:56:35.450686+05:00 use the following. These will convert all variations into something without variable delimiters like 20080903T205635.450686+0500 making it more consistent/easier to parse.

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


If your system does not support the %z strptime directive (you see something like ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z') then you need to manually offset the time from Z (UTC). Note %z may not work on your system in python versions < 3 as it depended on the c library support which varies across system/python build type (i.e. Jython, Cython, etc.).

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

#19


2  

For something that works with the 2.X standard library try:

对于适用于2.X标准库的内容,请尝试:

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

calendar.timegm is the missing gm version of time.mktime.

calendar.timegm是time.mktime缺少的gm版本。

#20


2  

The python-dateutil will throw an exception if parsing invalid date strings, so you may want to catch the exception.

如果解析无效的日期字符串,python-dateutil将抛出异常,因此您可能希望捕获异常。

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

#21


2  

Nowadays there's Maya: Datetimes for Humans™, from the author of the popular Requests: HTTP for Humans™ package:

现在有Maya:Humans™的日期时间,来自流行的请求: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>)

#22


2  

This works for stdlib on Python 3.2 onwards (assuming all the timestamps are UTC):

这适用于Python 3.2以上的stdlib(假设所有时间戳都是UTC):

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

For example,

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

#23


1  

Thanks to great Mark Amery's answer I devised function to account for all possible ISO formats of datetime:

感谢伟大的Mark Amery的回答,我设计了函数来解释日期时间的所有可能的ISO格式:

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

#24


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)

Note that we should look if the string doesn't ends with Z, we could parse using %z.

请注意,我们应该查看字符串是否以Z结尾,我们可以使用%z进行解析。