Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/dateutil/relativedelta.py: 39%

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

236 statements  

1import datetime 

2import calendar 

3 

4import operator 

5from math import copysign 

6 

7from warnings import warn 

8 

9from ._common import weekday 

10 

11MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) 

12 

13__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] 

14 

15 

16class relativedelta: 

17 """ 

18 The relativedelta type is designed to be applied to an existing datetime and 

19 can replace specific components of that datetime, or represents an interval 

20 of time. 

21 

22 It is based on the specification of the excellent work done by M.-A. Lemburg 

23 in his 

24 `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension. 

25 However, notice that this type does *NOT* implement the same algorithm as 

26 his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. 

27 

28 There are two different ways to build a relativedelta instance. The 

29 first one is passing it two date/datetime classes:: 

30 

31 relativedelta(datetime1, datetime2) 

32 

33 The second one is passing it any number of the following keyword arguments:: 

34 

35 relativedelta(arg1=x,arg2=y,arg3=z...) 

36 

37 year, month, day, hour, minute, second, microsecond: 

38 Absolute information (argument is singular); adding or subtracting a 

39 relativedelta with absolute information does not perform an arithmetic 

40 operation, but rather REPLACES the corresponding value in the 

41 original datetime with the value(s) in relativedelta. 

42 

43 years, months, weeks, days, hours, minutes, seconds, microseconds: 

44 Relative information, may be negative (argument is plural); adding 

45 or subtracting a relativedelta with relative information performs 

46 the corresponding arithmetic operation on the original datetime value 

47 with the information in the relativedelta. 

48 

49 weekday: 

50 One of the weekday instances (MO, TU, etc) available in the 

51 relativedelta module. These instances may receive a parameter N, 

52 specifying the Nth weekday, which could be positive or negative 

53 (like MO(+1) or MO(-2)). Not specifying it is the same as specifying 

54 +1. You can also use an integer, where 0=MO. This argument is always 

55 relative e.g. if the calculated date is already Monday, using MO(1) 

56 or MO(-1) won't change the day. To effectively make it absolute, use 

57 it in combination with the day argument (e.g. day=1, MO(1) for first 

58 Monday of the month). 

59 

60 leapdays: 

61 Will add given days to the date found, if year is a leap 

62 year, and the date found is post 28 of february. 

63 

64 yearday, nlyearday: 

65 Set the yearday or the non-leap year day (jump leap days). 

66 These are converted to day/month/leapdays information. 

67 

68 There are relative and absolute forms of the keyword 

69 arguments. The plural is relative, and the singular is 

70 absolute. For each argument in the order below, the absolute form 

71 is applied first (by setting each attribute to that value) and 

72 then the relative form (by adding the value to the attribute). 

73 

74 The order of attributes considered when this relativedelta is 

75 added to a datetime is: 

76 

77 1. Year 

78 2. Month 

79 3. Day 

80 4. Hours 

81 5. Minutes 

82 6. Seconds 

83 7. Microseconds 

84 

85 Finally, weekday is applied, using the rule described above. 

86 

87 For example 

88 

89 >>> from datetime import datetime 

90 >>> from dateutil.relativedelta import relativedelta, MO 

91 >>> dt = datetime(2018, 4, 9, 13, 37, 0) 

92 >>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) 

93 >>> dt + delta 

94 datetime.datetime(2018, 4, 2, 14, 37) 

95 

96 First, the day is set to 1 (the first of the month), then 25 hours 

97 are added, to get to the 2nd day and 14th hour, finally the 

98 weekday is applied, but since the 2nd is already a Monday there is 

99 no effect. 

100 

101 """ 

102 

103 def __init__(self, dt1=None, dt2=None, 

104 years=0, months=0, days=0, leapdays=0, weeks=0, 

105 hours=0, minutes=0, seconds=0, microseconds=0, 

106 year=None, month=None, day=None, weekday=None, 

107 yearday=None, nlyearday=None, 

108 hour=None, minute=None, second=None, microsecond=None): 

109 

110 if dt1 and dt2: 

111 # datetime is a subclass of date. So both must be date 

112 if not (isinstance(dt1, datetime.date) and 

113 isinstance(dt2, datetime.date)): 

114 raise TypeError("relativedelta only diffs datetime/date") 

115 

116 # We allow two dates, or two datetimes, so we coerce them to be 

117 # of the same type 

118 if (isinstance(dt1, datetime.datetime) != 

119 isinstance(dt2, datetime.datetime)): 

120 if not isinstance(dt1, datetime.datetime): 

121 dt1 = datetime.datetime.fromordinal(dt1.toordinal()) 

122 elif not isinstance(dt2, datetime.datetime): 

123 dt2 = datetime.datetime.fromordinal(dt2.toordinal()) 

124 

125 self.years = 0 

126 self.months = 0 

127 self.days = 0 

128 self.leapdays = 0 

129 self.hours = 0 

130 self.minutes = 0 

131 self.seconds = 0 

132 self.microseconds = 0 

133 self.year = None 

134 self.month = None 

135 self.day = None 

136 self.weekday = None 

137 self.hour = None 

138 self.minute = None 

139 self.second = None 

140 self.microsecond = None 

141 self._has_time = 0 

142 

143 # Get year / month delta between the two 

144 months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month) 

145 self._set_months(months) 

146 

147 # Remove the year/month delta so the timedelta is just well-defined 

148 # time units (seconds, days and microseconds) 

149 dtm = self.__radd__(dt2) 

150 

151 # If we've overshot our target, make an adjustment 

152 if dt1 < dt2: 

153 compare = operator.gt 

154 increment = 1 

155 else: 

156 compare = operator.lt 

157 increment = -1 

158 

159 while compare(dt1, dtm): 

160 months += increment 

161 self._set_months(months) 

162 dtm = self.__radd__(dt2) 

163 

164 # Get the timedelta between the "months-adjusted" date and dt1 

165 delta = dt1 - dtm 

166 self.seconds = delta.seconds + delta.days * 86400 

167 self.microseconds = delta.microseconds 

168 else: 

169 # Check for non-integer values in integer-only quantities 

170 if any(x is not None and x != int(x) for x in (years, months)): 

171 raise ValueError("Non-integer years and months are " 

172 "ambiguous and not currently supported.") 

173 

174 # Relative information 

175 self.years = int(years) 

176 self.months = int(months) 

177 self.days = days + weeks * 7 

178 self.leapdays = leapdays 

179 self.hours = hours 

180 self.minutes = minutes 

181 self.seconds = seconds 

182 self.microseconds = microseconds 

183 

184 # Absolute information 

185 self.year = year 

186 self.month = month 

187 self.day = day 

188 self.hour = hour 

189 self.minute = minute 

190 self.second = second 

191 self.microsecond = microsecond 

192 

193 if any(x is not None and int(x) != x 

194 for x in (year, month, day, hour, 

195 minute, second, microsecond)): 

196 # For now we'll deprecate floats - later it'll be an error. 

197 warn("Non-integer value passed as absolute information. " + 

198 "This is not a well-defined condition and will raise " + 

199 "errors in future versions.", DeprecationWarning) 

200 

201 if isinstance(weekday, int): 

202 self.weekday = weekdays[weekday] 

203 else: 

204 self.weekday = weekday 

205 

206 yday = 0 

207 if nlyearday: 

208 yday = nlyearday 

209 elif yearday: 

210 yday = yearday 

211 # Days after February are converted as common-year days, 

212 # and leapdays=-1 moves them back over Feb 29 in leap 

213 # years. Day 366 becomes December 32, which is clamped to 

214 # December 31, so it must not be moved back. 

215 if 59 < yearday < 366: 

216 self.leapdays = -1 

217 if yday: 

218 ydayidx = [31, 59, 90, 120, 151, 181, 212, 

219 243, 273, 304, 334, 366] 

220 for idx, ydays in enumerate(ydayidx): 

221 if yday <= ydays: 

222 self.month = idx+1 

223 if idx == 0: 

224 self.day = yday 

225 else: 

226 self.day = yday-ydayidx[idx-1] 

227 break 

228 else: 

229 raise ValueError("invalid year day (%d)" % yday) 

230 

231 self._fix() 

232 

233 def _fix(self): 

234 if abs(self.microseconds) > 999999: 

235 s = _sign(self.microseconds) 

236 div, mod = divmod(self.microseconds * s, 1000000) 

237 self.microseconds = mod * s 

238 self.seconds += div * s 

239 if abs(self.seconds) > 59: 

240 s = _sign(self.seconds) 

241 div, mod = divmod(self.seconds * s, 60) 

242 self.seconds = mod * s 

243 self.minutes += div * s 

244 if abs(self.minutes) > 59: 

245 s = _sign(self.minutes) 

246 div, mod = divmod(self.minutes * s, 60) 

247 self.minutes = mod * s 

248 self.hours += div * s 

249 if abs(self.hours) > 23: 

250 s = _sign(self.hours) 

251 div, mod = divmod(self.hours * s, 24) 

252 self.hours = mod * s 

253 self.days += div * s 

254 if abs(self.months) > 11: 

255 s = _sign(self.months) 

256 div, mod = divmod(self.months * s, 12) 

257 self.months = mod * s 

258 self.years += div * s 

259 if (self.hours or self.minutes or self.seconds or self.microseconds 

260 or self.hour is not None or self.minute is not None or 

261 self.second is not None or self.microsecond is not None): 

262 self._has_time = 1 

263 else: 

264 self._has_time = 0 

265 

266 @property 

267 def weeks(self): 

268 return int(self.days / 7.0) 

269 

270 @weeks.setter 

271 def weeks(self, value): 

272 self.days = self.days - (self.weeks * 7) + value * 7 

273 

274 def _set_months(self, months): 

275 self.months = months 

276 if abs(self.months) > 11: 

277 s = _sign(self.months) 

278 div, mod = divmod(self.months * s, 12) 

279 self.months = mod * s 

280 self.years = div * s 

281 else: 

282 self.years = 0 

283 

284 def normalized(self): 

285 """ 

286 Return a version of this object represented entirely using integer 

287 values for the relative attributes. 

288 

289 >>> relativedelta(days=1.5, hours=2).normalized() 

290 relativedelta(days=+1, hours=+14) 

291 

292 :return: 

293 Returns a :class:`dateutil.relativedelta.relativedelta` object. 

294 """ 

295 # Cascade remainders down (rounding each to roughly nearest microsecond) 

296 days = int(self.days) 

297 

298 hours_f = round(self.hours + 24 * (self.days - days), 11) 

299 hours = int(hours_f) 

300 

301 minutes_f = round(self.minutes + 60 * (hours_f - hours), 10) 

302 minutes = int(minutes_f) 

303 

304 seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8) 

305 seconds = int(seconds_f) 

306 

307 microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds)) 

308 

309 # Constructor carries overflow back up with call to _fix() 

310 return self.__class__(years=self.years, months=self.months, 

311 days=days, hours=hours, minutes=minutes, 

312 seconds=seconds, microseconds=microseconds, 

313 leapdays=self.leapdays, year=self.year, 

314 month=self.month, day=self.day, 

315 weekday=self.weekday, hour=self.hour, 

316 minute=self.minute, second=self.second, 

317 microsecond=self.microsecond) 

318 

319 def __add__(self, other): 

320 if isinstance(other, relativedelta): 

321 return self.__class__(years=other.years + self.years, 

322 months=other.months + self.months, 

323 days=other.days + self.days, 

324 hours=other.hours + self.hours, 

325 minutes=other.minutes + self.minutes, 

326 seconds=other.seconds + self.seconds, 

327 microseconds=(other.microseconds + 

328 self.microseconds), 

329 leapdays=other.leapdays or self.leapdays, 

330 year=(other.year if other.year is not None 

331 else self.year), 

332 month=(other.month if other.month is not None 

333 else self.month), 

334 day=(other.day if other.day is not None 

335 else self.day), 

336 weekday=(other.weekday if other.weekday is not None 

337 else self.weekday), 

338 hour=(other.hour if other.hour is not None 

339 else self.hour), 

340 minute=(other.minute if other.minute is not None 

341 else self.minute), 

342 second=(other.second if other.second is not None 

343 else self.second), 

344 microsecond=(other.microsecond if other.microsecond 

345 is not None else 

346 self.microsecond)) 

347 if isinstance(other, datetime.timedelta): 

348 return self.__class__(years=self.years, 

349 months=self.months, 

350 days=self.days + other.days, 

351 hours=self.hours, 

352 minutes=self.minutes, 

353 seconds=self.seconds + other.seconds, 

354 microseconds=self.microseconds + other.microseconds, 

355 leapdays=self.leapdays, 

356 year=self.year, 

357 month=self.month, 

358 day=self.day, 

359 weekday=self.weekday, 

360 hour=self.hour, 

361 minute=self.minute, 

362 second=self.second, 

363 microsecond=self.microsecond) 

364 if not isinstance(other, datetime.date): 

365 return NotImplemented 

366 elif self._has_time and not isinstance(other, datetime.datetime): 

367 other = datetime.datetime.fromordinal(other.toordinal()) 

368 year = (self.year or other.year)+self.years 

369 month = self.month or other.month 

370 if self.months: 

371 assert 1 <= abs(self.months) <= 12 

372 month += self.months 

373 if month > 12: 

374 year += 1 

375 month -= 12 

376 elif month < 1: 

377 year -= 1 

378 month += 12 

379 day = min(calendar.monthrange(year, month)[1], 

380 self.day or other.day) 

381 repl = {"year": year, "month": month, "day": day} 

382 for attr in ["hour", "minute", "second", "microsecond"]: 

383 value = getattr(self, attr) 

384 if value is not None: 

385 repl[attr] = value 

386 days = self.days 

387 if self.leapdays and month > 2 and calendar.isleap(year): 

388 days += self.leapdays 

389 ret = (other.replace(**repl) 

390 + datetime.timedelta(days=days, 

391 hours=self.hours, 

392 minutes=self.minutes, 

393 seconds=self.seconds, 

394 microseconds=self.microseconds)) 

395 if self.weekday: 

396 weekday, nth = self.weekday.weekday, self.weekday.n or 1 

397 jumpdays = (abs(nth) - 1) * 7 

398 if nth > 0: 

399 jumpdays += (7 - ret.weekday() + weekday) % 7 

400 else: 

401 jumpdays += (ret.weekday() - weekday) % 7 

402 jumpdays *= -1 

403 ret += datetime.timedelta(days=jumpdays) 

404 return ret 

405 

406 def __radd__(self, other): 

407 return self.__add__(other) 

408 

409 def __rsub__(self, other): 

410 return self.__neg__().__radd__(other) 

411 

412 def __sub__(self, other): 

413 if not isinstance(other, relativedelta): 

414 return NotImplemented # In case the other object defines __rsub__ 

415 return self.__class__(years=self.years - other.years, 

416 months=self.months - other.months, 

417 days=self.days - other.days, 

418 hours=self.hours - other.hours, 

419 minutes=self.minutes - other.minutes, 

420 seconds=self.seconds - other.seconds, 

421 microseconds=self.microseconds - other.microseconds, 

422 leapdays=self.leapdays or other.leapdays, 

423 year=(self.year if self.year is not None 

424 else other.year), 

425 month=(self.month if self.month is not None else 

426 other.month), 

427 day=(self.day if self.day is not None else 

428 other.day), 

429 weekday=(self.weekday if self.weekday is not None else 

430 other.weekday), 

431 hour=(self.hour if self.hour is not None else 

432 other.hour), 

433 minute=(self.minute if self.minute is not None else 

434 other.minute), 

435 second=(self.second if self.second is not None else 

436 other.second), 

437 microsecond=(self.microsecond if self.microsecond 

438 is not None else 

439 other.microsecond)) 

440 

441 def __abs__(self): 

442 return self.__class__(years=abs(self.years), 

443 months=abs(self.months), 

444 days=abs(self.days), 

445 hours=abs(self.hours), 

446 minutes=abs(self.minutes), 

447 seconds=abs(self.seconds), 

448 microseconds=abs(self.microseconds), 

449 leapdays=self.leapdays, 

450 year=self.year, 

451 month=self.month, 

452 day=self.day, 

453 weekday=self.weekday, 

454 hour=self.hour, 

455 minute=self.minute, 

456 second=self.second, 

457 microsecond=self.microsecond) 

458 

459 def __neg__(self): 

460 return self.__class__(years=-self.years, 

461 months=-self.months, 

462 days=-self.days, 

463 hours=-self.hours, 

464 minutes=-self.minutes, 

465 seconds=-self.seconds, 

466 microseconds=-self.microseconds, 

467 leapdays=self.leapdays, 

468 year=self.year, 

469 month=self.month, 

470 day=self.day, 

471 weekday=self.weekday, 

472 hour=self.hour, 

473 minute=self.minute, 

474 second=self.second, 

475 microsecond=self.microsecond) 

476 

477 def __bool__(self): 

478 return not (not self.years and 

479 not self.months and 

480 not self.days and 

481 not self.hours and 

482 not self.minutes and 

483 not self.seconds and 

484 not self.microseconds and 

485 not self.leapdays and 

486 self.year is None and 

487 self.month is None and 

488 self.day is None and 

489 self.weekday is None and 

490 self.hour is None and 

491 self.minute is None and 

492 self.second is None and 

493 self.microsecond is None) 

494 

495 def __mul__(self, other): 

496 try: 

497 f = float(other) 

498 except TypeError: 

499 return NotImplemented 

500 

501 return self.__class__(years=int(self.years * f), 

502 months=int(self.months * f), 

503 days=int(self.days * f), 

504 hours=int(self.hours * f), 

505 minutes=int(self.minutes * f), 

506 seconds=int(self.seconds * f), 

507 microseconds=int(self.microseconds * f), 

508 leapdays=self.leapdays, 

509 year=self.year, 

510 month=self.month, 

511 day=self.day, 

512 weekday=self.weekday, 

513 hour=self.hour, 

514 minute=self.minute, 

515 second=self.second, 

516 microsecond=self.microsecond) 

517 

518 __rmul__ = __mul__ 

519 

520 def __eq__(self, other): 

521 if not isinstance(other, relativedelta): 

522 return NotImplemented 

523 if self.weekday or other.weekday: 

524 if not self.weekday or not other.weekday: 

525 return False 

526 if self.weekday.weekday != other.weekday.weekday: 

527 return False 

528 n1, n2 = self.weekday.n, other.weekday.n 

529 if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): 

530 return False 

531 return (self.years == other.years and 

532 self.months == other.months and 

533 self.days == other.days and 

534 self.hours == other.hours and 

535 self.minutes == other.minutes and 

536 self.seconds == other.seconds and 

537 self.microseconds == other.microseconds and 

538 self.leapdays == other.leapdays and 

539 self.year == other.year and 

540 self.month == other.month and 

541 self.day == other.day and 

542 self.hour == other.hour and 

543 self.minute == other.minute and 

544 self.second == other.second and 

545 self.microsecond == other.microsecond) 

546 

547 def __hash__(self): 

548 return hash(( 

549 self.weekday, 

550 self.years, 

551 self.months, 

552 self.days, 

553 self.hours, 

554 self.minutes, 

555 self.seconds, 

556 self.microseconds, 

557 self.leapdays, 

558 self.year, 

559 self.month, 

560 self.day, 

561 self.hour, 

562 self.minute, 

563 self.second, 

564 self.microsecond, 

565 )) 

566 

567 def __truediv__(self, other): 

568 try: 

569 reciprocal = 1 / float(other) 

570 except TypeError: 

571 return NotImplemented 

572 

573 return self.__mul__(reciprocal) 

574 

575 

576 def __repr__(self): 

577 l = [] 

578 for attr in ["years", "months", "days", "leapdays", 

579 "hours", "minutes", "seconds", "microseconds"]: 

580 value = getattr(self, attr) 

581 if value: 

582 l.append("{attr}={value:+g}".format(attr=attr, value=value)) 

583 for attr in ["year", "month", "day", "weekday", 

584 "hour", "minute", "second", "microsecond"]: 

585 value = getattr(self, attr) 

586 if value is not None: 

587 l.append("{attr}={value}".format(attr=attr, value=repr(value))) 

588 return "{classname}({attrs})".format(classname=self.__class__.__name__, 

589 attrs=", ".join(l)) 

590 

591 

592def _sign(x): 

593 return int(copysign(1, x)) 

594 

595# vim:ts=4:sw=4:et