How do I parse an ISO 8601-formatted date?

后端 未结 27 2305
小鲜肉
小鲜肉 2020-11-21 06:08

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

I have found strptime in the Python sta

相关标签:
27条回答
  • 2020-11-21 06:31

    I have found ciso8601 to be the fastest way to parse ISO 8601 timestamps. As the name suggests, it is implemented in 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.

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

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

    0 讨论(0)
  • 2020-11-21 06:31

    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.

    #!/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
    
    0 讨论(0)
  • 2020-11-21 06:31

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

    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:

    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')
    
    0 讨论(0)
  • 2020-11-21 06:31

    Thanks to great Mark Amery's answer I devised function to account for all possible ISO formats of 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 讨论(0)
  • 2020-11-21 06:33

    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)
    
    0 讨论(0)
  • 2020-11-21 06:35

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

    >>> from iso8601utils import parsers
    >>> parsers.datetime('2008-09-03T20:56:35.450686Z')
    datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
    
    0 讨论(0)
提交回复
热议问题