132 lines
3.3 KiB
Python
132 lines
3.3 KiB
Python
import re
|
|
|
|
from datetime import date
|
|
from datetime import datetime
|
|
from datetime import time
|
|
from datetime import timedelta
|
|
|
|
|
|
from ._compat import decode
|
|
from ._compat import timezone
|
|
|
|
RFC_3339_LOOSE = re.compile(
|
|
"^"
|
|
r"(([0-9]+)-(\d{2})-(\d{2}))?" # Date
|
|
"("
|
|
"([T ])?" # Separator
|
|
r"(\d{2}):(\d{2}):(\d{2})(\.([0-9]+))?" # Time
|
|
r"((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
|
|
")?"
|
|
"$"
|
|
)
|
|
|
|
RFC_3339_DATETIME = re.compile(
|
|
"^"
|
|
"([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" # Date
|
|
"[T ]" # Separator
|
|
r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?" # Time
|
|
r"((Z)|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
|
|
"$"
|
|
)
|
|
|
|
RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$")
|
|
|
|
RFC_3339_TIME = re.compile(
|
|
r"^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?$"
|
|
)
|
|
|
|
_utc = timezone(timedelta(), "UTC")
|
|
|
|
|
|
def parse_rfc3339(string): # type: (str) -> Union[datetime, date, time]
|
|
m = RFC_3339_DATETIME.match(string)
|
|
if m:
|
|
year = int(m.group(1))
|
|
month = int(m.group(2))
|
|
day = int(m.group(3))
|
|
hour = int(m.group(4))
|
|
minute = int(m.group(5))
|
|
second = int(m.group(6))
|
|
microsecond = 0
|
|
|
|
if m.group(7):
|
|
microsecond = int(("{:<06s}".format(m.group(8)))[:6])
|
|
|
|
dt = datetime(year, month, day, hour, minute, second, microsecond)
|
|
|
|
if m.group(9):
|
|
# Timezone
|
|
tz = m.group(9)
|
|
if tz == "Z":
|
|
tzinfo = _utc
|
|
else:
|
|
sign = m.group(11)[0]
|
|
hour_offset, minute_offset = int(m.group(12)), int(m.group(13))
|
|
offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60)
|
|
if sign == "-":
|
|
offset = -offset
|
|
|
|
tzinfo = timezone(
|
|
offset, "{}{}:{}".format(sign, m.group(12), m.group(13))
|
|
)
|
|
|
|
return datetime(
|
|
year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
|
|
)
|
|
else:
|
|
return datetime(year, month, day, hour, minute, second, microsecond)
|
|
|
|
m = RFC_3339_DATE.match(string)
|
|
if m:
|
|
year = int(m.group(1))
|
|
month = int(m.group(2))
|
|
day = int(m.group(3))
|
|
|
|
return date(year, month, day)
|
|
|
|
m = RFC_3339_TIME.match(string)
|
|
if m:
|
|
hour = int(m.group(1))
|
|
minute = int(m.group(2))
|
|
second = int(m.group(3))
|
|
microsecond = 0
|
|
|
|
if m.group(4):
|
|
microsecond = int(("{:<06s}".format(m.group(5)))[:6])
|
|
|
|
return time(hour, minute, second, microsecond)
|
|
|
|
raise ValueError("Invalid RFC 339 string")
|
|
|
|
|
|
_escaped = {"b": "\b", "t": "\t", "n": "\n", "f": "\f", "r": "\r", '"': '"', "\\": "\\"}
|
|
_escapes = {v: k for k, v in _escaped.items()}
|
|
|
|
|
|
def escape_string(s):
|
|
s = decode(s)
|
|
|
|
res = []
|
|
start = 0
|
|
|
|
def flush():
|
|
if start != i:
|
|
res.append(s[start:i])
|
|
|
|
return i + 1
|
|
|
|
i = 0
|
|
while i < len(s):
|
|
c = s[i]
|
|
if c in '"\\\n\r\t\b\f':
|
|
start = flush()
|
|
res.append("\\" + _escapes[c])
|
|
elif ord(c) < 0x20:
|
|
start = flush()
|
|
res.append("\\u%04x" % ord(c))
|
|
i += 1
|
|
|
|
flush()
|
|
|
|
return "".join(res)
|