Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pendulum/parsing/__init__.py : 30%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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)