Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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, days, seconds, microseconds, milliseconds, minutes, hours, weeks 

70 ) 

71 

72 # Intuitive normalization 

73 total = self.total_seconds() 

74 

75 m = 1 

76 if total < 0: 

77 m = -1 

78 

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

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

81 

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

83 self._days = _days + (years * 365 + months * 30) 

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

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

86 self._months = months 

87 self._years = years 

88 

89 return self 

90 

91 def total_minutes(self): 

92 return self.total_seconds() / SECONDS_PER_MINUTE 

93 

94 def total_hours(self): 

95 return self.total_seconds() / SECONDS_PER_HOUR 

96 

97 def total_days(self): 

98 return self.total_seconds() / SECONDS_PER_DAY 

99 

100 def total_weeks(self): 

101 return self.total_days() / 7 

102 

103 if PYPY: 

104 

105 def total_seconds(self): 

106 if hasattr(self, "_remaining_days"): 

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

108 else: 

109 days = self._days 

110 

111 return ( 

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

113 + self._microseconds 

114 ) / US_PER_SECOND 

115 

116 @property 

117 def years(self): 

118 return self._years 

119 

120 @property 

121 def months(self): 

122 return self._months 

123 

124 @property 

125 def weeks(self): 

126 return self._weeks 

127 

128 @property 

129 def days(self): 

130 return self._days 

131 

132 @property 

133 def remaining_days(self): 

134 return self._remaining_days 

135 

136 @property 

137 def hours(self): 

138 if self._h is None: 

139 seconds = self._seconds 

140 self._h = 0 

141 if abs(seconds) >= 3600: 

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

143 

144 return self._h 

145 

146 @property 

147 def minutes(self): 

148 if self._i is None: 

149 seconds = self._seconds 

150 self._i = 0 

151 if abs(seconds) >= 60: 

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

153 

154 return self._i 

155 

156 @property 

157 def seconds(self): 

158 return self._seconds 

159 

160 @property 

161 def remaining_seconds(self): 

162 if self._s is None: 

163 self._s = self._seconds 

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

165 

166 return self._s 

167 

168 @property 

169 def microseconds(self): 

170 return self._microseconds 

171 

172 @property 

173 def invert(self): 

174 if self._invert is None: 

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

176 

177 return self._invert 

178 

179 def in_weeks(self): 

180 return int(self.total_weeks()) 

181 

182 def in_days(self): 

183 return int(self.total_days()) 

184 

185 def in_hours(self): 

186 return int(self.total_hours()) 

187 

188 def in_minutes(self): 

189 return int(self.total_minutes()) 

190 

191 def in_seconds(self): 

192 return int(self.total_seconds()) 

193 

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

195 """ 

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

197 

198 Ex: 6 jours 23 heures 58 minutes 

199 

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

201 :type locale: str 

202 

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

204 :type separator: str 

205 

206 :rtype: str 

207 """ 

208 periods = [ 

209 ("year", self.years), 

210 ("month", self.months), 

211 ("week", self.weeks), 

212 ("day", self.remaining_days), 

213 ("hour", self.hours), 

214 ("minute", self.minutes), 

215 ("second", self.remaining_seconds), 

216 ] 

217 

218 if locale is None: 

219 locale = pendulum.get_locale() 

220 

221 locale = pendulum.locale(locale) 

222 parts = [] 

223 for period in periods: 

224 unit, count = period 

225 if abs(count) > 0: 

226 translation = locale.translation( 

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

228 ) 

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

230 

231 if not parts: 

232 if abs(self.microseconds) > 0: 

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

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

235 else: 

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

237 count = 0 

238 translation = locale.translation(unit) 

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

240 

241 return decode(separator.join(parts)) 

242 

243 def _sign(self, value): 

244 if value < 0: 

245 return -1 

246 

247 return 1 

248 

249 def as_timedelta(self): 

250 """ 

251 Return the interval as a native timedelta. 

252 

253 :rtype: timedelta 

254 """ 

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

256 

257 def __str__(self): 

258 return self.in_words() 

259 

260 def __repr__(self): 

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

262 

263 if self._years: 

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

265 

266 if self._months: 

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

268 

269 if self._weeks: 

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

271 

272 if self._days: 

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

274 

275 if self.hours: 

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

277 

278 if self.minutes: 

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

280 

281 if self.remaining_seconds: 

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

283 

284 if self.microseconds: 

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

286 

287 rep += ")" 

288 

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

290 

291 def __add__(self, other): 

292 if isinstance(other, timedelta): 

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

294 

295 return NotImplemented 

296 

297 __radd__ = __add__ 

298 

299 def __sub__(self, other): 

300 if isinstance(other, timedelta): 

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

302 

303 return NotImplemented 

304 

305 def __neg__(self): 

306 return self.__class__( 

307 years=-self._years, 

308 months=-self._months, 

309 weeks=-self._weeks, 

310 days=-self._remaining_days, 

311 seconds=-self._seconds, 

312 microseconds=-self._microseconds, 

313 ) 

314 

315 def _to_microseconds(self): 

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

317 

318 def __mul__(self, other): 

319 if isinstance(other, int): 

320 return self.__class__( 

321 years=self._years * other, 

322 months=self._months * other, 

323 seconds=self.total_seconds() * other, 

324 ) 

325 

326 if isinstance(other, float): 

327 usec = self._to_microseconds() 

328 a, b = other.as_integer_ratio() 

329 

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

331 

332 return NotImplemented 

333 

334 __rmul__ = __mul__ 

335 

336 def __floordiv__(self, other): 

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

338 return NotImplemented 

339 

340 usec = self._to_microseconds() 

341 if isinstance(other, timedelta): 

342 return usec // other._to_microseconds() 

343 

344 # Removing years/months approximation 

345 usec -= (self._years * 365 + self._months * 30) * SECONDS_PER_DAY * 1e6 

346 

347 if isinstance(other, int): 

348 return self.__class__( 

349 0, 

350 0, 

351 usec // other, 

352 years=self._years // other, 

353 months=self._months // other, 

354 ) 

355 

356 def __truediv__(self, other): 

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

358 return NotImplemented 

359 

360 usec = self._to_microseconds() 

361 if isinstance(other, timedelta): 

362 return usec / other._to_microseconds() 

363 

364 # Removing years/months approximation 

365 usec -= (self._years * 365 + self._months * 30) * SECONDS_PER_DAY * 1e6 

366 

367 if isinstance(other, int): 

368 return self.__class__( 

369 0, 

370 0, 

371 _divide_and_round(usec, other), 

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

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

374 ) 

375 

376 if isinstance(other, float): 

377 a, b = other.as_integer_ratio() 

378 

379 return self.__class__( 

380 0, 

381 0, 

382 _divide_and_round(b * usec, a), 

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

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

385 ) 

386 

387 __div__ = __floordiv__ 

388 

389 def __mod__(self, other): 

390 if isinstance(other, timedelta): 

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

392 

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

394 

395 return NotImplemented 

396 

397 def __divmod__(self, other): 

398 if isinstance(other, timedelta): 

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

400 

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

402 

403 return NotImplemented 

404 

405 

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

407Duration.max = Duration( 

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

409) 

410Duration.resolution = Duration(microseconds=1) 

411 

412 

413class AbsoluteDuration(Duration): 

414 """ 

415 Duration that expresses a time difference in absolute values. 

416 """ 

417 

418 def __new__( 

419 cls, 

420 days=0, 

421 seconds=0, 

422 microseconds=0, 

423 milliseconds=0, 

424 minutes=0, 

425 hours=0, 

426 weeks=0, 

427 years=0, 

428 months=0, 

429 ): 

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

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

432 

433 self = timedelta.__new__( 

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

435 ) 

436 

437 # We need to compute the total_seconds() value 

438 # on a native timedelta object 

439 delta = timedelta( 

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

441 ) 

442 

443 # Intuitive normalization 

444 self._total = delta.total_seconds() 

445 total = abs(self._total) 

446 

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

448 self._seconds = int(total) % SECONDS_PER_DAY 

449 

450 days = int(total) // SECONDS_PER_DAY 

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

452 self._remaining_days = days % 7 

453 self._weeks = days // 7 

454 self._months = abs(months) 

455 self._years = abs(years) 

456 

457 return self 

458 

459 def total_seconds(self): 

460 return abs(self._total) 

461 

462 @property 

463 def invert(self): 

464 if self._invert is None: 

465 self._invert = self._total < 0 

466 

467 return self._invert