File: //home/arjun/projects/env/lib64/python3.10/site-packages/pendulum/parsing/__init__.py
import copy
import os
import re
import struct
from datetime import date
from datetime import datetime
from datetime import time
from dateutil import parser
from .exceptions import ParserError
with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"
try:
if not with_extensions or struct.calcsize("P") == 4:
raise ImportError()
from ._iso8601 import parse_iso8601
except ImportError:
from .iso8601 import parse_iso8601
COMMON = re.compile(
# Date (optional)
"^"
"(?P<date>"
" (?P<classic>" # Classic date (YYYY-MM-DD)
r" (?P<year>\d{4})" # Year
" (?P<monthday>"
r" (?P<monthsep>[/:])?(?P<month>\d{2})" # Month (optional)
r" ((?P<daysep>[/:])?(?P<day>\d{2}))" # Day (optional)
" )?"
" )"
")?"
# Time (optional)
"(?P<time>"
r" (?P<timesep>\ )?" # Separator (space)
r" (?P<hour>\d{1,2}):(?P<minute>\d{1,2})?(?::(?P<second>\d{1,2}))?" # HH:mm:ss (optional mm and ss)
# Subsecond part (optional)
" (?P<subsecondsection>"
" (?:[.|,])" # Subsecond separator (optional)
r" (?P<subsecond>\d{1,9})" # Subsecond
" )?"
")?"
"$",
re.VERBOSE,
)
DEFAULT_OPTIONS = {
"day_first": False,
"year_first": True,
"strict": True,
"exact": False,
"now": None,
}
def parse(text, **options):
"""
Parses a string with the given options.
:param text: The string to parse.
:type text: str
:rtype: Parsed
"""
_options = copy.copy(DEFAULT_OPTIONS)
_options.update(options)
return _normalize(_parse(text, **_options), **_options)
def _normalize(parsed, **options):
"""
Normalizes the parsed element.
:param parsed: The parsed elements.
:type parsed: Parsed
:rtype: Parsed
"""
if options.get("exact"):
return parsed
if isinstance(parsed, time):
now = options["now"] or datetime.now()
return datetime(
now.year,
now.month,
now.day,
parsed.hour,
parsed.minute,
parsed.second,
parsed.microsecond,
)
elif isinstance(parsed, date) and not isinstance(parsed, datetime):
return datetime(parsed.year, parsed.month, parsed.day)
return parsed
def _parse(text, **options):
# Trying to parse ISO8601
try:
return parse_iso8601(text)
except ValueError:
pass
try:
return _parse_iso8601_interval(text)
except ValueError:
pass
try:
return _parse_common(text, **options)
except ParserError:
pass
# We couldn't parse the string
# so we fallback on the dateutil parser
# If not strict
if options.get("strict", True):
raise ParserError("Unable to parse string [{}]".format(text))
try:
dt = parser.parse(
text, dayfirst=options["day_first"], yearfirst=options["year_first"]
)
except ValueError:
raise ParserError("Invalid date string: {}".format(text))
return dt
def _parse_common(text, **options):
"""
Tries to parse the string as a common datetime format.
:param text: The string to parse.
:type text: str
:rtype: dict or None
"""
m = COMMON.match(text)
has_date = False
year = 0
month = 1
day = 1
if not m:
raise ParserError("Invalid datetime string")
if m.group("date"):
# A date has been specified
has_date = True
year = int(m.group("year"))
if not m.group("monthday"):
# No month and day
month = 1
day = 1
else:
if options["day_first"]:
month = int(m.group("day"))
day = int(m.group("month"))
else:
month = int(m.group("month"))
day = int(m.group("day"))
if not m.group("time"):
return date(year, month, day)
# Grabbing hh:mm:ss
hour = int(m.group("hour"))
minute = int(m.group("minute"))
if m.group("second"):
second = int(m.group("second"))
else:
second = 0
# Grabbing subseconds, if any
microsecond = 0
if m.group("subsecondsection"):
# Limiting to 6 chars
subsecond = m.group("subsecond")[:6]
microsecond = int("{:0<6}".format(subsecond))
if has_date:
return datetime(year, month, day, hour, minute, second, microsecond)
return time(hour, minute, second, microsecond)
class _Interval:
"""
Special class to handle ISO 8601 intervals
"""
def __init__(self, start=None, end=None, duration=None):
self.start = start
self.end = end
self.duration = duration
def _parse_iso8601_interval(text):
if "/" not in text:
raise ParserError("Invalid interval")
first, last = text.split("/")
start = end = duration = None
if first[0] == "P":
# duration/end
duration = parse_iso8601(first)
end = parse_iso8601(last)
elif last[0] == "P":
# start/duration
start = parse_iso8601(first)
duration = parse_iso8601(last)
else:
# start/end
start = parse_iso8601(first)
end = parse_iso8601(last)
return _Interval(start, end, duration)