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

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

515 statements  

1from __future__ import annotations 

2 

3import calendar 

4import datetime 

5import traceback 

6 

7from typing import TYPE_CHECKING 

8from typing import Any 

9from typing import Callable 

10from typing import ClassVar 

11from typing import Optional 

12from typing import cast 

13from typing import overload 

14 

15import pendulum 

16 

17from pendulum.constants import ATOM 

18from pendulum.constants import COOKIE 

19from pendulum.constants import MINUTES_PER_HOUR 

20from pendulum.constants import MONTHS_PER_YEAR 

21from pendulum.constants import RFC822 

22from pendulum.constants import RFC850 

23from pendulum.constants import RFC1036 

24from pendulum.constants import RFC1123 

25from pendulum.constants import RFC2822 

26from pendulum.constants import RSS 

27from pendulum.constants import SECONDS_PER_DAY 

28from pendulum.constants import SECONDS_PER_MINUTE 

29from pendulum.constants import W3C 

30from pendulum.constants import YEARS_PER_CENTURY 

31from pendulum.constants import YEARS_PER_DECADE 

32from pendulum.date import Date 

33from pendulum.day import WeekDay 

34from pendulum.exceptions import PendulumException 

35from pendulum.helpers import add_duration 

36from pendulum.interval import Interval 

37from pendulum.time import Time 

38from pendulum.tz import UTC 

39from pendulum.tz import local_timezone 

40from pendulum.tz.timezone import FixedTimezone 

41from pendulum.tz.timezone import Timezone 

42 

43 

44if TYPE_CHECKING: 

45 from typing_extensions import Literal 

46 from typing_extensions import Self 

47 from typing_extensions import SupportsIndex 

48 

49 

50class DateTime(datetime.datetime, Date): 

51 EPOCH: ClassVar[DateTime] 

52 min: ClassVar[DateTime] 

53 max: ClassVar[DateTime] 

54 

55 # Formats 

56 

57 _FORMATS: ClassVar[dict[str, str | Callable[[datetime.datetime], str]]] = { 

58 "atom": ATOM, 

59 "cookie": COOKIE, 

60 "iso8601": lambda dt: dt.isoformat("T"), 

61 "rfc822": RFC822, 

62 "rfc850": RFC850, 

63 "rfc1036": RFC1036, 

64 "rfc1123": RFC1123, 

65 "rfc2822": RFC2822, 

66 "rfc3339": lambda dt: dt.isoformat("T"), 

67 "rss": RSS, 

68 "w3c": W3C, 

69 } 

70 

71 _MODIFIERS_VALID_UNITS: ClassVar[list[str]] = [ 

72 "second", 

73 "minute", 

74 "hour", 

75 "day", 

76 "week", 

77 "month", 

78 "year", 

79 "decade", 

80 "century", 

81 ] 

82 

83 _EPOCH: datetime.datetime = datetime.datetime(1970, 1, 1, tzinfo=UTC) 

84 

85 @classmethod 

86 def create( 

87 cls, 

88 year: SupportsIndex, 

89 month: SupportsIndex, 

90 day: SupportsIndex, 

91 hour: SupportsIndex = 0, 

92 minute: SupportsIndex = 0, 

93 second: SupportsIndex = 0, 

94 microsecond: SupportsIndex = 0, 

95 tz: str | float | Timezone | FixedTimezone | None | datetime.tzinfo = UTC, 

96 fold: int = 1, 

97 raise_on_unknown_times: bool = False, 

98 ) -> Self: 

99 """ 

100 Creates a new DateTime instance from a specific date and time. 

101 """ 

102 if tz is not None: 

103 tz = pendulum._safe_timezone(tz) 

104 

105 dt = datetime.datetime( 

106 year, month, day, hour, minute, second, microsecond, fold=fold 

107 ) 

108 

109 if tz is not None: 

110 dt = tz.convert(dt, raise_on_unknown_times=raise_on_unknown_times) 

111 

112 return cls( 

113 dt.year, 

114 dt.month, 

115 dt.day, 

116 dt.hour, 

117 dt.minute, 

118 dt.second, 

119 dt.microsecond, 

120 tzinfo=dt.tzinfo, 

121 fold=dt.fold, 

122 ) 

123 

124 @classmethod 

125 def instance( 

126 cls, 

127 dt: datetime.datetime, 

128 tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = UTC, 

129 ) -> Self: 

130 tz = dt.tzinfo or tz 

131 

132 if tz is not None: 

133 tz = pendulum._safe_timezone(tz, dt=dt) 

134 

135 return cls.create( 

136 dt.year, 

137 dt.month, 

138 dt.day, 

139 dt.hour, 

140 dt.minute, 

141 dt.second, 

142 dt.microsecond, 

143 tz=tz, 

144 fold=dt.fold, 

145 ) 

146 

147 @overload 

148 @classmethod 

149 def now(cls, tz: datetime.tzinfo | None = None) -> Self: 

150 ... 

151 

152 @overload 

153 @classmethod 

154 def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> Self: 

155 ... 

156 

157 @classmethod 

158 def now( 

159 cls, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = None 

160 ) -> Self: 

161 """ 

162 Get a DateTime instance for the current date and time. 

163 """ 

164 if tz is None or tz == "local": 

165 dt = datetime.datetime.now(local_timezone()) 

166 elif tz is UTC or tz == "UTC": 

167 dt = datetime.datetime.now(UTC) 

168 else: 

169 dt = datetime.datetime.now(UTC) 

170 tz = pendulum._safe_timezone(tz) 

171 dt = dt.astimezone(tz) 

172 

173 return cls( 

174 dt.year, 

175 dt.month, 

176 dt.day, 

177 dt.hour, 

178 dt.minute, 

179 dt.second, 

180 dt.microsecond, 

181 tzinfo=dt.tzinfo, 

182 fold=dt.fold, 

183 ) 

184 

185 @classmethod 

186 def utcnow(cls) -> Self: 

187 """ 

188 Get a DateTime instance for the current date and time in UTC. 

189 """ 

190 return cls.now(UTC) 

191 

192 @classmethod 

193 def today(cls) -> Self: 

194 return cls.now() 

195 

196 @classmethod 

197 def strptime(cls, time: str, fmt: str) -> Self: 

198 return cls.instance(datetime.datetime.strptime(time, fmt)) 

199 

200 # Getters/Setters 

201 

202 def set( 

203 self, 

204 year: int | None = None, 

205 month: int | None = None, 

206 day: int | None = None, 

207 hour: int | None = None, 

208 minute: int | None = None, 

209 second: int | None = None, 

210 microsecond: int | None = None, 

211 tz: str | float | Timezone | FixedTimezone | datetime.tzinfo | None = None, 

212 ) -> Self: 

213 if year is None: 

214 year = self.year 

215 if month is None: 

216 month = self.month 

217 if day is None: 

218 day = self.day 

219 if hour is None: 

220 hour = self.hour 

221 if minute is None: 

222 minute = self.minute 

223 if second is None: 

224 second = self.second 

225 if microsecond is None: 

226 microsecond = self.microsecond 

227 if tz is None: 

228 tz = self.tz 

229 

230 return self.__class__.create( 

231 year, month, day, hour, minute, second, microsecond, tz=tz, fold=self.fold 

232 ) 

233 

234 @property 

235 def float_timestamp(self) -> float: 

236 return self.timestamp() 

237 

238 @property 

239 def int_timestamp(self) -> int: 

240 # Workaround needed to avoid inaccuracy 

241 # for far into the future datetimes 

242 dt = datetime.datetime( 

243 self.year, 

244 self.month, 

245 self.day, 

246 self.hour, 

247 self.minute, 

248 self.second, 

249 self.microsecond, 

250 tzinfo=self.tzinfo, 

251 fold=self.fold, 

252 ) 

253 

254 delta = dt - self._EPOCH 

255 

256 return delta.days * SECONDS_PER_DAY + delta.seconds 

257 

258 @property 

259 def offset(self) -> int | None: 

260 return self.get_offset() 

261 

262 @property 

263 def offset_hours(self) -> float | None: 

264 offset = self.get_offset() 

265 

266 if offset is None: 

267 return None 

268 

269 return offset / SECONDS_PER_MINUTE / MINUTES_PER_HOUR 

270 

271 @property 

272 def timezone(self) -> Timezone | FixedTimezone | None: 

273 if not isinstance(self.tzinfo, (Timezone, FixedTimezone)): 

274 return None 

275 

276 return self.tzinfo 

277 

278 @property 

279 def tz(self) -> Timezone | FixedTimezone | None: 

280 return self.timezone 

281 

282 @property 

283 def timezone_name(self) -> str | None: 

284 tz = self.timezone 

285 

286 if tz is None: 

287 return None 

288 

289 return tz.name 

290 

291 @property 

292 def age(self) -> int: 

293 return self.date().diff(self.now(self.tz).date(), abs=False).in_years() 

294 

295 def is_local(self) -> bool: 

296 return self.offset == self.in_timezone(pendulum.local_timezone()).offset 

297 

298 def is_utc(self) -> bool: 

299 return self.offset == 0 

300 

301 def is_dst(self) -> bool: 

302 return self.dst() != datetime.timedelta() 

303 

304 def get_offset(self) -> int | None: 

305 utcoffset = self.utcoffset() 

306 if utcoffset is None: 

307 return None 

308 

309 return int(utcoffset.total_seconds()) 

310 

311 def date(self) -> Date: 

312 return Date(self.year, self.month, self.day) 

313 

314 def time(self) -> Time: 

315 return Time(self.hour, self.minute, self.second, self.microsecond) 

316 

317 def naive(self) -> Self: 

318 """ 

319 Return the DateTime without timezone information. 

320 """ 

321 return self.__class__( 

322 self.year, 

323 self.month, 

324 self.day, 

325 self.hour, 

326 self.minute, 

327 self.second, 

328 self.microsecond, 

329 ) 

330 

331 def on(self, year: int, month: int, day: int) -> Self: 

332 """ 

333 Returns a new instance with the current date set to a different date. 

334 """ 

335 return self.set(year=int(year), month=int(month), day=int(day)) 

336 

337 def at( 

338 self, hour: int, minute: int = 0, second: int = 0, microsecond: int = 0 

339 ) -> Self: 

340 """ 

341 Returns a new instance with the current time to a different time. 

342 """ 

343 return self.set( 

344 hour=hour, minute=minute, second=second, microsecond=microsecond 

345 ) 

346 

347 def in_timezone(self, tz: str | Timezone | FixedTimezone) -> Self: 

348 """ 

349 Set the instance's timezone from a string or object. 

350 """ 

351 tz = pendulum._safe_timezone(tz) 

352 

353 dt = self 

354 if not self.timezone: 

355 dt = dt.replace(fold=1) 

356 

357 return tz.convert(dt) 

358 

359 def in_tz(self, tz: str | Timezone | FixedTimezone) -> Self: 

360 """ 

361 Set the instance's timezone from a string or object. 

362 """ 

363 return self.in_timezone(tz) 

364 

365 # STRING FORMATTING 

366 

367 def to_time_string(self) -> str: 

368 """ 

369 Format the instance as time. 

370 """ 

371 return self.format("HH:mm:ss") 

372 

373 def to_datetime_string(self) -> str: 

374 """ 

375 Format the instance as date and time. 

376 """ 

377 return self.format("YYYY-MM-DD HH:mm:ss") 

378 

379 def to_day_datetime_string(self) -> str: 

380 """ 

381 Format the instance as day, date and time (in english). 

382 """ 

383 return self.format("ddd, MMM D, YYYY h:mm A", locale="en") 

384 

385 def to_atom_string(self) -> str: 

386 """ 

387 Format the instance as ATOM. 

388 """ 

389 return self._to_string("atom") 

390 

391 def to_cookie_string(self) -> str: 

392 """ 

393 Format the instance as COOKIE. 

394 """ 

395 return self._to_string("cookie", locale="en") 

396 

397 def to_iso8601_string(self) -> str: 

398 """ 

399 Format the instance as ISO 8601. 

400 """ 

401 string = self._to_string("iso8601") 

402 

403 if self.tz and self.tz.name == "UTC": 

404 string = string.replace("+00:00", "Z") 

405 

406 return string 

407 

408 def to_rfc822_string(self) -> str: 

409 """ 

410 Format the instance as RFC 822. 

411 """ 

412 return self._to_string("rfc822") 

413 

414 def to_rfc850_string(self) -> str: 

415 """ 

416 Format the instance as RFC 850. 

417 """ 

418 return self._to_string("rfc850") 

419 

420 def to_rfc1036_string(self) -> str: 

421 """ 

422 Format the instance as RFC 1036. 

423 """ 

424 return self._to_string("rfc1036") 

425 

426 def to_rfc1123_string(self) -> str: 

427 """ 

428 Format the instance as RFC 1123. 

429 """ 

430 return self._to_string("rfc1123") 

431 

432 def to_rfc2822_string(self) -> str: 

433 """ 

434 Format the instance as RFC 2822. 

435 """ 

436 return self._to_string("rfc2822") 

437 

438 def to_rfc3339_string(self) -> str: 

439 """ 

440 Format the instance as RFC 3339. 

441 """ 

442 return self._to_string("rfc3339") 

443 

444 def to_rss_string(self) -> str: 

445 """ 

446 Format the instance as RSS. 

447 """ 

448 return self._to_string("rss") 

449 

450 def to_w3c_string(self) -> str: 

451 """ 

452 Format the instance as W3C. 

453 """ 

454 return self._to_string("w3c") 

455 

456 def _to_string(self, fmt: str, locale: str | None = None) -> str: 

457 """ 

458 Format the instance to a common string format. 

459 """ 

460 if fmt not in self._FORMATS: 

461 raise ValueError(f"Format [{fmt}] is not supported") 

462 

463 fmt_value = self._FORMATS[fmt] 

464 if callable(fmt_value): 

465 return fmt_value(self) 

466 

467 return self.format(fmt_value, locale=locale) 

468 

469 def __str__(self) -> str: 

470 return self.isoformat(" ") 

471 

472 def __repr__(self) -> str: 

473 us = "" 

474 if self.microsecond: 

475 us = f", {self.microsecond}" 

476 

477 repr_ = "{klass}(" "{year}, {month}, {day}, " "{hour}, {minute}, {second}{us}" 

478 

479 if self.tzinfo is not None: 

480 repr_ += ", tzinfo={tzinfo}" 

481 

482 repr_ += ")" 

483 

484 return repr_.format( 

485 klass=self.__class__.__name__, 

486 year=self.year, 

487 month=self.month, 

488 day=self.day, 

489 hour=self.hour, 

490 minute=self.minute, 

491 second=self.second, 

492 us=us, 

493 tzinfo=repr(self.tzinfo), 

494 ) 

495 

496 # Comparisons 

497 def closest(self, *dts: datetime.datetime) -> Self: # type: ignore[override] 

498 """ 

499 Get the closest date to the instance. 

500 """ 

501 pdts = [self.instance(x) for x in dts] 

502 

503 return min((abs(self - dt), dt) for dt in pdts)[1] 

504 

505 def farthest(self, *dts: datetime.datetime) -> Self: # type: ignore[override] 

506 """ 

507 Get the farthest date from the instance. 

508 """ 

509 pdts = [self.instance(x) for x in dts] 

510 

511 return max((abs(self - dt), dt) for dt in pdts)[1] 

512 

513 def is_future(self) -> bool: 

514 """ 

515 Determines if the instance is in the future, ie. greater than now. 

516 """ 

517 return self > self.now(self.timezone) 

518 

519 def is_past(self) -> bool: 

520 """ 

521 Determines if the instance is in the past, ie. less than now. 

522 """ 

523 return self < self.now(self.timezone) 

524 

525 def is_long_year(self) -> bool: 

526 """ 

527 Determines if the instance is a long year 

528 

529 See link `https://en.wikipedia.org/wiki/ISO_8601#Week_dates`_ 

530 """ 

531 return ( 

532 DateTime.create(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1] 

533 == 53 

534 ) 

535 

536 def is_same_day(self, dt: datetime.datetime) -> bool: # type: ignore[override] 

537 """ 

538 Checks if the passed in date is the same day 

539 as the instance current day. 

540 """ 

541 dt = self.instance(dt) 

542 

543 return self.to_date_string() == dt.to_date_string() 

544 

545 def is_anniversary( # type: ignore[override] 

546 self, dt: datetime.datetime | None = None 

547 ) -> bool: 

548 """ 

549 Check if its the anniversary. 

550 Compares the date/month values of the two dates. 

551 """ 

552 if dt is None: 

553 dt = self.now(self.tz) 

554 

555 instance = self.instance(dt) 

556 

557 return (self.month, self.day) == (instance.month, instance.day) 

558 

559 # ADDITIONS AND SUBSTRACTIONS 

560 

561 def add( 

562 self, 

563 years: int = 0, 

564 months: int = 0, 

565 weeks: int = 0, 

566 days: int = 0, 

567 hours: int = 0, 

568 minutes: int = 0, 

569 seconds: float = 0, 

570 microseconds: int = 0, 

571 ) -> Self: 

572 """ 

573 Add a duration to the instance. 

574 

575 If we're adding units of variable length (i.e., years, months), 

576 move forward from current time, otherwise move forward from utc, for accuracy 

577 when moving across DST boundaries. 

578 """ 

579 units_of_variable_length = any([years, months, weeks, days]) 

580 

581 current_dt = datetime.datetime( 

582 self.year, 

583 self.month, 

584 self.day, 

585 self.hour, 

586 self.minute, 

587 self.second, 

588 self.microsecond, 

589 ) 

590 if not units_of_variable_length: 

591 offset = self.utcoffset() 

592 if offset: 

593 current_dt = current_dt - offset 

594 

595 dt = add_duration( 

596 current_dt, 

597 years=years, 

598 months=months, 

599 weeks=weeks, 

600 days=days, 

601 hours=hours, 

602 minutes=minutes, 

603 seconds=seconds, 

604 microseconds=microseconds, 

605 ) 

606 

607 if units_of_variable_length or self.tz is None: 

608 return self.__class__.create( 

609 dt.year, 

610 dt.month, 

611 dt.day, 

612 dt.hour, 

613 dt.minute, 

614 dt.second, 

615 dt.microsecond, 

616 tz=self.tz, 

617 ) 

618 

619 dt = datetime.datetime( 

620 dt.year, 

621 dt.month, 

622 dt.day, 

623 dt.hour, 

624 dt.minute, 

625 dt.second, 

626 dt.microsecond, 

627 tzinfo=UTC, 

628 ) 

629 

630 dt = self.tz.convert(dt) 

631 

632 return self.__class__( 

633 dt.year, 

634 dt.month, 

635 dt.day, 

636 dt.hour, 

637 dt.minute, 

638 dt.second, 

639 dt.microsecond, 

640 tzinfo=self.tz, 

641 fold=dt.fold, 

642 ) 

643 

644 def subtract( 

645 self, 

646 years: int = 0, 

647 months: int = 0, 

648 weeks: int = 0, 

649 days: int = 0, 

650 hours: int = 0, 

651 minutes: int = 0, 

652 seconds: float = 0, 

653 microseconds: int = 0, 

654 ) -> Self: 

655 """ 

656 Remove duration from the instance. 

657 """ 

658 return self.add( 

659 years=-years, 

660 months=-months, 

661 weeks=-weeks, 

662 days=-days, 

663 hours=-hours, 

664 minutes=-minutes, 

665 seconds=-seconds, 

666 microseconds=-microseconds, 

667 ) 

668 

669 # Adding a final underscore to the method name 

670 # to avoid errors for PyPy which already defines 

671 # a _add_timedelta method 

672 def _add_timedelta_(self, delta: datetime.timedelta) -> Self: 

673 """ 

674 Add timedelta duration to the instance. 

675 """ 

676 if isinstance(delta, pendulum.Interval): 

677 return self.add( 

678 years=delta.years, 

679 months=delta.months, 

680 weeks=delta.weeks, 

681 days=delta.remaining_days, 

682 hours=delta.hours, 

683 minutes=delta.minutes, 

684 seconds=delta.remaining_seconds, 

685 microseconds=delta.microseconds, 

686 ) 

687 elif isinstance(delta, pendulum.Duration): 

688 return self.add(**delta._signature) # type: ignore[attr-defined] 

689 

690 return self.add(seconds=delta.total_seconds()) 

691 

692 def _subtract_timedelta(self, delta: datetime.timedelta) -> Self: 

693 """ 

694 Remove timedelta duration from the instance. 

695 """ 

696 if isinstance(delta, pendulum.Duration): 

697 return self.subtract( 

698 years=delta.years, months=delta.months, seconds=delta._total 

699 ) 

700 

701 return self.subtract(seconds=delta.total_seconds()) 

702 

703 # DIFFERENCES 

704 

705 def diff( # type: ignore[override] 

706 self, dt: datetime.datetime | None = None, abs: bool = True 

707 ) -> Interval: 

708 """ 

709 Returns the difference between two DateTime objects represented as an Interval. 

710 """ 

711 if dt is None: 

712 dt = self.now(self.tz) 

713 

714 return Interval(self, dt, absolute=abs) 

715 

716 def diff_for_humans( # type: ignore[override] 

717 self, 

718 other: DateTime | None = None, 

719 absolute: bool = False, 

720 locale: str | None = None, 

721 ) -> str: 

722 """ 

723 Get the difference in a human readable format in the current locale. 

724 

725 When comparing a value in the past to default now: 

726 1 day ago 

727 5 months ago 

728 

729 When comparing a value in the future to default now: 

730 1 day from now 

731 5 months from now 

732 

733 When comparing a value in the past to another value: 

734 1 day before 

735 5 months before 

736 

737 When comparing a value in the future to another value: 

738 1 day after 

739 5 months after 

740 """ 

741 is_now = other is None 

742 

743 if is_now: 

744 other = self.now() 

745 

746 diff = self.diff(other) 

747 

748 return pendulum.format_diff(diff, is_now, absolute, locale) 

749 

750 # Modifiers 

751 def start_of(self, unit: str) -> Self: 

752 """ 

753 Returns a copy of the instance with the time reset 

754 with the following rules: 

755 

756 * second: microsecond set to 0 

757 * minute: second and microsecond set to 0 

758 * hour: minute, second and microsecond set to 0 

759 * day: time to 00:00:00 

760 * week: date to first day of the week and time to 00:00:00 

761 * month: date to first day of the month and time to 00:00:00 

762 * year: date to first day of the year and time to 00:00:00 

763 * decade: date to first day of the decade and time to 00:00:00 

764 * century: date to first day of century and time to 00:00:00 

765 """ 

766 if unit not in self._MODIFIERS_VALID_UNITS: 

767 raise ValueError(f'Invalid unit "{unit}" for start_of()') 

768 

769 return cast("Self", getattr(self, f"_start_of_{unit}")()) 

770 

771 def end_of(self, unit: str) -> Self: 

772 """ 

773 Returns a copy of the instance with the time reset 

774 with the following rules: 

775 

776 * second: microsecond set to 999999 

777 * minute: second set to 59 and microsecond set to 999999 

778 * hour: minute and second set to 59 and microsecond set to 999999 

779 * day: time to 23:59:59.999999 

780 * week: date to last day of the week and time to 23:59:59.999999 

781 * month: date to last day of the month and time to 23:59:59.999999 

782 * year: date to last day of the year and time to 23:59:59.999999 

783 * decade: date to last day of the decade and time to 23:59:59.999999 

784 * century: date to last day of century and time to 23:59:59.999999 

785 """ 

786 if unit not in self._MODIFIERS_VALID_UNITS: 

787 raise ValueError(f'Invalid unit "{unit}" for end_of()') 

788 

789 return cast("Self", getattr(self, f"_end_of_{unit}")()) 

790 

791 def _start_of_second(self) -> Self: 

792 """ 

793 Reset microseconds to 0. 

794 """ 

795 return self.set(microsecond=0) 

796 

797 def _end_of_second(self) -> Self: 

798 """ 

799 Set microseconds to 999999. 

800 """ 

801 return self.set(microsecond=999999) 

802 

803 def _start_of_minute(self) -> Self: 

804 """ 

805 Reset seconds and microseconds to 0. 

806 """ 

807 return self.set(second=0, microsecond=0) 

808 

809 def _end_of_minute(self) -> Self: 

810 """ 

811 Set seconds to 59 and microseconds to 999999. 

812 """ 

813 return self.set(second=59, microsecond=999999) 

814 

815 def _start_of_hour(self) -> Self: 

816 """ 

817 Reset minutes, seconds and microseconds to 0. 

818 """ 

819 return self.set(minute=0, second=0, microsecond=0) 

820 

821 def _end_of_hour(self) -> Self: 

822 """ 

823 Set minutes and seconds to 59 and microseconds to 999999. 

824 """ 

825 return self.set(minute=59, second=59, microsecond=999999) 

826 

827 def _start_of_day(self) -> Self: 

828 """ 

829 Reset the time to 00:00:00. 

830 """ 

831 return self.at(0, 0, 0, 0) 

832 

833 def _end_of_day(self) -> Self: 

834 """ 

835 Reset the time to 23:59:59.999999. 

836 """ 

837 return self.at(23, 59, 59, 999999) 

838 

839 def _start_of_month(self) -> Self: 

840 """ 

841 Reset the date to the first day of the month and the time to 00:00:00. 

842 """ 

843 return self.set(self.year, self.month, 1, 0, 0, 0, 0) 

844 

845 def _end_of_month(self) -> Self: 

846 """ 

847 Reset the date to the last day of the month 

848 and the time to 23:59:59.999999. 

849 """ 

850 return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999) 

851 

852 def _start_of_year(self) -> Self: 

853 """ 

854 Reset the date to the first day of the year and the time to 00:00:00. 

855 """ 

856 return self.set(self.year, 1, 1, 0, 0, 0, 0) 

857 

858 def _end_of_year(self) -> Self: 

859 """ 

860 Reset the date to the last day of the year 

861 and the time to 23:59:59.999999. 

862 """ 

863 return self.set(self.year, 12, 31, 23, 59, 59, 999999) 

864 

865 def _start_of_decade(self) -> Self: 

866 """ 

867 Reset the date to the first day of the decade 

868 and the time to 00:00:00. 

869 """ 

870 year = self.year - self.year % YEARS_PER_DECADE 

871 return self.set(year, 1, 1, 0, 0, 0, 0) 

872 

873 def _end_of_decade(self) -> Self: 

874 """ 

875 Reset the date to the last day of the decade 

876 and the time to 23:59:59.999999. 

877 """ 

878 year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1 

879 

880 return self.set(year, 12, 31, 23, 59, 59, 999999) 

881 

882 def _start_of_century(self) -> Self: 

883 """ 

884 Reset the date to the first day of the century 

885 and the time to 00:00:00. 

886 """ 

887 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1 

888 

889 return self.set(year, 1, 1, 0, 0, 0, 0) 

890 

891 def _end_of_century(self) -> Self: 

892 """ 

893 Reset the date to the last day of the century 

894 and the time to 23:59:59.999999. 

895 """ 

896 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY 

897 

898 return self.set(year, 12, 31, 23, 59, 59, 999999) 

899 

900 def _start_of_week(self) -> Self: 

901 """ 

902 Reset the date to the first day of the week 

903 and the time to 00:00:00. 

904 """ 

905 dt = self 

906 

907 if self.day_of_week != pendulum._WEEK_STARTS_AT: 

908 dt = self.previous(pendulum._WEEK_STARTS_AT) 

909 

910 return dt.start_of("day") 

911 

912 def _end_of_week(self) -> Self: 

913 """ 

914 Reset the date to the last day of the week 

915 and the time to 23:59:59. 

916 """ 

917 dt = self 

918 

919 if self.day_of_week != pendulum._WEEK_ENDS_AT: 

920 dt = self.next(pendulum._WEEK_ENDS_AT) 

921 

922 return dt.end_of("day") 

923 

924 def next(self, day_of_week: WeekDay | None = None, keep_time: bool = False) -> Self: 

925 """ 

926 Modify to the next occurrence of a given day of the week. 

927 If no day_of_week is provided, modify to the next occurrence 

928 of the current day of the week. Use the supplied consts 

929 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

930 """ 

931 if day_of_week is None: 

932 day_of_week = self.day_of_week 

933 

934 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY: 

935 raise ValueError("Invalid day of week") 

936 

937 dt = self if keep_time else self.start_of("day") 

938 

939 dt = dt.add(days=1) 

940 while dt.day_of_week != day_of_week: 

941 dt = dt.add(days=1) 

942 

943 return dt 

944 

945 def previous( 

946 self, day_of_week: WeekDay | None = None, keep_time: bool = False 

947 ) -> Self: 

948 """ 

949 Modify to the previous occurrence of a given day of the week. 

950 If no day_of_week is provided, modify to the previous occurrence 

951 of the current day of the week. Use the supplied consts 

952 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

953 """ 

954 if day_of_week is None: 

955 day_of_week = self.day_of_week 

956 

957 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY: 

958 raise ValueError("Invalid day of week") 

959 

960 dt = self if keep_time else self.start_of("day") 

961 

962 dt = dt.subtract(days=1) 

963 while dt.day_of_week != day_of_week: 

964 dt = dt.subtract(days=1) 

965 

966 return dt 

967 

968 def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self: 

969 """ 

970 Returns an instance set to the first occurrence 

971 of a given day of the week in the current unit. 

972 If no day_of_week is provided, modify to the first day of the unit. 

973 Use the supplied consts to indicate the desired day_of_week, 

974 ex. DateTime.MONDAY. 

975 

976 Supported units are month, quarter and year. 

977 """ 

978 if unit not in ["month", "quarter", "year"]: 

979 raise ValueError(f'Invalid unit "{unit}" for first_of()') 

980 

981 return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week)) 

982 

983 def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self: 

984 """ 

985 Returns an instance set to the last occurrence 

986 of a given day of the week in the current unit. 

987 If no day_of_week is provided, modify to the last day of the unit. 

988 Use the supplied consts to indicate the desired day_of_week, 

989 ex. DateTime.MONDAY. 

990 

991 Supported units are month, quarter and year. 

992 """ 

993 if unit not in ["month", "quarter", "year"]: 

994 raise ValueError(f'Invalid unit "{unit}" for first_of()') 

995 

996 return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week)) 

997 

998 def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self: 

999 """ 

1000 Returns a new instance set to the given occurrence 

1001 of a given day of the week in the current unit. 

1002 If the calculated occurrence is outside the scope of the current unit, 

1003 then raise an error. Use the supplied consts 

1004 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1005 

1006 Supported units are month, quarter and year. 

1007 """ 

1008 if unit not in ["month", "quarter", "year"]: 

1009 raise ValueError(f'Invalid unit "{unit}" for first_of()') 

1010 

1011 dt = cast(Optional["Self"], getattr(self, f"_nth_of_{unit}")(nth, day_of_week)) 

1012 if not dt: 

1013 raise PendulumException( 

1014 f"Unable to find occurrence {nth}" 

1015 f" of {WeekDay(day_of_week).name.capitalize()} in {unit}" 

1016 ) 

1017 

1018 return dt 

1019 

1020 def _first_of_month(self, day_of_week: WeekDay | None = None) -> Self: 

1021 """ 

1022 Modify to the first occurrence of a given day of the week 

1023 in the current month. If no day_of_week is provided, 

1024 modify to the first day of the month. Use the supplied consts 

1025 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1026 """ 

1027 dt = self.start_of("day") 

1028 

1029 if day_of_week is None: 

1030 return dt.set(day=1) 

1031 

1032 month = calendar.monthcalendar(dt.year, dt.month) 

1033 

1034 calendar_day = day_of_week 

1035 

1036 if month[0][calendar_day] > 0: 

1037 day_of_month = month[0][calendar_day] 

1038 else: 

1039 day_of_month = month[1][calendar_day] 

1040 

1041 return dt.set(day=day_of_month) 

1042 

1043 def _last_of_month(self, day_of_week: WeekDay | None = None) -> Self: 

1044 """ 

1045 Modify to the last occurrence of a given day of the week 

1046 in the current month. If no day_of_week is provided, 

1047 modify to the last day of the month. Use the supplied consts 

1048 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1049 """ 

1050 dt = self.start_of("day") 

1051 

1052 if day_of_week is None: 

1053 return dt.set(day=self.days_in_month) 

1054 

1055 month = calendar.monthcalendar(dt.year, dt.month) 

1056 

1057 calendar_day = day_of_week 

1058 

1059 if month[-1][calendar_day] > 0: 

1060 day_of_month = month[-1][calendar_day] 

1061 else: 

1062 day_of_month = month[-2][calendar_day] 

1063 

1064 return dt.set(day=day_of_month) 

1065 

1066 def _nth_of_month( 

1067 self, nth: int, day_of_week: WeekDay | None = None 

1068 ) -> Self | None: 

1069 """ 

1070 Modify to the given occurrence of a given day of the week 

1071 in the current month. If the calculated occurrence is outside, 

1072 the scope of the current month, then return False and no 

1073 modifications are made. Use the supplied consts 

1074 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1075 """ 

1076 if nth == 1: 

1077 return self.first_of("month", day_of_week) 

1078 

1079 dt = self.first_of("month") 

1080 check = dt.format("%Y-%M") 

1081 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)): 

1082 dt = dt.next(day_of_week) 

1083 

1084 if dt.format("%Y-%M") == check: 

1085 return self.set(day=dt.day).start_of("day") 

1086 

1087 return None 

1088 

1089 def _first_of_quarter(self, day_of_week: WeekDay | None = None) -> Self: 

1090 """ 

1091 Modify to the first occurrence of a given day of the week 

1092 in the current quarter. If no day_of_week is provided, 

1093 modify to the first day of the quarter. Use the supplied consts 

1094 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1095 """ 

1096 return self.on(self.year, self.quarter * 3 - 2, 1).first_of( 

1097 "month", day_of_week 

1098 ) 

1099 

1100 def _last_of_quarter(self, day_of_week: WeekDay | None = None) -> Self: 

1101 """ 

1102 Modify to the last occurrence of a given day of the week 

1103 in the current quarter. If no day_of_week is provided, 

1104 modify to the last day of the quarter. Use the supplied consts 

1105 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1106 """ 

1107 return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week) 

1108 

1109 def _nth_of_quarter( 

1110 self, nth: int, day_of_week: WeekDay | None = None 

1111 ) -> Self | None: 

1112 """ 

1113 Modify to the given occurrence of a given day of the week 

1114 in the current quarter. If the calculated occurrence is outside, 

1115 the scope of the current quarter, then return False and no 

1116 modifications are made. Use the supplied consts 

1117 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1118 """ 

1119 if nth == 1: 

1120 return self.first_of("quarter", day_of_week) 

1121 

1122 dt = self.set(day=1, month=self.quarter * 3) 

1123 last_month = dt.month 

1124 year = dt.year 

1125 dt = dt.first_of("quarter") 

1126 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)): 

1127 dt = dt.next(day_of_week) 

1128 

1129 if last_month < dt.month or year != dt.year: 

1130 return None 

1131 

1132 return self.on(self.year, dt.month, dt.day).start_of("day") 

1133 

1134 def _first_of_year(self, day_of_week: WeekDay | None = None) -> Self: 

1135 """ 

1136 Modify to the first occurrence of a given day of the week 

1137 in the current year. If no day_of_week is provided, 

1138 modify to the first day of the year. Use the supplied consts 

1139 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1140 """ 

1141 return self.set(month=1).first_of("month", day_of_week) 

1142 

1143 def _last_of_year(self, day_of_week: WeekDay | None = None) -> Self: 

1144 """ 

1145 Modify to the last occurrence of a given day of the week 

1146 in the current year. If no day_of_week is provided, 

1147 modify to the last day of the year. Use the supplied consts 

1148 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1149 """ 

1150 return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week) 

1151 

1152 def _nth_of_year(self, nth: int, day_of_week: WeekDay | None = None) -> Self | None: 

1153 """ 

1154 Modify to the given occurrence of a given day of the week 

1155 in the current year. If the calculated occurrence is outside, 

1156 the scope of the current year, then return False and no 

1157 modifications are made. Use the supplied consts 

1158 to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1159 """ 

1160 if nth == 1: 

1161 return self.first_of("year", day_of_week) 

1162 

1163 dt = self.first_of("year") 

1164 year = dt.year 

1165 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)): 

1166 dt = dt.next(day_of_week) 

1167 

1168 if year != dt.year: 

1169 return None 

1170 

1171 return self.on(self.year, dt.month, dt.day).start_of("day") 

1172 

1173 def average( # type: ignore[override] 

1174 self, dt: datetime.datetime | None = None 

1175 ) -> Self: 

1176 """ 

1177 Modify the current instance to the average 

1178 of a given instance (default now) and the current instance. 

1179 """ 

1180 if dt is None: 

1181 dt = self.now(self.tz) 

1182 

1183 diff = self.diff(dt, False) 

1184 return self.add( 

1185 microseconds=(diff.in_seconds() * 1000000 + diff.microseconds) // 2 

1186 ) 

1187 

1188 @overload # type: ignore[override] 

1189 def __sub__(self, other: datetime.timedelta) -> Self: 

1190 ... 

1191 

1192 @overload 

1193 def __sub__(self, other: DateTime) -> Interval: 

1194 ... 

1195 

1196 def __sub__(self, other: datetime.datetime | datetime.timedelta) -> Self | Interval: 

1197 if isinstance(other, datetime.timedelta): 

1198 return self._subtract_timedelta(other) 

1199 

1200 if not isinstance(other, datetime.datetime): 

1201 return NotImplemented 

1202 

1203 if not isinstance(other, self.__class__): 

1204 if other.tzinfo is None: 

1205 other = pendulum.naive( 

1206 other.year, 

1207 other.month, 

1208 other.day, 

1209 other.hour, 

1210 other.minute, 

1211 other.second, 

1212 other.microsecond, 

1213 ) 

1214 else: 

1215 other = self.instance(other) 

1216 

1217 return other.diff(self, False) 

1218 

1219 def __rsub__(self, other: datetime.datetime) -> Interval: 

1220 if not isinstance(other, datetime.datetime): 

1221 return NotImplemented 

1222 

1223 if not isinstance(other, self.__class__): 

1224 if other.tzinfo is None: 

1225 other = pendulum.naive( 

1226 other.year, 

1227 other.month, 

1228 other.day, 

1229 other.hour, 

1230 other.minute, 

1231 other.second, 

1232 other.microsecond, 

1233 ) 

1234 else: 

1235 other = self.instance(other) 

1236 

1237 return self.diff(other, False) 

1238 

1239 def __add__(self, other: datetime.timedelta) -> Self: 

1240 if not isinstance(other, datetime.timedelta): 

1241 return NotImplemented 

1242 

1243 caller = traceback.extract_stack(limit=2)[0].name 

1244 if caller == "astimezone": 

1245 return super().__add__(other) 

1246 

1247 return self._add_timedelta_(other) 

1248 

1249 def __radd__(self, other: datetime.timedelta) -> Self: 

1250 return self.__add__(other) 

1251 

1252 # Native methods override 

1253 

1254 @classmethod 

1255 def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> Self: 

1256 tzinfo = pendulum._safe_timezone(tz) 

1257 

1258 return cls.instance(datetime.datetime.fromtimestamp(t, tz=tzinfo), tz=tzinfo) 

1259 

1260 @classmethod 

1261 def utcfromtimestamp(cls, t: float) -> Self: 

1262 return cls.instance(datetime.datetime.utcfromtimestamp(t), tz=None) 

1263 

1264 @classmethod 

1265 def fromordinal(cls, n: int) -> Self: 

1266 return cls.instance(datetime.datetime.fromordinal(n), tz=None) 

1267 

1268 @classmethod 

1269 def combine( 

1270 cls, 

1271 date: datetime.date, 

1272 time: datetime.time, 

1273 tzinfo: datetime.tzinfo | None = None, 

1274 ) -> Self: 

1275 return cls.instance(datetime.datetime.combine(date, time), tz=tzinfo) 

1276 

1277 def astimezone(self, tz: datetime.tzinfo | None = None) -> Self: 

1278 dt = super().astimezone(tz) 

1279 

1280 return self.__class__( 

1281 dt.year, 

1282 dt.month, 

1283 dt.day, 

1284 dt.hour, 

1285 dt.minute, 

1286 dt.second, 

1287 dt.microsecond, 

1288 fold=dt.fold, 

1289 tzinfo=dt.tzinfo, 

1290 ) 

1291 

1292 def replace( 

1293 self, 

1294 year: SupportsIndex | None = None, 

1295 month: SupportsIndex | None = None, 

1296 day: SupportsIndex | None = None, 

1297 hour: SupportsIndex | None = None, 

1298 minute: SupportsIndex | None = None, 

1299 second: SupportsIndex | None = None, 

1300 microsecond: SupportsIndex | None = None, 

1301 tzinfo: bool | datetime.tzinfo | Literal[True] | None = True, 

1302 fold: int | None = None, 

1303 ) -> Self: 

1304 if year is None: 

1305 year = self.year 

1306 if month is None: 

1307 month = self.month 

1308 if day is None: 

1309 day = self.day 

1310 if hour is None: 

1311 hour = self.hour 

1312 if minute is None: 

1313 minute = self.minute 

1314 if second is None: 

1315 second = self.second 

1316 if microsecond is None: 

1317 microsecond = self.microsecond 

1318 if tzinfo is True: 

1319 tzinfo = self.tzinfo 

1320 if fold is None: 

1321 fold = self.fold 

1322 

1323 if tzinfo is not None: 

1324 tzinfo = pendulum._safe_timezone(tzinfo) 

1325 

1326 return self.__class__.create( 

1327 year, 

1328 month, 

1329 day, 

1330 hour, 

1331 minute, 

1332 second, 

1333 microsecond, 

1334 tz=tzinfo, 

1335 fold=fold, 

1336 ) 

1337 

1338 def __getnewargs__(self) -> tuple[Self]: 

1339 return (self,) 

1340 

1341 def _getstate( 

1342 self, protocol: SupportsIndex = 3 

1343 ) -> tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]: 

1344 return ( 

1345 self.year, 

1346 self.month, 

1347 self.day, 

1348 self.hour, 

1349 self.minute, 

1350 self.second, 

1351 self.microsecond, 

1352 self.tzinfo, 

1353 ) 

1354 

1355 def __reduce__( 

1356 self, 

1357 ) -> tuple[ 

1358 type[Self], 

1359 tuple[int, int, int, int, int, int, int, datetime.tzinfo | None], 

1360 ]: 

1361 return self.__reduce_ex__(2) 

1362 

1363 def __reduce_ex__( 

1364 self, protocol: SupportsIndex 

1365 ) -> tuple[ 

1366 type[Self], 

1367 tuple[int, int, int, int, int, int, int, datetime.tzinfo | None], 

1368 ]: 

1369 return self.__class__, self._getstate(protocol) 

1370 

1371 def __deepcopy__(self, _: dict[int, Self]) -> Self: 

1372 return self.__class__( 

1373 self.year, 

1374 self.month, 

1375 self.day, 

1376 self.hour, 

1377 self.minute, 

1378 self.second, 

1379 self.microsecond, 

1380 tzinfo=self.tz, 

1381 fold=self.fold, 

1382 ) 

1383 

1384 def _cmp(self, other: datetime.datetime, **kwargs: Any) -> int: 

1385 # Fix for pypy which compares using this method 

1386 # which would lead to infinite recursion if we didn't override 

1387 dt = datetime.datetime( 

1388 self.year, 

1389 self.month, 

1390 self.day, 

1391 self.hour, 

1392 self.minute, 

1393 self.second, 

1394 self.microsecond, 

1395 tzinfo=self.tz, 

1396 fold=self.fold, 

1397 ) 

1398 

1399 return 0 if dt == other else 1 if dt > other else -1 

1400 

1401 

1402DateTime.min = DateTime(1, 1, 1, 0, 0, tzinfo=UTC) 

1403DateTime.max = DateTime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC) 

1404DateTime.EPOCH = DateTime(1970, 1, 1, tzinfo=UTC)