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

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

216 statements  

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 An interval 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( 

65 "Both start and end of an Interval must have the same type" 

66 ) 

67 

68 if ( 

69 isinstance(start, datetime) 

70 and isinstance(end, datetime) 

71 and ( 

72 start.tzinfo is None 

73 and end.tzinfo is not None 

74 or start.tzinfo is not None 

75 and end.tzinfo is None 

76 ) 

77 ): 

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

79 

80 if absolute and start > end: 

81 end, start = start, end 

82 

83 _start = start 

84 _end = end 

85 if isinstance(start, pendulum.DateTime): 

86 _start = datetime( 

87 start.year, 

88 start.month, 

89 start.day, 

90 start.hour, 

91 start.minute, 

92 start.second, 

93 start.microsecond, 

94 tzinfo=start.tzinfo, 

95 fold=start.fold, 

96 ) 

97 elif isinstance(start, pendulum.Date): 

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

99 

100 if isinstance(end, pendulum.DateTime): 

101 _end = datetime( 

102 end.year, 

103 end.month, 

104 end.day, 

105 end.hour, 

106 end.minute, 

107 end.second, 

108 end.microsecond, 

109 tzinfo=end.tzinfo, 

110 fold=end.fold, 

111 ) 

112 elif isinstance(end, pendulum.Date): 

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

114 

115 # Fixing issues with datetime.__sub__() 

116 # not handling offsets if the tzinfo is the same 

117 if ( 

118 isinstance(_start, datetime) 

119 and isinstance(_end, datetime) 

120 and _start.tzinfo is _end.tzinfo 

121 ): 

122 if _start.tzinfo is not None: 

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

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

125 

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

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

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

129 

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

131 

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

133 

134 def __init__( 

135 self, 

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

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

138 absolute: bool = False, 

139 ) -> None: 

140 super().__init__() 

141 

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

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

144 if isinstance(start, datetime): 

145 start = pendulum.instance(start) 

146 else: 

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

148 

149 _start = start 

150 else: 

151 if isinstance(start, pendulum.DateTime): 

152 _start = datetime( 

153 start.year, 

154 start.month, 

155 start.day, 

156 start.hour, 

157 start.minute, 

158 start.second, 

159 start.microsecond, 

160 tzinfo=start.tzinfo, 

161 ) 

162 else: 

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

164 

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

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

167 if isinstance(end, datetime): 

168 end = pendulum.instance(end) 

169 else: 

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

171 

172 _end = end 

173 else: 

174 if isinstance(end, pendulum.DateTime): 

175 _end = datetime( 

176 end.year, 

177 end.month, 

178 end.day, 

179 end.hour, 

180 end.minute, 

181 end.second, 

182 end.microsecond, 

183 tzinfo=end.tzinfo, 

184 ) 

185 else: 

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

187 

188 self._invert = False 

189 if start > end: 

190 self._invert = True 

191 

192 if absolute: 

193 end, start = start, end 

194 _end, _start = _start, _end 

195 

196 self._absolute = absolute 

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

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

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

200 

201 @property 

202 def years(self) -> int: 

203 return self._delta.years 

204 

205 @property 

206 def months(self) -> int: 

207 return self._delta.months 

208 

209 @property 

210 def weeks(self) -> int: 

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

212 

213 @property 

214 def days(self) -> int: 

215 return self._days 

216 

217 @property 

218 def remaining_days(self) -> int: 

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

220 

221 @property 

222 def hours(self) -> int: 

223 return self._delta.hours 

224 

225 @property 

226 def minutes(self) -> int: 

227 return self._delta.minutes 

228 

229 @property 

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

231 return self._start 

232 

233 @property 

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

235 return self._end 

236 

237 def in_years(self) -> int: 

238 """ 

239 Gives the duration of the Interval in full years. 

240 """ 

241 return self.years 

242 

243 def in_months(self) -> int: 

244 """ 

245 Gives the duration of the Interval in full months. 

246 """ 

247 return self.years * MONTHS_PER_YEAR + self.months 

248 

249 def in_weeks(self) -> int: 

250 days = self.in_days() 

251 sign = 1 

252 

253 if days < 0: 

254 sign = -1 

255 

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

257 

258 def in_days(self) -> int: 

259 return self._delta.total_days 

260 

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

262 """ 

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

264 

265 Ex: 6 jours 23 heures 58 minutes 

266 

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

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

269 """ 

270 from pendulum.locales.locale import Locale 

271 

272 intervals = [ 

273 ("year", self.years), 

274 ("month", self.months), 

275 ("week", self.weeks), 

276 ("day", self.remaining_days), 

277 ("hour", self.hours), 

278 ("minute", self.minutes), 

279 ("second", self.remaining_seconds), 

280 ] 

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

282 parts = [] 

283 for interval in intervals: 

284 unit, interval_count = interval 

285 if abs(interval_count) > 0: 

286 translation = loaded_locale.translation( 

287 f"units.{unit}.{loaded_locale.plural(abs(interval_count))}" 

288 ) 

289 parts.append(translation.format(interval_count)) 

290 

291 if not parts: 

292 count: str | int = 0 

293 if abs(self.microseconds) > 0: 

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

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

296 else: 

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

298 

299 translation = loaded_locale.translation(unit) 

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

301 

302 return separator.join(parts) 

303 

304 def range( 

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

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

307 method = "add" 

308 op = operator.le 

309 if not self._absolute and self.invert: 

310 method = "subtract" 

311 op = operator.ge 

312 

313 start, end = self.start, self.end 

314 

315 i = amount 

316 while op(start, end): 

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

318 

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

320 

321 i += amount 

322 

323 def as_duration(self) -> Duration: 

324 """ 

325 Return the Interval as a Duration. 

326 """ 

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

328 

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

330 return self.range("days") 

331 

332 def __contains__( 

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

334 ) -> bool: 

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

336 

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

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

339 

340 __radd__ = __add__ # type: ignore[assignment] 

341 

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

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

344 

345 def __neg__(self) -> Self: 

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

347 

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

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

350 

351 __rmul__ = __mul__ # type: ignore[assignment] 

352 

353 @overload # type: ignore[override] 

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

355 ... 

356 

357 @overload 

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

359 ... 

360 

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

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

363 

364 __div__ = __floordiv__ # type: ignore[assignment] 

365 

366 @overload # type: ignore[override] 

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

368 ... 

369 

370 @overload 

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

372 ... 

373 

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

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

376 

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

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

379 

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

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

382 

383 def __abs__(self) -> Self: 

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

385 

386 def __repr__(self) -> str: 

387 return f"<Interval [{self._start} -> {self._end}]>" 

388 

389 def __str__(self) -> str: 

390 return self.__repr__() 

391 

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

393 # Only needed for PyPy 

394 assert isinstance(other, timedelta) 

395 

396 if isinstance(other, Interval): 

397 other = other.as_timedelta() 

398 

399 td = self.as_timedelta() 

400 

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

402 

403 def _getstate( 

404 self, protocol: SupportsIndex = 3 

405 ) -> tuple[ 

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

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

408 bool, 

409 ]: 

410 start, end = self.start, self.end 

411 

412 if self._invert and self._absolute: 

413 end, start = start, end 

414 

415 return start, end, self._absolute 

416 

417 def __reduce__( 

418 self, 

419 ) -> tuple[ 

420 type[Self], 

421 tuple[ 

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

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

424 bool, 

425 ], 

426 ]: 

427 return self.__reduce_ex__(2) 

428 

429 def __reduce_ex__( 

430 self, protocol: SupportsIndex 

431 ) -> tuple[ 

432 type[Self], 

433 tuple[ 

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

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

436 bool, 

437 ], 

438 ]: 

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

440 

441 def __hash__(self) -> int: 

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

443 

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

445 if isinstance(other, Interval): 

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

447 other.start, 

448 other.end, 

449 other._absolute, 

450 ) 

451 else: 

452 return self.as_duration() == other 

453 

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

455 return not self.__eq__(other)