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

216 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-30 06:11 +0000

1from __future__ import annotations 

2 

3import operator 

4 

5from datetime import date 

6from datetime import datetime 

7from datetime import timedelta 

8from typing import TYPE_CHECKING 

9from typing import Iterator 

10from typing import Union 

11from typing import cast 

12from typing import overload 

13 

14import pendulum 

15 

16from pendulum.constants import MONTHS_PER_YEAR 

17from pendulum.duration import Duration 

18from pendulum.helpers import precise_diff 

19 

20 

21if TYPE_CHECKING: 

22 from typing_extensions import Self 

23 from typing_extensions import SupportsIndex 

24 

25 from pendulum.helpers import PreciseDiff 

26 from pendulum.locales.locale import Locale 

27 

28 

29class Interval(Duration): 

30 """ 

31 A period of time between two datetimes. 

32 """ 

33 

34 @overload 

35 def __new__( 

36 cls, 

37 start: pendulum.DateTime | datetime, 

38 end: pendulum.DateTime | datetime, 

39 absolute: bool = False, 

40 ) -> Self: 

41 ... 

42 

43 @overload 

44 def __new__( 

45 cls, 

46 start: pendulum.Date | date, 

47 end: pendulum.Date | date, 

48 absolute: bool = False, 

49 ) -> Self: 

50 ... 

51 

52 def __new__( 

53 cls, 

54 start: pendulum.DateTime | pendulum.Date | datetime | date, 

55 end: pendulum.DateTime | pendulum.Date | datetime | date, 

56 absolute: bool = False, 

57 ) -> Self: 

58 if ( 

59 isinstance(start, datetime) 

60 and not isinstance(end, datetime) 

61 or not isinstance(start, datetime) 

62 and isinstance(end, datetime) 

63 ): 

64 raise ValueError("Both start and end of a Period must have the same type") 

65 

66 if ( 

67 isinstance(start, datetime) 

68 and isinstance(end, datetime) 

69 and ( 

70 start.tzinfo is None 

71 and end.tzinfo is not None 

72 or start.tzinfo is not None 

73 and end.tzinfo is None 

74 ) 

75 ): 

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

77 

78 if absolute and start > end: 

79 end, start = start, end 

80 

81 _start = start 

82 _end = end 

83 if isinstance(start, pendulum.DateTime): 

84 _start = datetime( 

85 start.year, 

86 start.month, 

87 start.day, 

88 start.hour, 

89 start.minute, 

90 start.second, 

91 start.microsecond, 

92 tzinfo=start.tzinfo, 

93 fold=start.fold, 

94 ) 

95 elif isinstance(start, pendulum.Date): 

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

97 

98 if isinstance(end, pendulum.DateTime): 

99 _end = datetime( 

100 end.year, 

101 end.month, 

102 end.day, 

103 end.hour, 

104 end.minute, 

105 end.second, 

106 end.microsecond, 

107 tzinfo=end.tzinfo, 

108 fold=end.fold, 

109 ) 

110 elif isinstance(end, pendulum.Date): 

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

112 

113 # Fixing issues with datetime.__sub__() 

114 # not handling offsets if the tzinfo is the same 

115 if ( 

116 isinstance(_start, datetime) 

117 and isinstance(_end, datetime) 

118 and _start.tzinfo is _end.tzinfo 

119 ): 

120 if _start.tzinfo is not None: 

121 offset = cast(timedelta, cast(datetime, start).utcoffset()) 

122 _start = (_start - offset).replace(tzinfo=None) 

123 

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

125 offset = cast(timedelta, end.utcoffset()) 

126 _end = (_end - offset).replace(tzinfo=None) 

127 

128 delta: timedelta = _end - _start # type: ignore[operator] 

129 

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

131 

132 def __init__( 

133 self, 

134 start: pendulum.DateTime | pendulum.Date | datetime | date, 

135 end: pendulum.DateTime | pendulum.Date | datetime | date, 

136 absolute: bool = False, 

137 ) -> None: 

138 super().__init__() 

139 

140 _start: pendulum.DateTime | pendulum.Date | datetime | date 

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

142 if isinstance(start, datetime): 

143 start = pendulum.instance(start) 

144 else: 

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

146 

147 _start = start 

148 else: 

149 if isinstance(start, pendulum.DateTime): 

150 _start = datetime( 

151 start.year, 

152 start.month, 

153 start.day, 

154 start.hour, 

155 start.minute, 

156 start.second, 

157 start.microsecond, 

158 tzinfo=start.tzinfo, 

159 ) 

160 else: 

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

162 

163 _end: pendulum.DateTime | pendulum.Date | datetime | date 

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

165 if isinstance(end, datetime): 

166 end = pendulum.instance(end) 

167 else: 

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

169 

170 _end = end 

171 else: 

172 if isinstance(end, pendulum.DateTime): 

173 _end = datetime( 

174 end.year, 

175 end.month, 

176 end.day, 

177 end.hour, 

178 end.minute, 

179 end.second, 

180 end.microsecond, 

181 tzinfo=end.tzinfo, 

182 ) 

183 else: 

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

185 

186 self._invert = False 

187 if start > end: 

188 self._invert = True 

189 

190 if absolute: 

191 end, start = start, end 

192 _end, _start = _start, _end 

193 

194 self._absolute = absolute 

195 self._start: pendulum.DateTime | pendulum.Date = start 

196 self._end: pendulum.DateTime | pendulum.Date = end 

197 self._delta: PreciseDiff = precise_diff(_start, _end) 

198 

199 @property 

200 def years(self) -> int: 

201 return self._delta.years 

202 

203 @property 

204 def months(self) -> int: 

205 return self._delta.months 

206 

207 @property 

208 def weeks(self) -> int: 

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

210 

211 @property 

212 def days(self) -> int: 

213 return self._days 

214 

215 @property 

216 def remaining_days(self) -> int: 

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

218 

219 @property 

220 def hours(self) -> int: 

221 return self._delta.hours 

222 

223 @property 

224 def minutes(self) -> int: 

225 return self._delta.minutes 

226 

227 @property 

228 def start(self) -> pendulum.DateTime | pendulum.Date | datetime | date: 

229 return self._start 

230 

231 @property 

232 def end(self) -> pendulum.DateTime | pendulum.Date | datetime | date: 

233 return self._end 

234 

235 def in_years(self) -> int: 

236 """ 

237 Gives the duration of the Period in full years. 

238 """ 

239 return self.years 

240 

241 def in_months(self) -> int: 

242 """ 

243 Gives the duration of the Period in full months. 

244 """ 

245 return self.years * MONTHS_PER_YEAR + self.months 

246 

247 def in_weeks(self) -> int: 

248 days = self.in_days() 

249 sign = 1 

250 

251 if days < 0: 

252 sign = -1 

253 

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

255 

256 def in_days(self) -> int: 

257 return self._delta.total_days 

258 

259 def in_words(self, locale: str | None = None, separator: str = " ") -> str: 

260 """ 

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

262 

263 Ex: 6 jours 23 heures 58 minutes 

264 

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

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

267 """ 

268 from pendulum.locales.locale import Locale 

269 

270 periods = [ 

271 ("year", self.years), 

272 ("month", self.months), 

273 ("week", self.weeks), 

274 ("day", self.remaining_days), 

275 ("hour", self.hours), 

276 ("minute", self.minutes), 

277 ("second", self.remaining_seconds), 

278 ] 

279 loaded_locale: Locale = Locale.load(locale or pendulum.get_locale()) 

280 parts = [] 

281 for period in periods: 

282 unit, period_count = period 

283 if abs(period_count) > 0: 

284 translation = loaded_locale.translation( 

285 f"units.{unit}.{loaded_locale.plural(abs(period_count))}" 

286 ) 

287 parts.append(translation.format(period_count)) 

288 

289 if not parts: 

290 count: str | int = 0 

291 if abs(self.microseconds) > 0: 

292 unit = f"units.second.{loaded_locale.plural(1)}" 

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

294 else: 

295 unit = f"units.microsecond.{loaded_locale.plural(0)}" 

296 

297 translation = loaded_locale.translation(unit) 

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

299 

300 return separator.join(parts) 

301 

302 def range( 

303 self, unit: str, amount: int = 1 

304 ) -> Iterator[pendulum.DateTime | pendulum.Date]: 

305 method = "add" 

306 op = operator.le 

307 if not self._absolute and self.invert: 

308 method = "subtract" 

309 op = operator.ge 

310 

311 start, end = self.start, self.end 

312 

313 i = amount 

314 while op(start, end): 

315 yield cast(Union[pendulum.DateTime, pendulum.Date], start) 

316 

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

318 

319 i += amount 

320 

321 def as_duration(self) -> Duration: 

322 """ 

323 Return the Interval as a Duration. 

324 """ 

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

326 

327 def __iter__(self) -> Iterator[pendulum.DateTime | pendulum.Date]: 

328 return self.range("days") 

329 

330 def __contains__( 

331 self, item: datetime | date | pendulum.DateTime | pendulum.Date 

332 ) -> bool: 

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

334 

335 def __add__(self, other: timedelta) -> Duration: # type: ignore[override] 

336 return self.as_duration().__add__(other) 

337 

338 __radd__ = __add__ # type: ignore[assignment] 

339 

340 def __sub__(self, other: timedelta) -> Duration: # type: ignore[override] 

341 return self.as_duration().__sub__(other) 

342 

343 def __neg__(self) -> Self: 

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

345 

346 def __mul__(self, other: int | float) -> Duration: # type: ignore[override] 

347 return self.as_duration().__mul__(other) 

348 

349 __rmul__ = __mul__ # type: ignore[assignment] 

350 

351 @overload # type: ignore[override] 

352 def __floordiv__(self, other: timedelta) -> int: 

353 ... 

354 

355 @overload 

356 def __floordiv__(self, other: int) -> Duration: 

357 ... 

358 

359 def __floordiv__(self, other: int | timedelta) -> int | Duration: 

360 return self.as_duration().__floordiv__(other) 

361 

362 __div__ = __floordiv__ # type: ignore[assignment] 

363 

364 @overload # type: ignore[override] 

365 def __truediv__(self, other: timedelta) -> float: 

366 ... 

367 

368 @overload 

369 def __truediv__(self, other: float) -> Duration: 

370 ... 

371 

372 def __truediv__(self, other: float | timedelta) -> Duration | float: 

373 return self.as_duration().__truediv__(other) 

374 

375 def __mod__(self, other: timedelta) -> Duration: # type: ignore[override] 

376 return self.as_duration().__mod__(other) 

377 

378 def __divmod__(self, other: timedelta) -> tuple[int, Duration]: 

379 return self.as_duration().__divmod__(other) 

380 

381 def __abs__(self) -> Self: 

382 return self.__class__(self.start, self.end, absolute=True) 

383 

384 def __repr__(self) -> str: 

385 return f"<Period [{self._start} -> {self._end}]>" 

386 

387 def __str__(self) -> str: 

388 return self.__repr__() 

389 

390 def _cmp(self, other: timedelta) -> int: 

391 # Only needed for PyPy 

392 assert isinstance(other, timedelta) 

393 

394 if isinstance(other, Interval): 

395 other = other.as_timedelta() 

396 

397 td = self.as_timedelta() 

398 

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

400 

401 def _getstate( 

402 self, protocol: SupportsIndex = 3 

403 ) -> tuple[ 

404 pendulum.DateTime | pendulum.Date | datetime | date, 

405 pendulum.DateTime | pendulum.Date | datetime | date, 

406 bool, 

407 ]: 

408 start, end = self.start, self.end 

409 

410 if self._invert and self._absolute: 

411 end, start = start, end 

412 

413 return start, end, self._absolute 

414 

415 def __reduce__( 

416 self, 

417 ) -> tuple[ 

418 type[Self], 

419 tuple[ 

420 pendulum.DateTime | pendulum.Date | datetime | date, 

421 pendulum.DateTime | pendulum.Date | datetime | date, 

422 bool, 

423 ], 

424 ]: 

425 return self.__reduce_ex__(2) 

426 

427 def __reduce_ex__( 

428 self, protocol: SupportsIndex 

429 ) -> tuple[ 

430 type[Self], 

431 tuple[ 

432 pendulum.DateTime | pendulum.Date | datetime | date, 

433 pendulum.DateTime | pendulum.Date | datetime | date, 

434 bool, 

435 ], 

436 ]: 

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

438 

439 def __hash__(self) -> int: 

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

441 

442 def __eq__(self, other: object) -> bool: 

443 if isinstance(other, Interval): 

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

445 other.start, 

446 other.end, 

447 other._absolute, 

448 ) 

449 else: 

450 return self.as_duration() == other 

451 

452 def __ne__(self, other: object) -> bool: 

453 return not self.__eq__(other)