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

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 if yearday > 59: 

212 self.leapdays = -1 

213 if yday: 

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

215 243, 273, 304, 334, 366] 

216 for idx, ydays in enumerate(ydayidx): 

217 if yday <= ydays: 

218 self.month = idx+1 

219 if idx == 0: 

220 self.day = yday 

221 else: 

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

223 break 

224 else: 

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

226 

227 self._fix() 

228 

229 def _fix(self): 

230 if abs(self.microseconds) > 999999: 

231 s = _sign(self.microseconds) 

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

233 self.microseconds = mod * s 

234 self.seconds += div * s 

235 if abs(self.seconds) > 59: 

236 s = _sign(self.seconds) 

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

238 self.seconds = mod * s 

239 self.minutes += div * s 

240 if abs(self.minutes) > 59: 

241 s = _sign(self.minutes) 

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

243 self.minutes = mod * s 

244 self.hours += div * s 

245 if abs(self.hours) > 23: 

246 s = _sign(self.hours) 

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

248 self.hours = mod * s 

249 self.days += div * s 

250 if abs(self.months) > 11: 

251 s = _sign(self.months) 

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

253 self.months = mod * s 

254 self.years += div * s 

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

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

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

258 self._has_time = 1 

259 else: 

260 self._has_time = 0 

261 

262 @property 

263 def weeks(self): 

264 return int(self.days / 7.0) 

265 

266 @weeks.setter 

267 def weeks(self, value): 

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

269 

270 def _set_months(self, months): 

271 self.months = months 

272 if abs(self.months) > 11: 

273 s = _sign(self.months) 

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

275 self.months = mod * s 

276 self.years = div * s 

277 else: 

278 self.years = 0 

279 

280 def normalized(self): 

281 """ 

282 Return a version of this object represented entirely using integer 

283 values for the relative attributes. 

284 

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

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

287 

288 :return: 

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

290 """ 

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

292 days = int(self.days) 

293 

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

295 hours = int(hours_f) 

296 

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

298 minutes = int(minutes_f) 

299 

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

301 seconds = int(seconds_f) 

302 

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

304 

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

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

307 days=days, hours=hours, minutes=minutes, 

308 seconds=seconds, microseconds=microseconds, 

309 leapdays=self.leapdays, year=self.year, 

310 month=self.month, day=self.day, 

311 weekday=self.weekday, hour=self.hour, 

312 minute=self.minute, second=self.second, 

313 microsecond=self.microsecond) 

314 

315 def __add__(self, other): 

316 if isinstance(other, relativedelta): 

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

318 months=other.months + self.months, 

319 days=other.days + self.days, 

320 hours=other.hours + self.hours, 

321 minutes=other.minutes + self.minutes, 

322 seconds=other.seconds + self.seconds, 

323 microseconds=(other.microseconds + 

324 self.microseconds), 

325 leapdays=other.leapdays or self.leapdays, 

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

327 else self.year), 

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

329 else self.month), 

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

331 else self.day), 

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

333 else self.weekday), 

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

335 else self.hour), 

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

337 else self.minute), 

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

339 else self.second), 

340 microsecond=(other.microsecond if other.microsecond 

341 is not None else 

342 self.microsecond)) 

343 if isinstance(other, datetime.timedelta): 

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

345 months=self.months, 

346 days=self.days + other.days, 

347 hours=self.hours, 

348 minutes=self.minutes, 

349 seconds=self.seconds + other.seconds, 

350 microseconds=self.microseconds + other.microseconds, 

351 leapdays=self.leapdays, 

352 year=self.year, 

353 month=self.month, 

354 day=self.day, 

355 weekday=self.weekday, 

356 hour=self.hour, 

357 minute=self.minute, 

358 second=self.second, 

359 microsecond=self.microsecond) 

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

361 return NotImplemented 

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

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

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

365 month = self.month or other.month 

366 if self.months: 

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

368 month += self.months 

369 if month > 12: 

370 year += 1 

371 month -= 12 

372 elif month < 1: 

373 year -= 1 

374 month += 12 

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

376 self.day or other.day) 

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

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

379 value = getattr(self, attr) 

380 if value is not None: 

381 repl[attr] = value 

382 days = self.days 

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

384 days += self.leapdays 

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

386 + datetime.timedelta(days=days, 

387 hours=self.hours, 

388 minutes=self.minutes, 

389 seconds=self.seconds, 

390 microseconds=self.microseconds)) 

391 if self.weekday: 

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

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

394 if nth > 0: 

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

396 else: 

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

398 jumpdays *= -1 

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

400 return ret 

401 

402 def __radd__(self, other): 

403 return self.__add__(other) 

404 

405 def __rsub__(self, other): 

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

407 

408 def __sub__(self, other): 

409 if not isinstance(other, relativedelta): 

410 return NotImplemented # In case the other object defines __rsub__ 

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

412 months=self.months - other.months, 

413 days=self.days - other.days, 

414 hours=self.hours - other.hours, 

415 minutes=self.minutes - other.minutes, 

416 seconds=self.seconds - other.seconds, 

417 microseconds=self.microseconds - other.microseconds, 

418 leapdays=self.leapdays or other.leapdays, 

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

420 else other.year), 

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

422 other.month), 

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

424 other.day), 

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

426 other.weekday), 

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

428 other.hour), 

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

430 other.minute), 

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

432 other.second), 

433 microsecond=(self.microsecond if self.microsecond 

434 is not None else 

435 other.microsecond)) 

436 

437 def __abs__(self): 

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

439 months=abs(self.months), 

440 days=abs(self.days), 

441 hours=abs(self.hours), 

442 minutes=abs(self.minutes), 

443 seconds=abs(self.seconds), 

444 microseconds=abs(self.microseconds), 

445 leapdays=self.leapdays, 

446 year=self.year, 

447 month=self.month, 

448 day=self.day, 

449 weekday=self.weekday, 

450 hour=self.hour, 

451 minute=self.minute, 

452 second=self.second, 

453 microsecond=self.microsecond) 

454 

455 def __neg__(self): 

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

457 months=-self.months, 

458 days=-self.days, 

459 hours=-self.hours, 

460 minutes=-self.minutes, 

461 seconds=-self.seconds, 

462 microseconds=-self.microseconds, 

463 leapdays=self.leapdays, 

464 year=self.year, 

465 month=self.month, 

466 day=self.day, 

467 weekday=self.weekday, 

468 hour=self.hour, 

469 minute=self.minute, 

470 second=self.second, 

471 microsecond=self.microsecond) 

472 

473 def __bool__(self): 

474 return not (not self.years and 

475 not self.months and 

476 not self.days and 

477 not self.hours and 

478 not self.minutes and 

479 not self.seconds and 

480 not self.microseconds and 

481 not self.leapdays and 

482 self.year is None and 

483 self.month is None and 

484 self.day is None and 

485 self.weekday is None and 

486 self.hour is None and 

487 self.minute is None and 

488 self.second is None and 

489 self.microsecond is None) 

490 

491 def __mul__(self, other): 

492 try: 

493 f = float(other) 

494 except TypeError: 

495 return NotImplemented 

496 

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

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

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

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

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

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

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

504 leapdays=self.leapdays, 

505 year=self.year, 

506 month=self.month, 

507 day=self.day, 

508 weekday=self.weekday, 

509 hour=self.hour, 

510 minute=self.minute, 

511 second=self.second, 

512 microsecond=self.microsecond) 

513 

514 __rmul__ = __mul__ 

515 

516 def __eq__(self, other): 

517 if not isinstance(other, relativedelta): 

518 return NotImplemented 

519 if self.weekday or other.weekday: 

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

521 return False 

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

523 return False 

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

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

526 return False 

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

528 self.months == other.months and 

529 self.days == other.days and 

530 self.hours == other.hours and 

531 self.minutes == other.minutes and 

532 self.seconds == other.seconds and 

533 self.microseconds == other.microseconds and 

534 self.leapdays == other.leapdays and 

535 self.year == other.year and 

536 self.month == other.month and 

537 self.day == other.day and 

538 self.hour == other.hour and 

539 self.minute == other.minute and 

540 self.second == other.second and 

541 self.microsecond == other.microsecond) 

542 

543 def __hash__(self): 

544 return hash(( 

545 self.weekday, 

546 self.years, 

547 self.months, 

548 self.days, 

549 self.hours, 

550 self.minutes, 

551 self.seconds, 

552 self.microseconds, 

553 self.leapdays, 

554 self.year, 

555 self.month, 

556 self.day, 

557 self.hour, 

558 self.minute, 

559 self.second, 

560 self.microsecond, 

561 )) 

562 

563 def __truediv__(self, other): 

564 try: 

565 reciprocal = 1 / float(other) 

566 except TypeError: 

567 return NotImplemented 

568 

569 return self.__mul__(reciprocal) 

570 

571 

572 def __repr__(self): 

573 l = [] 

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

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

576 value = getattr(self, attr) 

577 if value: 

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

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

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

581 value = getattr(self, attr) 

582 if value is not None: 

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

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

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

586 

587 

588def _sign(x): 

589 return int(copysign(1, x)) 

590 

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