Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/parsing/__init__.py: 21%
103 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1import copy
2import os
3import re
4import struct
6from datetime import date
7from datetime import datetime
8from datetime import time
10from dateutil import parser
12from .exceptions import ParserError
15with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1"
17try:
18 if not with_extensions or struct.calcsize("P") == 4:
19 raise ImportError()
21 from ._iso8601 import parse_iso8601
22except ImportError:
23 from .iso8601 import parse_iso8601
26COMMON = re.compile(
27 # Date (optional)
28 "^"
29 "(?P<date>"
30 " (?P<classic>" # Classic date (YYYY-MM-DD)
31 r" (?P<year>\d{4})" # Year
32 " (?P<monthday>"
33 r" (?P<monthsep>[/:])?(?P<month>\d{2})" # Month (optional)
34 r" ((?P<daysep>[/:])?(?P<day>\d{2}))" # Day (optional)
35 " )?"
36 " )"
37 ")?"
38 # Time (optional)
39 "(?P<time>"
40 r" (?P<timesep>\ )?" # Separator (space)
41 r" (?P<hour>\d{1,2}):(?P<minute>\d{1,2})?(?::(?P<second>\d{1,2}))?" # HH:mm:ss (optional mm and ss)
42 # Subsecond part (optional)
43 " (?P<subsecondsection>"
44 " (?:[.|,])" # Subsecond separator (optional)
45 r" (?P<subsecond>\d{1,9})" # Subsecond
46 " )?"
47 ")?"
48 "$",
49 re.VERBOSE,
50)
53DEFAULT_OPTIONS = {
54 "day_first": False,
55 "year_first": True,
56 "strict": True,
57 "exact": False,
58 "now": None,
59}
62def parse(text, **options):
63 """
64 Parses a string with the given options.
66 :param text: The string to parse.
67 :type text: str
69 :rtype: Parsed
70 """
71 _options = copy.copy(DEFAULT_OPTIONS)
72 _options.update(options)
74 return _normalize(_parse(text, **_options), **_options)
77def _normalize(parsed, **options):
78 """
79 Normalizes the parsed element.
81 :param parsed: The parsed elements.
82 :type parsed: Parsed
84 :rtype: Parsed
85 """
86 if options.get("exact"):
87 return parsed
89 if isinstance(parsed, time):
90 now = options["now"] or datetime.now()
92 return datetime(
93 now.year,
94 now.month,
95 now.day,
96 parsed.hour,
97 parsed.minute,
98 parsed.second,
99 parsed.microsecond,
100 )
101 elif isinstance(parsed, date) and not isinstance(parsed, datetime):
102 return datetime(parsed.year, parsed.month, parsed.day)
104 return parsed
107def _parse(text, **options):
108 # Trying to parse ISO8601
109 try:
110 return parse_iso8601(text)
111 except ValueError:
112 pass
114 try:
115 return _parse_iso8601_interval(text)
116 except ValueError:
117 pass
119 try:
120 return _parse_common(text, **options)
121 except ParserError:
122 pass
124 # We couldn't parse the string
125 # so we fallback on the dateutil parser
126 # If not strict
127 if options.get("strict", True):
128 raise ParserError("Unable to parse string [{}]".format(text))
130 try:
131 dt = parser.parse(
132 text, dayfirst=options["day_first"], yearfirst=options["year_first"]
133 )
134 except ValueError:
135 raise ParserError("Invalid date string: {}".format(text))
137 return dt
140def _parse_common(text, **options):
141 """
142 Tries to parse the string as a common datetime format.
144 :param text: The string to parse.
145 :type text: str
147 :rtype: dict or None
148 """
149 m = COMMON.match(text)
150 has_date = False
151 year = 0
152 month = 1
153 day = 1
155 if not m:
156 raise ParserError("Invalid datetime string")
158 if m.group("date"):
159 # A date has been specified
160 has_date = True
162 year = int(m.group("year"))
164 if not m.group("monthday"):
165 # No month and day
166 month = 1
167 day = 1
168 else:
169 if options["day_first"]:
170 month = int(m.group("day"))
171 day = int(m.group("month"))
172 else:
173 month = int(m.group("month"))
174 day = int(m.group("day"))
176 if not m.group("time"):
177 return date(year, month, day)
179 # Grabbing hh:mm:ss
180 hour = int(m.group("hour"))
182 minute = int(m.group("minute"))
184 if m.group("second"):
185 second = int(m.group("second"))
186 else:
187 second = 0
189 # Grabbing subseconds, if any
190 microsecond = 0
191 if m.group("subsecondsection"):
192 # Limiting to 6 chars
193 subsecond = m.group("subsecond")[:6]
195 microsecond = int("{:0<6}".format(subsecond))
197 if has_date:
198 return datetime(year, month, day, hour, minute, second, microsecond)
200 return time(hour, minute, second, microsecond)
203class _Interval:
204 """
205 Special class to handle ISO 8601 intervals
206 """
208 def __init__(self, start=None, end=None, duration=None):
209 self.start = start
210 self.end = end
211 self.duration = duration
214def _parse_iso8601_interval(text):
215 if "/" not in text:
216 raise ParserError("Invalid interval")
218 first, last = text.split("/")
219 start = end = duration = None
221 if first[0] == "P":
222 # duration/end
223 duration = parse_iso8601(first)
224 end = parse_iso8601(last)
225 elif last[0] == "P":
226 # start/duration
227 start = parse_iso8601(first)
228 duration = parse_iso8601(last)
229 else:
230 # start/end
231 start = parse_iso8601(first)
232 end = parse_iso8601(last)
234 return _Interval(start, end, duration)