Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/aniso8601/interval.py: 61%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

146 statements  

1# -*- coding: utf-8 -*- 

2 

3# Copyright (c) 2021, Brandon Nielsen 

4# All rights reserved. 

5# 

6# This software may be modified and distributed under the terms 

7# of the BSD license. See the LICENSE file for details. 

8 

9from aniso8601.builders import DatetimeTuple, DateTuple, TupleBuilder 

10from aniso8601.builders.python import PythonTimeBuilder 

11from aniso8601.compat import is_string 

12from aniso8601.date import parse_date 

13from aniso8601.duration import parse_duration 

14from aniso8601.exceptions import ISOFormatError 

15from aniso8601.resolution import IntervalResolution 

16from aniso8601.time import parse_datetime, parse_time 

17 

18 

19def get_interval_resolution( 

20 isointervalstr, intervaldelimiter="/", datetimedelimiter="T" 

21): 

22 isointervaltuple = parse_interval( 

23 isointervalstr, 

24 intervaldelimiter=intervaldelimiter, 

25 datetimedelimiter=datetimedelimiter, 

26 builder=TupleBuilder, 

27 ) 

28 

29 return _get_interval_resolution(isointervaltuple) 

30 

31 

32def get_repeating_interval_resolution( 

33 isointervalstr, intervaldelimiter="/", datetimedelimiter="T" 

34): 

35 repeatingintervaltuple = parse_repeating_interval( 

36 isointervalstr, 

37 intervaldelimiter=intervaldelimiter, 

38 datetimedelimiter=datetimedelimiter, 

39 builder=TupleBuilder, 

40 ) 

41 

42 return _get_interval_resolution(repeatingintervaltuple.interval) 

43 

44 

45def _get_interval_resolution(intervaltuple): 

46 if intervaltuple.start is not None and intervaltuple.end is not None: 

47 return max( 

48 _get_interval_component_resolution(intervaltuple.start), 

49 _get_interval_component_resolution(intervaltuple.end), 

50 ) 

51 

52 if intervaltuple.start is not None and intervaltuple.duration is not None: 

53 return max( 

54 _get_interval_component_resolution(intervaltuple.start), 

55 _get_interval_component_resolution(intervaltuple.duration), 

56 ) 

57 

58 return max( 

59 _get_interval_component_resolution(intervaltuple.end), 

60 _get_interval_component_resolution(intervaltuple.duration), 

61 ) 

62 

63 

64def _get_interval_component_resolution(componenttuple): 

65 if type(componenttuple) is DateTuple: 

66 if componenttuple.DDD is not None: 

67 # YYYY-DDD 

68 # YYYYDDD 

69 return IntervalResolution.Ordinal 

70 

71 if componenttuple.D is not None: 

72 # YYYY-Www-D 

73 # YYYYWwwD 

74 return IntervalResolution.Weekday 

75 

76 if componenttuple.Www is not None: 

77 # YYYY-Www 

78 # YYYYWww 

79 return IntervalResolution.Week 

80 

81 if componenttuple.DD is not None: 

82 # YYYY-MM-DD 

83 # YYYYMMDD 

84 return IntervalResolution.Day 

85 

86 if componenttuple.MM is not None: 

87 # YYYY-MM 

88 return IntervalResolution.Month 

89 

90 # Y[YYY] 

91 return IntervalResolution.Year 

92 elif type(componenttuple) is DatetimeTuple: 

93 # Datetime 

94 if componenttuple.time.ss is not None: 

95 return IntervalResolution.Seconds 

96 

97 if componenttuple.time.mm is not None: 

98 return IntervalResolution.Minutes 

99 

100 return IntervalResolution.Hours 

101 

102 # Duration 

103 if componenttuple.TnS is not None: 

104 return IntervalResolution.Seconds 

105 

106 if componenttuple.TnM is not None: 

107 return IntervalResolution.Minutes 

108 

109 if componenttuple.TnH is not None: 

110 return IntervalResolution.Hours 

111 

112 if componenttuple.PnD is not None: 

113 return IntervalResolution.Day 

114 

115 if componenttuple.PnW is not None: 

116 return IntervalResolution.Week 

117 

118 if componenttuple.PnM is not None: 

119 return IntervalResolution.Month 

120 

121 return IntervalResolution.Year 

122 

123 

124def parse_interval( 

125 isointervalstr, 

126 intervaldelimiter="/", 

127 datetimedelimiter="T", 

128 builder=PythonTimeBuilder, 

129): 

130 # Given a string representing an ISO 8601 interval, return an 

131 # interval built by the given builder. Valid formats are: 

132 # 

133 # <start>/<end> 

134 # <start>/<duration> 

135 # <duration>/<end> 

136 # 

137 # The <start> and <end> values can represent dates, or datetimes, 

138 # not times. 

139 # 

140 # The format: 

141 # 

142 # <duration> 

143 # 

144 # Is expressly not supported as there is no way to provide the additional 

145 # required context. 

146 

147 if is_string(isointervalstr) is False: 

148 raise ValueError("Interval must be string.") 

149 

150 if len(isointervalstr) == 0: 

151 raise ISOFormatError("Interval string is empty.") 

152 

153 if isointervalstr[0] == "R": 

154 raise ISOFormatError( 

155 "ISO 8601 repeating intervals must be parsed " 

156 "with parse_repeating_interval." 

157 ) 

158 

159 intervaldelimitercount = isointervalstr.count(intervaldelimiter) 

160 

161 if intervaldelimitercount == 0: 

162 raise ISOFormatError( 

163 'Interval delimiter "{0}" is not in interval ' 

164 'string "{1}".'.format(intervaldelimiter, isointervalstr) 

165 ) 

166 

167 if intervaldelimitercount > 1: 

168 raise ISOFormatError( 

169 "{0} is not a valid ISO 8601 interval".format(isointervalstr) 

170 ) 

171 

172 return _parse_interval( 

173 isointervalstr, builder, intervaldelimiter, datetimedelimiter 

174 ) 

175 

176 

177def parse_repeating_interval( 

178 isointervalstr, 

179 intervaldelimiter="/", 

180 datetimedelimiter="T", 

181 builder=PythonTimeBuilder, 

182): 

183 # Given a string representing an ISO 8601 interval repeating, return an 

184 # interval built by the given builder. Valid formats are: 

185 # 

186 # Rnn/<interval> 

187 # R/<interval> 

188 

189 if not isinstance(isointervalstr, str): 

190 raise ValueError("Interval must be string.") 

191 

192 if len(isointervalstr) == 0: 

193 raise ISOFormatError("Repeating interval string is empty.") 

194 

195 if isointervalstr[0] != "R": 

196 raise ISOFormatError("ISO 8601 repeating interval must start " "with an R.") 

197 

198 if intervaldelimiter not in isointervalstr: 

199 raise ISOFormatError( 

200 'Interval delimiter "{0}" is not in interval ' 

201 'string "{1}".'.format(intervaldelimiter, isointervalstr) 

202 ) 

203 

204 # Parse the number of iterations 

205 iterationpart, intervalpart = isointervalstr.split(intervaldelimiter, 1) 

206 

207 if len(iterationpart) > 1: 

208 R = False 

209 Rnn = iterationpart[1:] 

210 else: 

211 R = True 

212 Rnn = None 

213 

214 interval = _parse_interval( 

215 intervalpart, TupleBuilder, intervaldelimiter, datetimedelimiter 

216 ) 

217 

218 return builder.build_repeating_interval(R=R, Rnn=Rnn, interval=interval) 

219 

220 

221def _parse_interval( 

222 isointervalstr, builder, intervaldelimiter="/", datetimedelimiter="T" 

223): 

224 # Returns a tuple containing the start of the interval, the end of the 

225 # interval, and or the interval duration 

226 

227 firstpart, secondpart = isointervalstr.split(intervaldelimiter) 

228 

229 if len(firstpart) == 0 or len(secondpart) == 0: 

230 raise ISOFormatError( 

231 "{0} is not a valid ISO 8601 interval".format(isointervalstr) 

232 ) 

233 

234 if firstpart[0] == "P": 

235 # <duration>/<end> 

236 # Notice that these are not returned 'in order' (earlier to later), this 

237 # is to maintain consistency with parsing <start>/<end> durations, as 

238 # well as making repeating interval code cleaner. Users who desire 

239 # durations to be in order can use the 'sorted' operator. 

240 duration = parse_duration(firstpart, builder=TupleBuilder) 

241 

242 # We need to figure out if <end> is a date, or a datetime 

243 if secondpart.find(datetimedelimiter) != -1: 

244 # <end> is a datetime 

245 endtuple = parse_datetime( 

246 secondpart, delimiter=datetimedelimiter, builder=TupleBuilder 

247 ) 

248 else: 

249 endtuple = parse_date(secondpart, builder=TupleBuilder) 

250 

251 return builder.build_interval(end=endtuple, duration=duration) 

252 elif secondpart[0] == "P": 

253 # <start>/<duration> 

254 # We need to figure out if <start> is a date, or a datetime 

255 duration = parse_duration(secondpart, builder=TupleBuilder) 

256 

257 if firstpart.find(datetimedelimiter) != -1: 

258 # <start> is a datetime 

259 starttuple = parse_datetime( 

260 firstpart, delimiter=datetimedelimiter, builder=TupleBuilder 

261 ) 

262 else: 

263 # <start> must just be a date 

264 starttuple = parse_date(firstpart, builder=TupleBuilder) 

265 

266 return builder.build_interval(start=starttuple, duration=duration) 

267 

268 # <start>/<end> 

269 if firstpart.find(datetimedelimiter) != -1: 

270 # Both parts are datetimes 

271 starttuple = parse_datetime( 

272 firstpart, delimiter=datetimedelimiter, builder=TupleBuilder 

273 ) 

274 else: 

275 starttuple = parse_date(firstpart, builder=TupleBuilder) 

276 

277 endtuple = _parse_interval_end(secondpart, starttuple, datetimedelimiter) 

278 

279 return builder.build_interval(start=starttuple, end=endtuple) 

280 

281 

282def _parse_interval_end(endstr, starttuple, datetimedelimiter): 

283 datestr = None 

284 timestr = None 

285 

286 monthstr = None 

287 daystr = None 

288 

289 concise = False 

290 

291 if type(starttuple) is DateTuple: 

292 startdatetuple = starttuple 

293 else: 

294 # Start is a datetime 

295 startdatetuple = starttuple.date 

296 

297 if datetimedelimiter in endstr: 

298 datestr, timestr = endstr.split(datetimedelimiter, 1) 

299 elif ":" in endstr: 

300 timestr = endstr 

301 else: 

302 datestr = endstr 

303 

304 if timestr is not None: 

305 endtimetuple = parse_time(timestr, builder=TupleBuilder) 

306 

307 # End is just a time 

308 if datestr is None: 

309 return endtimetuple 

310 

311 # Handle backwards concise representation 

312 if datestr.count("-") == 1: 

313 monthstr, daystr = datestr.split("-") 

314 concise = True 

315 elif len(datestr) <= 2: 

316 daystr = datestr 

317 concise = True 

318 elif len(datestr) <= 4: 

319 monthstr = datestr[0:2] 

320 daystr = datestr[2:] 

321 concise = True 

322 

323 if concise is True: 

324 concisedatestr = startdatetuple.YYYY 

325 

326 # Separators required because concise elements may be missing digits 

327 if monthstr is not None: 

328 concisedatestr += "-" + monthstr 

329 elif startdatetuple.MM is not None: 

330 concisedatestr += "-" + startdatetuple.MM 

331 

332 concisedatestr += "-" + daystr 

333 

334 enddatetuple = parse_date(concisedatestr, builder=TupleBuilder) 

335 

336 # Clear unsupplied components 

337 if monthstr is None: 

338 enddatetuple = TupleBuilder.build_date(DD=enddatetuple.DD) 

339 else: 

340 # Year not provided 

341 enddatetuple = TupleBuilder.build_date( 

342 MM=enddatetuple.MM, DD=enddatetuple.DD 

343 ) 

344 else: 

345 enddatetuple = parse_date(datestr, builder=TupleBuilder) 

346 

347 if timestr is None: 

348 return enddatetuple 

349 

350 return TupleBuilder.build_datetime(enddatetuple, endtimetuple)