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

249 statements  

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

1from __future__ import absolute_import 

2from __future__ import division 

3 

4from datetime import timedelta 

5 

6import pendulum 

7 

8from pendulum.utils._compat import PYPY 

9from pendulum.utils._compat import decode 

10 

11from .constants import SECONDS_PER_DAY 

12from .constants import SECONDS_PER_HOUR 

13from .constants import SECONDS_PER_MINUTE 

14from .constants import US_PER_SECOND 

15 

16 

17def _divide_and_round(a, b): 

18 """divide a by b and round result to the nearest integer 

19 

20 When the ratio is exactly half-way between two integers, 

21 the even integer is returned. 

22 """ 

23 # Based on the reference implementation for divmod_near 

24 # in Objects/longobject.c. 

25 q, r = divmod(a, b) 

26 # round up if either r / b > 0.5, or r / b == 0.5 and q is odd. 

27 # The expression r / b > 0.5 is equivalent to 2 * r > b if b is 

28 # positive, 2 * r < b if b negative. 

29 r *= 2 

30 greater_than_half = r > b if b > 0 else r < b 

31 if greater_than_half or r == b and q % 2 == 1: 

32 q += 1 

33 

34 return q 

35 

36 

37class Duration(timedelta): 

38 """ 

39 Replacement for the standard timedelta class. 

40 

41 Provides several improvements over the base class. 

42 """ 

43 

44 _y = None 

45 _m = None 

46 _w = None 

47 _d = None 

48 _h = None 

49 _i = None 

50 _s = None 

51 _invert = None 

52 

53 def __new__( 

54 cls, 

55 days=0, 

56 seconds=0, 

57 microseconds=0, 

58 milliseconds=0, 

59 minutes=0, 

60 hours=0, 

61 weeks=0, 

62 years=0, 

63 months=0, 

64 ): 

65 if not isinstance(years, int) or not isinstance(months, int): 

66 raise ValueError("Float year and months are not supported") 

67 

68 self = timedelta.__new__( 

69 cls, 

70 days + years * 365 + months * 30, 

71 seconds, 

72 microseconds, 

73 milliseconds, 

74 minutes, 

75 hours, 

76 weeks, 

77 ) 

78 

79 # Intuitive normalization 

80 total = self.total_seconds() - (years * 365 + months * 30) * SECONDS_PER_DAY 

81 self._total = total 

82 

83 m = 1 

84 if total < 0: 

85 m = -1 

86 

87 self._microseconds = round(total % m * 1e6) 

88 self._seconds = abs(int(total)) % SECONDS_PER_DAY * m 

89 

90 _days = abs(int(total)) // SECONDS_PER_DAY * m 

91 self._days = _days 

92 self._remaining_days = abs(_days) % 7 * m 

93 self._weeks = abs(_days) // 7 * m 

94 self._months = months 

95 self._years = years 

96 

97 return self 

98 

99 def total_minutes(self): 

100 return self.total_seconds() / SECONDS_PER_MINUTE 

101 

102 def total_hours(self): 

103 return self.total_seconds() / SECONDS_PER_HOUR 

104 

105 def total_days(self): 

106 return self.total_seconds() / SECONDS_PER_DAY 

107 

108 def total_weeks(self): 

109 return self.total_days() / 7 

110 

111 if PYPY: 

112 

113 def total_seconds(self): 

114 days = 0 

115 

116 if hasattr(self, "_years"): 

117 days += self._years * 365 

118 

119 if hasattr(self, "_months"): 

120 days += self._months * 30 

121 

122 if hasattr(self, "_remaining_days"): 

123 days += self._weeks * 7 + self._remaining_days 

124 else: 

125 days += self._days 

126 

127 return ( 

128 (days * SECONDS_PER_DAY + self._seconds) * US_PER_SECOND 

129 + self._microseconds 

130 ) / US_PER_SECOND 

131 

132 @property 

133 def years(self): 

134 return self._years 

135 

136 @property 

137 def months(self): 

138 return self._months 

139 

140 @property 

141 def weeks(self): 

142 return self._weeks 

143 

144 if PYPY: 

145 

146 @property 

147 def days(self): 

148 return self._years * 365 + self._months * 30 + self._days 

149 

150 @property 

151 def remaining_days(self): 

152 return self._remaining_days 

153 

154 @property 

155 def hours(self): 

156 if self._h is None: 

157 seconds = self._seconds 

158 self._h = 0 

159 if abs(seconds) >= 3600: 

160 self._h = (abs(seconds) // 3600 % 24) * self._sign(seconds) 

161 

162 return self._h 

163 

164 @property 

165 def minutes(self): 

166 if self._i is None: 

167 seconds = self._seconds 

168 self._i = 0 

169 if abs(seconds) >= 60: 

170 self._i = (abs(seconds) // 60 % 60) * self._sign(seconds) 

171 

172 return self._i 

173 

174 @property 

175 def seconds(self): 

176 return self._seconds 

177 

178 @property 

179 def remaining_seconds(self): 

180 if self._s is None: 

181 self._s = self._seconds 

182 self._s = abs(self._s) % 60 * self._sign(self._s) 

183 

184 return self._s 

185 

186 @property 

187 def microseconds(self): 

188 return self._microseconds 

189 

190 @property 

191 def invert(self): 

192 if self._invert is None: 

193 self._invert = self.total_seconds() < 0 

194 

195 return self._invert 

196 

197 def in_weeks(self): 

198 return int(self.total_weeks()) 

199 

200 def in_days(self): 

201 return int(self.total_days()) 

202 

203 def in_hours(self): 

204 return int(self.total_hours()) 

205 

206 def in_minutes(self): 

207 return int(self.total_minutes()) 

208 

209 def in_seconds(self): 

210 return int(self.total_seconds()) 

211 

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

213 """ 

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

215 

216 Ex: 6 jours 23 heures 58 minutes 

217 

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

219 :type locale: str 

220 

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

222 :type separator: str 

223 

224 :rtype: str 

225 """ 

226 periods = [ 

227 ("year", self.years), 

228 ("month", self.months), 

229 ("week", self.weeks), 

230 ("day", self.remaining_days), 

231 ("hour", self.hours), 

232 ("minute", self.minutes), 

233 ("second", self.remaining_seconds), 

234 ] 

235 

236 if locale is None: 

237 locale = pendulum.get_locale() 

238 

239 locale = pendulum.locale(locale) 

240 parts = [] 

241 for period in periods: 

242 unit, count = period 

243 if abs(count) > 0: 

244 translation = locale.translation( 

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

246 ) 

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

248 

249 if not parts: 

250 if abs(self.microseconds) > 0: 

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

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

253 else: 

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

255 count = 0 

256 translation = locale.translation(unit) 

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

258 

259 return decode(separator.join(parts)) 

260 

261 def _sign(self, value): 

262 if value < 0: 

263 return -1 

264 

265 return 1 

266 

267 def as_timedelta(self): 

268 """ 

269 Return the interval as a native timedelta. 

270 

271 :rtype: timedelta 

272 """ 

273 return timedelta(seconds=self.total_seconds()) 

274 

275 def __str__(self): 

276 return self.in_words() 

277 

278 def __repr__(self): 

279 rep = "{}(".format(self.__class__.__name__) 

280 

281 if self._years: 

282 rep += "years={}, ".format(self._years) 

283 

284 if self._months: 

285 rep += "months={}, ".format(self._months) 

286 

287 if self._weeks: 

288 rep += "weeks={}, ".format(self._weeks) 

289 

290 if self._days: 

291 rep += "days={}, ".format(self._remaining_days) 

292 

293 if self.hours: 

294 rep += "hours={}, ".format(self.hours) 

295 

296 if self.minutes: 

297 rep += "minutes={}, ".format(self.minutes) 

298 

299 if self.remaining_seconds: 

300 rep += "seconds={}, ".format(self.remaining_seconds) 

301 

302 if self.microseconds: 

303 rep += "microseconds={}, ".format(self.microseconds) 

304 

305 rep += ")" 

306 

307 return rep.replace(", )", ")") 

308 

309 def __add__(self, other): 

310 if isinstance(other, timedelta): 

311 return self.__class__(seconds=self.total_seconds() + other.total_seconds()) 

312 

313 return NotImplemented 

314 

315 __radd__ = __add__ 

316 

317 def __sub__(self, other): 

318 if isinstance(other, timedelta): 

319 return self.__class__(seconds=self.total_seconds() - other.total_seconds()) 

320 

321 return NotImplemented 

322 

323 def __neg__(self): 

324 return self.__class__( 

325 years=-self._years, 

326 months=-self._months, 

327 weeks=-self._weeks, 

328 days=-self._remaining_days, 

329 seconds=-self._seconds, 

330 microseconds=-self._microseconds, 

331 ) 

332 

333 def _to_microseconds(self): 

334 return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds 

335 

336 def __mul__(self, other): 

337 if isinstance(other, int): 

338 return self.__class__( 

339 years=self._years * other, 

340 months=self._months * other, 

341 seconds=self._total * other, 

342 ) 

343 

344 if isinstance(other, float): 

345 usec = self._to_microseconds() 

346 a, b = other.as_integer_ratio() 

347 

348 return self.__class__(0, 0, _divide_and_round(usec * a, b)) 

349 

350 return NotImplemented 

351 

352 __rmul__ = __mul__ 

353 

354 def __floordiv__(self, other): 

355 if not isinstance(other, (int, timedelta)): 

356 return NotImplemented 

357 

358 usec = self._to_microseconds() 

359 if isinstance(other, timedelta): 

360 return usec // other._to_microseconds() 

361 

362 if isinstance(other, int): 

363 return self.__class__( 

364 0, 

365 0, 

366 usec // other, 

367 years=self._years // other, 

368 months=self._months // other, 

369 ) 

370 

371 def __truediv__(self, other): 

372 if not isinstance(other, (int, float, timedelta)): 

373 return NotImplemented 

374 

375 usec = self._to_microseconds() 

376 if isinstance(other, timedelta): 

377 return usec / other._to_microseconds() 

378 

379 if isinstance(other, int): 

380 return self.__class__( 

381 0, 

382 0, 

383 _divide_and_round(usec, other), 

384 years=_divide_and_round(self._years, other), 

385 months=_divide_and_round(self._months, other), 

386 ) 

387 

388 if isinstance(other, float): 

389 a, b = other.as_integer_ratio() 

390 

391 return self.__class__( 

392 0, 

393 0, 

394 _divide_and_round(b * usec, a), 

395 years=_divide_and_round(self._years * b, a), 

396 months=_divide_and_round(self._months, other), 

397 ) 

398 

399 __div__ = __floordiv__ 

400 

401 def __mod__(self, other): 

402 if isinstance(other, timedelta): 

403 r = self._to_microseconds() % other._to_microseconds() 

404 

405 return self.__class__(0, 0, r) 

406 

407 return NotImplemented 

408 

409 def __divmod__(self, other): 

410 if isinstance(other, timedelta): 

411 q, r = divmod(self._to_microseconds(), other._to_microseconds()) 

412 

413 return q, self.__class__(0, 0, r) 

414 

415 return NotImplemented 

416 

417 

418Duration.min = Duration(days=-999999999) 

419Duration.max = Duration( 

420 days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999 

421) 

422Duration.resolution = Duration(microseconds=1) 

423 

424 

425class AbsoluteDuration(Duration): 

426 """ 

427 Duration that expresses a time difference in absolute values. 

428 """ 

429 

430 def __new__( 

431 cls, 

432 days=0, 

433 seconds=0, 

434 microseconds=0, 

435 milliseconds=0, 

436 minutes=0, 

437 hours=0, 

438 weeks=0, 

439 years=0, 

440 months=0, 

441 ): 

442 if not isinstance(years, int) or not isinstance(months, int): 

443 raise ValueError("Float year and months are not supported") 

444 

445 self = timedelta.__new__( 

446 cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks 

447 ) 

448 

449 # We need to compute the total_seconds() value 

450 # on a native timedelta object 

451 delta = timedelta( 

452 days, seconds, microseconds, milliseconds, minutes, hours, weeks 

453 ) 

454 

455 # Intuitive normalization 

456 self._total = delta.total_seconds() 

457 total = abs(self._total) 

458 

459 self._microseconds = round(total % 1 * 1e6) 

460 self._seconds = int(total) % SECONDS_PER_DAY 

461 

462 days = int(total) // SECONDS_PER_DAY 

463 self._days = abs(days + years * 365 + months * 30) 

464 self._remaining_days = days % 7 

465 self._weeks = days // 7 

466 self._months = abs(months) 

467 self._years = abs(years) 

468 

469 return self 

470 

471 def total_seconds(self): 

472 return abs(self._total) 

473 

474 @property 

475 def invert(self): 

476 if self._invert is None: 

477 self._invert = self._total < 0 

478 

479 return self._invert