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

241 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1# -*- coding: utf-8 -*- 

2import datetime 

3import calendar 

4 

5import operator 

6from math import copysign 

7 

8from six import integer_types 

9from warnings import warn 

10 

11from ._common import weekday 

12 

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

14 

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

16 

17 

18class relativedelta(object): 

19 """ 

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

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

22 of time. 

23 

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

25 in his 

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

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

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

29 

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

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

32 

33 relativedelta(datetime1, datetime2) 

34 

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

36 

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

38 

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

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

41 relativedelta with absolute information does not perform an arithmetic 

42 operation, but rather REPLACES the corresponding value in the 

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

44 

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

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

47 or subtracting a relativedelta with relative information performs 

48 the corresponding arithmetic operation on the original datetime value 

49 with the information in the relativedelta. 

50 

51 weekday:  

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

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

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

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

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

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

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

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

60 Monday of the month). 

61 

62 leapdays: 

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

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

65 

66 yearday, nlyearday: 

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

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

69 

70 There are relative and absolute forms of the keyword 

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

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

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

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

75 

76 The order of attributes considered when this relativedelta is 

77 added to a datetime is: 

78 

79 1. Year 

80 2. Month 

81 3. Day 

82 4. Hours 

83 5. Minutes 

84 6. Seconds 

85 7. Microseconds 

86 

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

88 

89 For example 

90 

91 >>> from datetime import datetime 

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

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

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

95 >>> dt + delta 

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

97 

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

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

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

101 no effect. 

102 

103 """ 

104 

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

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

107 hours=0, minutes=0, seconds=0, microseconds=0, 

108 year=None, month=None, day=None, weekday=None, 

109 yearday=None, nlyearday=None, 

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

111 

112 if dt1 and dt2: 

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

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

115 isinstance(dt2, datetime.date)): 

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

117 

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

119 # of the same type 

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

121 isinstance(dt2, datetime.datetime)): 

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

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

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

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

126 

127 self.years = 0 

128 self.months = 0 

129 self.days = 0 

130 self.leapdays = 0 

131 self.hours = 0 

132 self.minutes = 0 

133 self.seconds = 0 

134 self.microseconds = 0 

135 self.year = None 

136 self.month = None 

137 self.day = None 

138 self.weekday = None 

139 self.hour = None 

140 self.minute = None 

141 self.second = None 

142 self.microsecond = None 

143 self._has_time = 0 

144 

145 # Get year / month delta between the two 

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

147 self._set_months(months) 

148 

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

150 # time units (seconds, days and microseconds) 

151 dtm = self.__radd__(dt2) 

152 

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

154 if dt1 < dt2: 

155 compare = operator.gt 

156 increment = 1 

157 else: 

158 compare = operator.lt 

159 increment = -1 

160 

161 while compare(dt1, dtm): 

162 months += increment 

163 self._set_months(months) 

164 dtm = self.__radd__(dt2) 

165 

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

167 delta = dt1 - dtm 

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

169 self.microseconds = delta.microseconds 

170 else: 

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

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

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

174 "ambiguous and not currently supported.") 

175 

176 # Relative information 

177 self.years = int(years) 

178 self.months = int(months) 

179 self.days = days + weeks * 7 

180 self.leapdays = leapdays 

181 self.hours = hours 

182 self.minutes = minutes 

183 self.seconds = seconds 

184 self.microseconds = microseconds 

185 

186 # Absolute information 

187 self.year = year 

188 self.month = month 

189 self.day = day 

190 self.hour = hour 

191 self.minute = minute 

192 self.second = second 

193 self.microsecond = microsecond 

194 

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

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

197 minute, second, microsecond)): 

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

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

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

201 "errors in future versions.", DeprecationWarning) 

202 

203 if isinstance(weekday, integer_types): 

204 self.weekday = weekdays[weekday] 

205 else: 

206 self.weekday = weekday 

207 

208 yday = 0 

209 if nlyearday: 

210 yday = nlyearday 

211 elif yearday: 

212 yday = yearday 

213 if yearday > 59: 

214 self.leapdays = -1 

215 if yday: 

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

217 243, 273, 304, 334, 366] 

218 for idx, ydays in enumerate(ydayidx): 

219 if yday <= ydays: 

220 self.month = idx+1 

221 if idx == 0: 

222 self.day = yday 

223 else: 

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

225 break 

226 else: 

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

228 

229 self._fix() 

230 

231 def _fix(self): 

232 if abs(self.microseconds) > 999999: 

233 s = _sign(self.microseconds) 

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

235 self.microseconds = mod * s 

236 self.seconds += div * s 

237 if abs(self.seconds) > 59: 

238 s = _sign(self.seconds) 

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

240 self.seconds = mod * s 

241 self.minutes += div * s 

242 if abs(self.minutes) > 59: 

243 s = _sign(self.minutes) 

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

245 self.minutes = mod * s 

246 self.hours += div * s 

247 if abs(self.hours) > 23: 

248 s = _sign(self.hours) 

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

250 self.hours = mod * s 

251 self.days += div * s 

252 if abs(self.months) > 11: 

253 s = _sign(self.months) 

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

255 self.months = mod * s 

256 self.years += div * s 

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

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

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

260 self._has_time = 1 

261 else: 

262 self._has_time = 0 

263 

264 @property 

265 def weeks(self): 

266 return int(self.days / 7.0) 

267 

268 @weeks.setter 

269 def weeks(self, value): 

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

271 

272 def _set_months(self, months): 

273 self.months = months 

274 if abs(self.months) > 11: 

275 s = _sign(self.months) 

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

277 self.months = mod * s 

278 self.years = div * s 

279 else: 

280 self.years = 0 

281 

282 def normalized(self): 

283 """ 

284 Return a version of this object represented entirely using integer 

285 values for the relative attributes. 

286 

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

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

289 

290 :return: 

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

292 """ 

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

294 days = int(self.days) 

295 

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

297 hours = int(hours_f) 

298 

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

300 minutes = int(minutes_f) 

301 

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

303 seconds = int(seconds_f) 

304 

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

306 

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

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

309 days=days, hours=hours, minutes=minutes, 

310 seconds=seconds, microseconds=microseconds, 

311 leapdays=self.leapdays, year=self.year, 

312 month=self.month, day=self.day, 

313 weekday=self.weekday, hour=self.hour, 

314 minute=self.minute, second=self.second, 

315 microsecond=self.microsecond) 

316 

317 def __add__(self, other): 

318 if isinstance(other, relativedelta): 

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

320 months=other.months + self.months, 

321 days=other.days + self.days, 

322 hours=other.hours + self.hours, 

323 minutes=other.minutes + self.minutes, 

324 seconds=other.seconds + self.seconds, 

325 microseconds=(other.microseconds + 

326 self.microseconds), 

327 leapdays=other.leapdays or self.leapdays, 

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

329 else self.year), 

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

331 else self.month), 

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

333 else self.day), 

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

335 else self.weekday), 

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

337 else self.hour), 

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

339 else self.minute), 

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

341 else self.second), 

342 microsecond=(other.microsecond if other.microsecond 

343 is not None else 

344 self.microsecond)) 

345 if isinstance(other, datetime.timedelta): 

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

347 months=self.months, 

348 days=self.days + other.days, 

349 hours=self.hours, 

350 minutes=self.minutes, 

351 seconds=self.seconds + other.seconds, 

352 microseconds=self.microseconds + other.microseconds, 

353 leapdays=self.leapdays, 

354 year=self.year, 

355 month=self.month, 

356 day=self.day, 

357 weekday=self.weekday, 

358 hour=self.hour, 

359 minute=self.minute, 

360 second=self.second, 

361 microsecond=self.microsecond) 

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

363 return NotImplemented 

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

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

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

367 month = self.month or other.month 

368 if self.months: 

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

370 month += self.months 

371 if month > 12: 

372 year += 1 

373 month -= 12 

374 elif month < 1: 

375 year -= 1 

376 month += 12 

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

378 self.day or other.day) 

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

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

381 value = getattr(self, attr) 

382 if value is not None: 

383 repl[attr] = value 

384 days = self.days 

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

386 days += self.leapdays 

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

388 + datetime.timedelta(days=days, 

389 hours=self.hours, 

390 minutes=self.minutes, 

391 seconds=self.seconds, 

392 microseconds=self.microseconds)) 

393 if self.weekday: 

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

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

396 if nth > 0: 

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

398 else: 

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

400 jumpdays *= -1 

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

402 return ret 

403 

404 def __radd__(self, other): 

405 return self.__add__(other) 

406 

407 def __rsub__(self, other): 

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

409 

410 def __sub__(self, other): 

411 if not isinstance(other, relativedelta): 

412 return NotImplemented # In case the other object defines __rsub__ 

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

414 months=self.months - other.months, 

415 days=self.days - other.days, 

416 hours=self.hours - other.hours, 

417 minutes=self.minutes - other.minutes, 

418 seconds=self.seconds - other.seconds, 

419 microseconds=self.microseconds - other.microseconds, 

420 leapdays=self.leapdays or other.leapdays, 

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

422 else other.year), 

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

424 other.month), 

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

426 other.day), 

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

428 other.weekday), 

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

430 other.hour), 

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

432 other.minute), 

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

434 other.second), 

435 microsecond=(self.microsecond if self.microsecond 

436 is not None else 

437 other.microsecond)) 

438 

439 def __abs__(self): 

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

441 months=abs(self.months), 

442 days=abs(self.days), 

443 hours=abs(self.hours), 

444 minutes=abs(self.minutes), 

445 seconds=abs(self.seconds), 

446 microseconds=abs(self.microseconds), 

447 leapdays=self.leapdays, 

448 year=self.year, 

449 month=self.month, 

450 day=self.day, 

451 weekday=self.weekday, 

452 hour=self.hour, 

453 minute=self.minute, 

454 second=self.second, 

455 microsecond=self.microsecond) 

456 

457 def __neg__(self): 

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

459 months=-self.months, 

460 days=-self.days, 

461 hours=-self.hours, 

462 minutes=-self.minutes, 

463 seconds=-self.seconds, 

464 microseconds=-self.microseconds, 

465 leapdays=self.leapdays, 

466 year=self.year, 

467 month=self.month, 

468 day=self.day, 

469 weekday=self.weekday, 

470 hour=self.hour, 

471 minute=self.minute, 

472 second=self.second, 

473 microsecond=self.microsecond) 

474 

475 def __bool__(self): 

476 return not (not self.years and 

477 not self.months and 

478 not self.days and 

479 not self.hours and 

480 not self.minutes and 

481 not self.seconds and 

482 not self.microseconds and 

483 not self.leapdays and 

484 self.year is None and 

485 self.month is None and 

486 self.day is None and 

487 self.weekday is None and 

488 self.hour is None and 

489 self.minute is None and 

490 self.second is None and 

491 self.microsecond is None) 

492 # Compatibility with Python 2.x 

493 __nonzero__ = __bool__ 

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 __ne__(self, other): 

568 return not self.__eq__(other) 

569 

570 def __div__(self, other): 

571 try: 

572 reciprocal = 1 / float(other) 

573 except TypeError: 

574 return NotImplemented 

575 

576 return self.__mul__(reciprocal) 

577 

578 __truediv__ = __div__ 

579 

580 def __repr__(self): 

581 l = [] 

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

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

584 value = getattr(self, attr) 

585 if value: 

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

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

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

589 value = getattr(self, attr) 

590 if value is not None: 

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

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

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

594 

595 

596def _sign(x): 

597 return int(copysign(1, x)) 

598 

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