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

1import copy 

2import os 

3import re 

4import struct 

5 

6from datetime import date 

7from datetime import datetime 

8from datetime import time 

9 

10from dateutil import parser 

11 

12from .exceptions import ParserError 

13 

14 

15with_extensions = os.getenv("PENDULUM_EXTENSIONS", "1") == "1" 

16 

17try: 

18 if not with_extensions or struct.calcsize("P") == 4: 

19 raise ImportError() 

20 

21 from ._iso8601 import parse_iso8601 

22except ImportError: 

23 from .iso8601 import parse_iso8601 

24 

25 

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) 

51 

52 

53DEFAULT_OPTIONS = { 

54 "day_first": False, 

55 "year_first": True, 

56 "strict": True, 

57 "exact": False, 

58 "now": None, 

59} 

60 

61 

62def parse(text, **options): 

63 """ 

64 Parses a string with the given options. 

65 

66 :param text: The string to parse. 

67 :type text: str 

68 

69 :rtype: Parsed 

70 """ 

71 _options = copy.copy(DEFAULT_OPTIONS) 

72 _options.update(options) 

73 

74 return _normalize(_parse(text, **_options), **_options) 

75 

76 

77def _normalize(parsed, **options): 

78 """ 

79 Normalizes the parsed element. 

80 

81 :param parsed: The parsed elements. 

82 :type parsed: Parsed 

83 

84 :rtype: Parsed 

85 """ 

86 if options.get("exact"): 

87 return parsed 

88 

89 if isinstance(parsed, time): 

90 now = options["now"] or datetime.now() 

91 

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) 

103 

104 return parsed 

105 

106 

107def _parse(text, **options): 

108 # Trying to parse ISO8601 

109 try: 

110 return parse_iso8601(text) 

111 except ValueError: 

112 pass 

113 

114 try: 

115 return _parse_iso8601_interval(text) 

116 except ValueError: 

117 pass 

118 

119 try: 

120 return _parse_common(text, **options) 

121 except ParserError: 

122 pass 

123 

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)) 

129 

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)) 

136 

137 return dt 

138 

139 

140def _parse_common(text, **options): 

141 """ 

142 Tries to parse the string as a common datetime format. 

143 

144 :param text: The string to parse. 

145 :type text: str 

146 

147 :rtype: dict or None 

148 """ 

149 m = COMMON.match(text) 

150 has_date = False 

151 year = 0 

152 month = 1 

153 day = 1 

154 

155 if not m: 

156 raise ParserError("Invalid datetime string") 

157 

158 if m.group("date"): 

159 # A date has been specified 

160 has_date = True 

161 

162 year = int(m.group("year")) 

163 

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")) 

175 

176 if not m.group("time"): 

177 return date(year, month, day) 

178 

179 # Grabbing hh:mm:ss 

180 hour = int(m.group("hour")) 

181 

182 minute = int(m.group("minute")) 

183 

184 if m.group("second"): 

185 second = int(m.group("second")) 

186 else: 

187 second = 0 

188 

189 # Grabbing subseconds, if any 

190 microsecond = 0 

191 if m.group("subsecondsection"): 

192 # Limiting to 6 chars 

193 subsecond = m.group("subsecond")[:6] 

194 

195 microsecond = int("{:0<6}".format(subsecond)) 

196 

197 if has_date: 

198 return datetime(year, month, day, hour, minute, second, microsecond) 

199 

200 return time(hour, minute, second, microsecond) 

201 

202 

203class _Interval: 

204 """ 

205 Special class to handle ISO 8601 intervals 

206 """ 

207 

208 def __init__(self, start=None, end=None, duration=None): 

209 self.start = start 

210 self.end = end 

211 self.duration = duration 

212 

213 

214def _parse_iso8601_interval(text): 

215 if "/" not in text: 

216 raise ParserError("Invalid interval") 

217 

218 first, last = text.split("/") 

219 start = end = duration = None 

220 

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) 

233 

234 return _Interval(start, end, duration)