Muszę przeanalizować ciągi znaków RFC 3339 jak "2008-09-03T20:56:35.450686Z"
w datetime
typie Python .
Znalazłem strptime
w standardowej bibliotece Pythona, ale nie jest to zbyt wygodne.
Jak najlepiej to zrobić?
Muszę przeanalizować ciągi znaków RFC 3339 jak "2008-09-03T20:56:35.450686Z"
w datetime
typie Python .
Znalazłem strptime
w standardowej bibliotece Pythona, ale nie jest to zbyt wygodne.
Jak najlepiej to zrobić?
Odpowiedzi:
Pakiet python-dateutil może analizować nie tylko ciągi daty i godziny RFC 3339, jak ten w pytaniu, ale także inne ciągi daty i godziny ISO 8601 , które nie są zgodne z RFC 3339 (takie jak te bez przesunięcia UTC lub te, które reprezentują tylko randka).
>>> 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)
Zauważ, że dateutil.parser.isoparse
jest to prawdopodobnie bardziej rygorystyczne niż bardziej hackerskie dateutil.parser.parse
, ale oba są dość wybaczające i będą próbowały zinterpretować przekazany ciąg. Jeśli chcesz wyeliminować możliwość jakichkolwiek błędnych odczytów, musisz użyć czegoś surowszego niż którekolwiek z tych Funkcje.
Nazwa Pypi to python-dateutil
nie dateutil
(dzięki code3monk3y ):
pip install python-dateutil
Jeśli używasz języka Python 3.7, zapoznaj się z tą odpowiedzią na temat datetime.datetime.fromisoformat
.
python-dateutil
nie dateutil
, tak: pip install python-dateutil
.
dateutil.parser
jest celowo włamany: próbuje odgadnąć format i przyjmuje nieuniknione założenia (konfigurowalne tylko ręcznie) w niejednoznacznych przypadkach. Używaj go TYLKO, jeśli chcesz przeanalizować dane wejściowe o nieznanym formacie i dobrze jest tolerować okazjonalne błędne odczytywanie.
datetime
Standardowa biblioteka wprowadziła funkcję odwracania datetime.isoformat()
.
classmethod
datetime.fromisoformat(date_string)
:Zwraca
datetime
odpowiadający adate_string
w jednym z formatów emitowanych przezdate.isoformat()
idatetime.isoformat()
.W szczególności ta funkcja obsługuje ciągi w formacie (formatach):
YYYY-MM-DD[*HH[:MM[:SS[.mmm[mmm]]]][+HH:MM[:SS[.ffffff]]]]
gdzie
*
można dopasować dowolny pojedynczy znak.Uwaga : To nie obsługuje parsowania dowolnych ciągów ISO 8601 - jest przeznaczone tylko do działania odwrotnego
datetime.isoformat()
.
Przykład zastosowania:
from datetime import datetime
date = datetime.fromisoformat('2017-01-01T12:30:59.000000')
datetime
może zawierać tzinfo
strefę czasową, a tym samym wyświetla strefę czasową, ale datetime.fromisoformat()
nie analizuje tzinfo? wygląda jak błąd ..
isoformat
. Nie akceptuje przykładu w pytaniu z "2008-09-03T20:56:35.450686Z"
powodu końcowego Z
, ale akceptuje "2008-09-03T20:56:35.450686"
.
Z
skrypt wejściowy, można go zmodyfikować za pomocą date_string.replace("Z", "+00:00")
.
Uwaga w Pythonie 2.6+ i Py3K znak% f przechwytuje mikrosekundy.
>>> datetime.datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
Zobacz problem tutaj
strptime
jest w rzeczywistości niemożliwe.
datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f')
więc to
Kilka odpowiedzi tutaj sugeruje użycie datetime.datetime.strptime
do parsowania czasów danych RFC 3339 lub ISO 8601 ze strefami czasowymi, takich jak ta pokazana w pytaniu:
2008-09-03T20:56:35.450686Z
To jest zły pomysł.
Zakładając, że chcesz obsługiwać pełny format RFC 3339, w tym obsługę przesunięć UTC innych niż zero, kod sugerowany przez te odpowiedzi nie działa. Rzeczywiście, nie może działać, ponieważ analiza składni RFC 3339 przy użyciu strptime
jest niemożliwa. Ciągi formatujące używane przez moduł datetime Pythona nie są w stanie opisać składni RFC 3339.
Problemem są przesunięcia UTC. RFC 3339 Internet Date / Time Format wymaga, aby każda data-czas zawiera UTC offset, i że te przesunięcia mogą być albo Z
(skrót od „czasu Zulu”) lub w +HH:MM
lub -HH:MM
formatu, jak +05:00
i -10:30
.
W związku z tym wszystkie są poprawnymi danymi RFC 3339:
2008-09-03T20:56:35.450686Z
2008-09-03T20:56:35.450686+05:00
2008-09-03T20:56:35.450686-10:30
Niestety, ciągi formatu używane przez strptime
i strftime
nie mają dyrektywy, która odpowiada przesunięciom UTC w formacie RFC 3339. Pełna lista obsługiwanych przez nie dyrektyw znajduje się na stronie https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior , a jedyną dyrektywą offsetową UTC zawartą na liście jest %z
:
% z
Przesunięcie UTC w postaci + HHMM lub -HHMM (pusty ciąg, jeśli obiekt jest naiwny).
Przykład: (pusty), +0000, -0400, +1030
To nie pasuje do formatu przesunięcia RFC 3339, a jeśli spróbujemy użyć %z
w ciągu formatu i przeanalizować datę RFC 3339, nie powiedzie się:
>>> 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'
(W rzeczywistości powyższe jest dokładnie tym, co zobaczysz w Pythonie 3. W Pythonie 2 nie powiedzie się z jeszcze prostszego powodu, to strptime
znaczy,%z
że w ogóle nie implementuje dyrektywy w Pythonie 2 ).
Wiele odpowiedzi tutaj, które zalecają strptime
obejście tego problemu, poprzez dołączenie literału Z
w łańcuchu formatu, który odpowiada Z
przykładowemu ciągowi datetime zapytania pytającego (i odrzuca go, tworząc datetime
obiekt bez strefy czasowej):
>>> datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686)
Ponieważ powoduje to odrzucenie informacji o strefie czasowej zawartych w oryginalnym ciągu daty i godziny, wątpliwe jest, czy nawet ten wynik należy uznać za poprawny. Ale co ważniejsze, ponieważ takie podejście wymaga zakodowania na stałe określonego przesunięcia UTC w ciągu formatu , zadławi się w chwili, gdy spróbuje parsować dowolną datę / godzinę RFC 3339 z innym przesunięciem UTC:
>>> 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'
Jeśli nie masz pewności , że potrzebujesz obsługi danych RFC 3339 tylko w czasie Zulu, a nie tych z innymi przesunięciami stref czasowych, nie używaj strptime
. Zamiast tego użyj jednego z wielu innych podejść opisanych w odpowiedziach tutaj.
strptime()
w Pythonie 3.7 obsługuje teraz wszystko opisane w tej odpowiedzi jako niemożliwe (literał „Z” i „:” w przesunięciu strefy czasowej). Niestety, istnieje inny przypadek narożny, który sprawia, że RFC 3339 jest zasadniczo niezgodny z ISO 8601, a mianowicie ten pierwszy pozwala na ujemne przesunięcie strefy czasowej zerowej -00: 00, a później nie.
Wypróbuj moduł iso8601 ; robi dokładnie to.
Istnieje kilka innych opcji wymienionych na stronie WorkingWithTime na wiki python.org.
iso8601.parse_date("2008-09-03T20:56:35.450686Z")
import re, 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))
Jaki dokładnie otrzymujesz błąd? Czy to wygląda następująco?
>>> 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
Jeśli tak, możesz podzielić ciąg wejściowy na „.”, A następnie dodać mikrosekundy do otrzymanego czasu danych.
Spróbuj tego:
>>> 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)
""
lub "Z"
, to musi być przesunięciem w godzinach / minutach, które można bezpośrednio dodawać / odejmować od obiektu datetime. Państwo mogłoby stworzyć tzinfo podklasy, aby go obsługiwać, ale to chyba nie polecamy.
Począwszy od Python 3.7, strptime obsługuje ograniczniki jelita grubego w przesunięciach UTC ( źródło ). Możesz więc użyć:
import datetime
datetime.datetime.strptime('2018-01-31T09:24:31.488670+00:00', '%Y-%m-%dT%H:%M:%S.%f%z')
EDYTOWAĆ:
Jak zauważył Martijn, jeśli utworzyłeś obiekt datetime za pomocą isoformat (), możesz po prostu użyć datetime.fromisoformat ()
datetime.fromisoformat()
który obsługuje ciągi jak twój wprowadzania automatycznie: datetime.datetime.isoformat('2018-01-31T09:24:31.488670+00:00')
.
datetime.fromisoformat()
idatetime.isoformat()
W tych dniach, Arrow może być również używany jako rozwiązanie zewnętrzne:
>>> import arrow
>>> date = arrow.get("2008-09-03T20:56:35.450686Z")
>>> date.datetime
datetime.datetime(2008, 9, 3, 20, 56, 35, 450686, tzinfo=tzutc())
Wystarczy użyć python-dateutil
modułu:
>>> 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
(sprawdzone na epochconverter.com ) ,,, chyba że czegoś brakuje?
Jeśli nie chcesz używać dateutil, możesz wypróbować tę funkcję:
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")
Wynik:
datetime.datetime(2007, 3, 4, 21, 8, 12, 123000)
strptime
. Jest to zły pomysł, ponieważ nie uda mu się parsować daty i godziny z innym przesunięciem UTC i nie zgłosi wyjątku. Zobacz moją odpowiedź, która opisuje, w jaki sposób parsowanie RFC 3339 ze strptime jest w rzeczywistości niemożliwe.
toISOString
metodą JavaScript . Ale w tej odpowiedzi nie ma wzmianki o ograniczeniu dat Zulu, ani pytanie nie wskazywało, że to wszystko, co jest potrzebne, a samo użycie dateutil
jest zwykle równie wygodne i mniej wąskie w tym, co może przeanalizować.
Jeśli pracujesz z Django, udostępnia on moduł dateparse który akceptuje wiele formatów podobnych do formatu ISO, w tym strefę czasową.
Jeśli nie korzystasz z Django i nie chcesz używać żadnej z wymienionych tu bibliotek, prawdopodobnie możesz dostosować kod źródłowy Django dla dateparse do swojego projektu.
DateTimeField
używa tego podczas ustawiania wartości ciągu.
Odkryłem, że ciso8601 jest najszybszym sposobem na analizowanie znaczników czasu ISO 8601. Jak sama nazwa wskazuje, jest zaimplementowany w C.
import ciso8601
ciso8601.parse_datetime('2014-01-09T21:48:00.921000+05:30')
The GitHub Repo README pokazuje ich> 10x przyspieszenie w porównaniu do wszystkich innych bibliotek wymienionych w innych odpowiedzi.
Mój osobisty projekt wymagał dużej analizy ISO 8601. Miło było po prostu przełączyć rozmowę i przejść 10 razy szybciej. :)
Edycja: Od tego czasu stałem się opiekunem ciso8601. Jest teraz szybszy niż kiedykolwiek!
datetime.strptime()
jest kolejnym najszybszym rozwiązaniem. Dziękujemy za połączenie wszystkich tych informacji!
datetime.strptime()
nie jest to pełna biblioteka parsująca ISO 8601. Jeśli korzystasz z języka Python 3.7, możesz użyć datetime.fromisoformat()
metody, która jest nieco bardziej elastyczna. Być może zainteresuje Cię ta bardziej kompletna lista parserów, która wkrótce powinna zostać scalona z ciso8601 README.
Działa to dla stdlib w Pythonie 3.2 i nowszych (zakładając, że wszystkie znaczniki czasu to UTC):
from datetime import datetime, timezone, timedelta
datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%fZ").replace(
tzinfo=timezone(timedelta(0)))
Na przykład,
>>> datetime.utcnow().replace(tzinfo=timezone(timedelta(0)))
... datetime.datetime(2015, 3, 11, 6, 2, 47, 879129, tzinfo=datetime.timezone.utc)
strptime
. Jest to zły pomysł, ponieważ nie uda mu się parsować daty i godziny z innym przesunięciem UTC i nie zgłosi wyjątku. Zobacz moją odpowiedź, która opisuje, w jaki sposób parsowanie RFC 3339 ze strptime jest w rzeczywistości niemożliwe.
timezone.utc
zamiast timezone(timedelta(0))
. Również prace kodu w Pythonie 2.6+ (co najmniej), jeśli dostarczy utc
tzinfo obiektu
%Z
strefy czasowej for w najnowszych wersjach Pythona.
Jednym prostym sposobem na konwersję łańcucha daty podobnego do ISO 8601 na znacznik czasu lub datetime.datetime
obiekt UNIX we wszystkich obsługiwanych wersjach Pythona bez instalowania modułów innych firm jest użycie analizatora daty 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)
Wynik:
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
Zakodowałem parser dla standardu ISO 8601 i umieściłem go w GitHub: https://github.com/boxed/iso8601 . Ta implementacja obsługuje wszystko w specyfikacji, z wyjątkiem czasów trwania, interwałów, interwałów okresowych i dat poza obsługiwanym zakresem dat modułu datetime modułu Python.
Testy są włączone! : P
Funkcja parse_datetime () Django obsługuje daty z przesunięciami UTC:
parse_datetime('2016-08-09T15:12:03.65478Z') =
datetime.datetime(2016, 8, 9, 15, 12, 3, 654780, tzinfo=<UTC>)
Można go zatem wykorzystać do analizy dat ISO 8601 w polach w ramach całego projektu:
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')
Ponieważ ISO 8601 pozwala zasadniczo na wiele odmian opcjonalnych dwukropków i myślników CCYY-MM-DDThh:mm:ss[Z|(+|-)hh:mm]
. Jeśli chcesz użyć Strptime, musisz najpierw usunąć te odmiany.
Celem jest wygenerowanie obiektu 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
lub 2008-09-03T20:56:35.450686+05:00
skorzystaj z poniższych. Spowodują to przekształcenie wszystkich odmian w coś bez ograniczników zmiennych, takich jak 20080903T205635.450686+0500
uczynienie go bardziej spójnym / łatwiejszym do parsowania.
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
dyrektywy Strptime (widzisz coś takiego ValueError: 'z' is a bad directive in format '%Y%m%dT%H%M%S.%f%z'
), musisz ręcznie przesunąć czas od Z
(UTC). Uwaga %z
może nie działać w systemie w wersjach języka Python <3, ponieważ zależała od obsługi biblioteki c, która różni się w zależności od typu kompilacji system / python (tj. Jython, Cython itp.).
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
Aby uzyskać coś, co działa ze standardową biblioteką 2.X, spróbuj:
calendar.timegm(time.strptime(date.split(".")[0]+"UTC", "%Y-%m-%dT%H:%M:%S%Z"))
calendar.timegm to brakująca wersja gm time.mktime.
Python-dateutil zgłosi wyjątek, jeśli parsuje niepoprawne ciągi daty, więc możesz chcieć złapać wyjątek.
from dateutil import parser
ds = '2012-60-31'
try:
dt = parser.parse(ds)
except ValueError, e:
print '"%s" is an invalid date' % ds
Obecnie jest Maya: Datetimes for Humans ™ , autor popularnego pakietu Requests: 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>)
Innym sposobem jest użycie specjalistycznego parser ISO-8601 jest użycie isoparse funkcję dateutil parsera:
from dateutil import parser
date = parser.isoparse("2008-09-03T20:56:35.450686+01:00")
print(date)
Wynik:
2008-09-03 20:56:35.450686+01:00
Ta funkcja jest również wspomniana w dokumentacji standardowej funkcji Python datetime.fromisoformat :
Bardziej funkcjonalny parser ISO 8601, dateutil.parser.isoparse jest dostępny w pakiecie dateutil innej firmy.
Dzięki wielkiej odpowiedzi Marka Ameryka opracowałem funkcję rozliczania wszystkich możliwych formatów ISO daty / godziny:
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)
Zauważ, że powinniśmy sprawdzić, czy ciąg nie kończy się na Z
, możemy przeanalizować za pomocą %z
.
Początkowo próbowałem z:
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)
Ale to nie działało na ujemnych strefach czasowych. To jednak działało dobrze w Pythonie 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)
W niektórych testach należy zauważyć, że wyjście różni się jedynie precyzją w mikrosekundach. Mam 6 cyfr precyzji na moim komputerze, ale 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(('+', '-'))
? Czy normalny krotek nie powinien ('+', '-')
być w stanie osiągnąć tego samego?