Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/period.py: 32%

190 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1from __future__ import absolute_import 

2 

3import operator 

4 

5from datetime import date 

6from datetime import datetime 

7from datetime import timedelta 

8 

9import pendulum 

10 

11from pendulum.utils._compat import _HAS_FOLD 

12from pendulum.utils._compat import decode 

13 

14from .constants import MONTHS_PER_YEAR 

15from .duration import Duration 

16from .helpers import precise_diff 

17 

18 

19class Period(Duration): 

20 """ 

21 Duration class that is aware of the datetimes that generated the 

22 time difference. 

23 """ 

24 

25 def __new__(cls, start, end, absolute=False): 

26 if isinstance(start, datetime) and isinstance(end, datetime): 

27 if ( 

28 start.tzinfo is None 

29 and end.tzinfo is not None 

30 or start.tzinfo is not None 

31 and end.tzinfo is None 

32 ): 

33 raise TypeError("can't compare offset-naive and offset-aware datetimes") 

34 

35 if absolute and start > end: 

36 end, start = start, end 

37 

38 _start = start 

39 _end = end 

40 if isinstance(start, pendulum.DateTime): 

41 if _HAS_FOLD: 

42 _start = datetime( 

43 start.year, 

44 start.month, 

45 start.day, 

46 start.hour, 

47 start.minute, 

48 start.second, 

49 start.microsecond, 

50 tzinfo=start.tzinfo, 

51 fold=start.fold, 

52 ) 

53 else: 

54 _start = datetime( 

55 start.year, 

56 start.month, 

57 start.day, 

58 start.hour, 

59 start.minute, 

60 start.second, 

61 start.microsecond, 

62 tzinfo=start.tzinfo, 

63 ) 

64 elif isinstance(start, pendulum.Date): 

65 _start = date(start.year, start.month, start.day) 

66 

67 if isinstance(end, pendulum.DateTime): 

68 if _HAS_FOLD: 

69 _end = datetime( 

70 end.year, 

71 end.month, 

72 end.day, 

73 end.hour, 

74 end.minute, 

75 end.second, 

76 end.microsecond, 

77 tzinfo=end.tzinfo, 

78 fold=end.fold, 

79 ) 

80 else: 

81 _end = datetime( 

82 end.year, 

83 end.month, 

84 end.day, 

85 end.hour, 

86 end.minute, 

87 end.second, 

88 end.microsecond, 

89 tzinfo=end.tzinfo, 

90 ) 

91 elif isinstance(end, pendulum.Date): 

92 _end = date(end.year, end.month, end.day) 

93 

94 # Fixing issues with datetime.__sub__() 

95 # not handling offsets if the tzinfo is the same 

96 if ( 

97 isinstance(_start, datetime) 

98 and isinstance(_end, datetime) 

99 and _start.tzinfo is _end.tzinfo 

100 ): 

101 if _start.tzinfo is not None: 

102 _start = (_start - start.utcoffset()).replace(tzinfo=None) 

103 

104 if isinstance(end, datetime) and _end.tzinfo is not None: 

105 _end = (_end - end.utcoffset()).replace(tzinfo=None) 

106 

107 delta = _end - _start 

108 

109 return super(Period, cls).__new__(cls, seconds=delta.total_seconds()) 

110 

111 def __init__(self, start, end, absolute=False): 

112 super(Period, self).__init__() 

113 

114 if not isinstance(start, pendulum.Date): 

115 if isinstance(start, datetime): 

116 start = pendulum.instance(start) 

117 else: 

118 start = pendulum.date(start.year, start.month, start.day) 

119 

120 _start = start 

121 else: 

122 if isinstance(start, pendulum.DateTime): 

123 _start = datetime( 

124 start.year, 

125 start.month, 

126 start.day, 

127 start.hour, 

128 start.minute, 

129 start.second, 

130 start.microsecond, 

131 tzinfo=start.tzinfo, 

132 ) 

133 else: 

134 _start = date(start.year, start.month, start.day) 

135 

136 if not isinstance(end, pendulum.Date): 

137 if isinstance(end, datetime): 

138 end = pendulum.instance(end) 

139 else: 

140 end = pendulum.date(end.year, end.month, end.day) 

141 

142 _end = end 

143 else: 

144 if isinstance(end, pendulum.DateTime): 

145 _end = datetime( 

146 end.year, 

147 end.month, 

148 end.day, 

149 end.hour, 

150 end.minute, 

151 end.second, 

152 end.microsecond, 

153 tzinfo=end.tzinfo, 

154 ) 

155 else: 

156 _end = date(end.year, end.month, end.day) 

157 

158 self._invert = False 

159 if start > end: 

160 self._invert = True 

161 

162 if absolute: 

163 end, start = start, end 

164 _end, _start = _start, _end 

165 

166 self._absolute = absolute 

167 self._start = start 

168 self._end = end 

169 self._delta = precise_diff(_start, _end) 

170 

171 @property 

172 def years(self): 

173 return self._delta.years 

174 

175 @property 

176 def months(self): 

177 return self._delta.months 

178 

179 @property 

180 def weeks(self): 

181 return abs(self._delta.days) // 7 * self._sign(self._delta.days) 

182 

183 @property 

184 def days(self): 

185 return self._days 

186 

187 @property 

188 def remaining_days(self): 

189 return abs(self._delta.days) % 7 * self._sign(self._days) 

190 

191 @property 

192 def hours(self): 

193 return self._delta.hours 

194 

195 @property 

196 def minutes(self): 

197 return self._delta.minutes 

198 

199 @property 

200 def start(self): 

201 return self._start 

202 

203 @property 

204 def end(self): 

205 return self._end 

206 

207 def in_years(self): 

208 """ 

209 Gives the duration of the Period in full years. 

210 

211 :rtype: int 

212 """ 

213 return self.years 

214 

215 def in_months(self): 

216 """ 

217 Gives the duration of the Period in full months. 

218 

219 :rtype: int 

220 """ 

221 return self.years * MONTHS_PER_YEAR + self.months 

222 

223 def in_weeks(self): 

224 days = self.in_days() 

225 sign = 1 

226 

227 if days < 0: 

228 sign = -1 

229 

230 return sign * (abs(days) // 7) 

231 

232 def in_days(self): 

233 return self._delta.total_days 

234 

235 def in_words(self, locale=None, separator=" "): 

236 """ 

237 Get the current interval in words in the current locale. 

238 

239 Ex: 6 jours 23 heures 58 minutes 

240 

241 :param locale: The locale to use. Defaults to current locale. 

242 :type locale: str 

243 

244 :param separator: The separator to use between each unit 

245 :type separator: str 

246 

247 :rtype: str 

248 """ 

249 periods = [ 

250 ("year", self.years), 

251 ("month", self.months), 

252 ("week", self.weeks), 

253 ("day", self.remaining_days), 

254 ("hour", self.hours), 

255 ("minute", self.minutes), 

256 ("second", self.remaining_seconds), 

257 ] 

258 

259 if locale is None: 

260 locale = pendulum.get_locale() 

261 

262 locale = pendulum.locale(locale) 

263 parts = [] 

264 for period in periods: 

265 unit, count = period 

266 if abs(count) > 0: 

267 translation = locale.translation( 

268 "units.{}.{}".format(unit, locale.plural(abs(count))) 

269 ) 

270 parts.append(translation.format(count)) 

271 

272 if not parts: 

273 if abs(self.microseconds) > 0: 

274 unit = "units.second.{}".format(locale.plural(1)) 

275 count = "{:.2f}".format(abs(self.microseconds) / 1e6) 

276 else: 

277 unit = "units.microsecond.{}".format(locale.plural(0)) 

278 count = 0 

279 translation = locale.translation(unit) 

280 parts.append(translation.format(count)) 

281 

282 return decode(separator.join(parts)) 

283 

284 def range(self, unit, amount=1): 

285 method = "add" 

286 op = operator.le 

287 if not self._absolute and self.invert: 

288 method = "subtract" 

289 op = operator.ge 

290 

291 start, end = self.start, self.end 

292 

293 i = amount 

294 while op(start, end): 

295 yield start 

296 

297 start = getattr(self.start, method)(**{unit: i}) 

298 

299 i += amount 

300 

301 def as_interval(self): 

302 """ 

303 Return the Period as an Duration. 

304 

305 :rtype: Duration 

306 """ 

307 return Duration(seconds=self.total_seconds()) 

308 

309 def __iter__(self): 

310 return self.range("days") 

311 

312 def __contains__(self, item): 

313 return self.start <= item <= self.end 

314 

315 def __add__(self, other): 

316 return self.as_interval().__add__(other) 

317 

318 __radd__ = __add__ 

319 

320 def __sub__(self, other): 

321 return self.as_interval().__sub__(other) 

322 

323 def __neg__(self): 

324 return self.__class__(self.end, self.start, self._absolute) 

325 

326 def __mul__(self, other): 

327 return self.as_interval().__mul__(other) 

328 

329 __rmul__ = __mul__ 

330 

331 def __floordiv__(self, other): 

332 return self.as_interval().__floordiv__(other) 

333 

334 def __truediv__(self, other): 

335 return self.as_interval().__truediv__(other) 

336 

337 __div__ = __floordiv__ 

338 

339 def __mod__(self, other): 

340 return self.as_interval().__mod__(other) 

341 

342 def __divmod__(self, other): 

343 return self.as_interval().__divmod__(other) 

344 

345 def __abs__(self): 

346 return self.__class__(self.start, self.end, True) 

347 

348 def __repr__(self): 

349 return "<Period [{} -> {}]>".format(self._start, self._end) 

350 

351 def __str__(self): 

352 return self.__repr__() 

353 

354 def _cmp(self, other): 

355 # Only needed for PyPy 

356 assert isinstance(other, timedelta) 

357 

358 if isinstance(other, Period): 

359 other = other.as_timedelta() 

360 

361 td = self.as_timedelta() 

362 

363 return 0 if td == other else 1 if td > other else -1 

364 

365 def _getstate(self, protocol=3): 

366 start, end = self.start, self.end 

367 

368 if self._invert and self._absolute: 

369 end, start = start, end 

370 

371 return (start, end, self._absolute) 

372 

373 def __reduce__(self): 

374 return self.__reduce_ex__(2) 

375 

376 def __reduce_ex__(self, protocol): 

377 return self.__class__, self._getstate(protocol) 

378 

379 def __hash__(self): 

380 return hash((self.start, self.end, self._absolute)) 

381 

382 def __eq__(self, other): 

383 if isinstance(other, Period): 

384 return (self.start, self.end, self._absolute) == ( 

385 other.start, 

386 other.end, 

387 other._absolute, 

388 ) 

389 else: 

390 return self.as_interval() == other