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

513 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-30 06:11 +0000

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( 

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

690 ) 

691 

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

693 

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

695 """ 

696 Remove timedelta duration from the instance. 

697 """ 

698 if isinstance(delta, pendulum.Duration): 

699 return self.subtract( 

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

701 ) 

702 

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

704 

705 # DIFFERENCES 

706 

707 def diff( # type: ignore[override] 

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

709 ) -> Interval: 

710 """ 

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

712 """ 

713 if dt is None: 

714 dt = self.now(self.tz) 

715 

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

717 

718 def diff_for_humans( # type: ignore[override] 

719 self, 

720 other: DateTime | None = None, 

721 absolute: bool = False, 

722 locale: str | None = None, 

723 ) -> str: 

724 """ 

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

726 

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

728 1 day ago 

729 5 months ago 

730 

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

732 1 day from now 

733 5 months from now 

734 

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

736 1 day before 

737 5 months before 

738 

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

740 1 day after 

741 5 months after 

742 """ 

743 is_now = other is None 

744 

745 if is_now: 

746 other = self.now() 

747 

748 diff = self.diff(other) 

749 

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

751 

752 # Modifiers 

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

754 """ 

755 Returns a copy of the instance with the time reset 

756 with the following rules: 

757 

758 * second: microsecond set to 0 

759 * minute: second and microsecond set to 0 

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

761 * day: time to 00:00:00 

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

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

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

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

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

767 """ 

768 if unit not in self._MODIFIERS_VALID_UNITS: 

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

770 

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

772 

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

774 """ 

775 Returns a copy of the instance with the time reset 

776 with the following rules: 

777 

778 * second: microsecond set to 999999 

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

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

781 * day: time to 23:59:59.999999 

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

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

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

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

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

787 """ 

788 if unit not in self._MODIFIERS_VALID_UNITS: 

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

790 

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

792 

793 def _start_of_second(self) -> Self: 

794 """ 

795 Reset microseconds to 0. 

796 """ 

797 return self.set(microsecond=0) 

798 

799 def _end_of_second(self) -> Self: 

800 """ 

801 Set microseconds to 999999. 

802 """ 

803 return self.set(microsecond=999999) 

804 

805 def _start_of_minute(self) -> Self: 

806 """ 

807 Reset seconds and microseconds to 0. 

808 """ 

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

810 

811 def _end_of_minute(self) -> Self: 

812 """ 

813 Set seconds to 59 and microseconds to 999999. 

814 """ 

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

816 

817 def _start_of_hour(self) -> Self: 

818 """ 

819 Reset minutes, seconds and microseconds to 0. 

820 """ 

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

822 

823 def _end_of_hour(self) -> Self: 

824 """ 

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

826 """ 

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

828 

829 def _start_of_day(self) -> Self: 

830 """ 

831 Reset the time to 00:00:00. 

832 """ 

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

834 

835 def _end_of_day(self) -> Self: 

836 """ 

837 Reset the time to 23:59:59.999999. 

838 """ 

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

840 

841 def _start_of_month(self) -> Self: 

842 """ 

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

844 """ 

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

846 

847 def _end_of_month(self) -> Self: 

848 """ 

849 Reset the date to the last day of the month 

850 and the time to 23:59:59.999999. 

851 """ 

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

853 

854 def _start_of_year(self) -> Self: 

855 """ 

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

857 """ 

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

859 

860 def _end_of_year(self) -> Self: 

861 """ 

862 Reset the date to the last day of the year 

863 and the time to 23:59:59.999999. 

864 """ 

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

866 

867 def _start_of_decade(self) -> Self: 

868 """ 

869 Reset the date to the first day of the decade 

870 and the time to 00:00:00. 

871 """ 

872 year = self.year - self.year % YEARS_PER_DECADE 

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

874 

875 def _end_of_decade(self) -> Self: 

876 """ 

877 Reset the date to the last day of the decade 

878 and the time to 23:59:59.999999. 

879 """ 

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

881 

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

883 

884 def _start_of_century(self) -> Self: 

885 """ 

886 Reset the date to the first day of the century 

887 and the time to 00:00:00. 

888 """ 

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

890 

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

892 

893 def _end_of_century(self) -> Self: 

894 """ 

895 Reset the date to the last day of the century 

896 and the time to 23:59:59.999999. 

897 """ 

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

899 

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

901 

902 def _start_of_week(self) -> Self: 

903 """ 

904 Reset the date to the first day of the week 

905 and the time to 00:00:00. 

906 """ 

907 dt = self 

908 

909 if self.day_of_week != pendulum._WEEK_STARTS_AT: 

910 dt = self.previous(pendulum._WEEK_STARTS_AT) 

911 

912 return dt.start_of("day") 

913 

914 def _end_of_week(self) -> Self: 

915 """ 

916 Reset the date to the last day of the week 

917 and the time to 23:59:59. 

918 """ 

919 dt = self 

920 

921 if self.day_of_week != pendulum._WEEK_ENDS_AT: 

922 dt = self.next(pendulum._WEEK_ENDS_AT) 

923 

924 return dt.end_of("day") 

925 

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

927 """ 

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

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

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

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

932 """ 

933 if day_of_week is None: 

934 day_of_week = self.day_of_week 

935 

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

937 raise ValueError("Invalid day of week") 

938 

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

940 

941 dt = dt.add(days=1) 

942 while dt.day_of_week != day_of_week: 

943 dt = dt.add(days=1) 

944 

945 return dt 

946 

947 def previous( 

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

949 ) -> Self: 

950 """ 

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

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

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

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

955 """ 

956 if day_of_week is None: 

957 day_of_week = self.day_of_week 

958 

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

960 raise ValueError("Invalid day of week") 

961 

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

963 

964 dt = dt.subtract(days=1) 

965 while dt.day_of_week != day_of_week: 

966 dt = dt.subtract(days=1) 

967 

968 return dt 

969 

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

971 """ 

972 Returns an instance set to the first occurrence 

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

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

975 Use the supplied consts to indicate the desired day_of_week, 

976 ex. DateTime.MONDAY. 

977 

978 Supported units are month, quarter and year. 

979 """ 

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

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

982 

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

984 

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

986 """ 

987 Returns an instance set to the last occurrence 

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

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

990 Use the supplied consts to indicate the desired day_of_week, 

991 ex. DateTime.MONDAY. 

992 

993 Supported units are month, quarter and year. 

994 """ 

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

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

997 

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

999 

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

1001 """ 

1002 Returns a new instance set to the given occurrence 

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

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

1005 then raise an error. Use the supplied consts 

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

1007 

1008 Supported units are month, quarter and year. 

1009 """ 

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

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

1012 

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

1014 if not dt: 

1015 raise PendulumException( 

1016 f"Unable to find occurrence {nth}" 

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

1018 ) 

1019 

1020 return dt 

1021 

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

1023 """ 

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

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

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

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

1028 """ 

1029 dt = self.start_of("day") 

1030 

1031 if day_of_week is None: 

1032 return dt.set(day=1) 

1033 

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

1035 

1036 calendar_day = day_of_week 

1037 

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

1039 day_of_month = month[0][calendar_day] 

1040 else: 

1041 day_of_month = month[1][calendar_day] 

1042 

1043 return dt.set(day=day_of_month) 

1044 

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

1046 """ 

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

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

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

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

1051 """ 

1052 dt = self.start_of("day") 

1053 

1054 if day_of_week is None: 

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

1056 

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

1058 

1059 calendar_day = day_of_week 

1060 

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

1062 day_of_month = month[-1][calendar_day] 

1063 else: 

1064 day_of_month = month[-2][calendar_day] 

1065 

1066 return dt.set(day=day_of_month) 

1067 

1068 def _nth_of_month( 

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

1070 ) -> Self | None: 

1071 """ 

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

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

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

1075 modifications are made. Use the supplied consts 

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

1077 """ 

1078 if nth == 1: 

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

1080 

1081 dt = self.first_of("month") 

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

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

1084 dt = dt.next(day_of_week) 

1085 

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

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

1088 

1089 return None 

1090 

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

1092 """ 

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

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

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

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

1097 """ 

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

1099 "month", day_of_week 

1100 ) 

1101 

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

1103 """ 

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

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

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

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

1108 """ 

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

1110 

1111 def _nth_of_quarter( 

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

1113 ) -> Self | None: 

1114 """ 

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

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

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

1118 modifications are made. Use the supplied consts 

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

1120 """ 

1121 if nth == 1: 

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

1123 

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

1125 last_month = dt.month 

1126 year = dt.year 

1127 dt = dt.first_of("quarter") 

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

1129 dt = dt.next(day_of_week) 

1130 

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

1132 return None 

1133 

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

1135 

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

1137 """ 

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

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

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

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

1142 """ 

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

1144 

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

1146 """ 

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

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

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

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

1151 """ 

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

1153 

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

1155 """ 

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

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

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

1159 modifications are made. Use the supplied consts 

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

1161 """ 

1162 if nth == 1: 

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

1164 

1165 dt = self.first_of("year") 

1166 year = dt.year 

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

1168 dt = dt.next(day_of_week) 

1169 

1170 if year != dt.year: 

1171 return None 

1172 

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

1174 

1175 def average( # type: ignore[override] 

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

1177 ) -> Self: 

1178 """ 

1179 Modify the current instance to the average 

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

1181 """ 

1182 if dt is None: 

1183 dt = self.now(self.tz) 

1184 

1185 diff = self.diff(dt, False) 

1186 return self.add( 

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

1188 ) 

1189 

1190 @overload # type: ignore[override] 

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

1192 ... 

1193 

1194 @overload 

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

1196 ... 

1197 

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

1199 if isinstance(other, datetime.timedelta): 

1200 return self._subtract_timedelta(other) 

1201 

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

1203 return NotImplemented 

1204 

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

1206 if other.tzinfo is None: 

1207 other = pendulum.naive( 

1208 other.year, 

1209 other.month, 

1210 other.day, 

1211 other.hour, 

1212 other.minute, 

1213 other.second, 

1214 other.microsecond, 

1215 ) 

1216 else: 

1217 other = self.instance(other) 

1218 

1219 return other.diff(self, False) 

1220 

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

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

1223 return NotImplemented 

1224 

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

1226 if other.tzinfo is None: 

1227 other = pendulum.naive( 

1228 other.year, 

1229 other.month, 

1230 other.day, 

1231 other.hour, 

1232 other.minute, 

1233 other.second, 

1234 other.microsecond, 

1235 ) 

1236 else: 

1237 other = self.instance(other) 

1238 

1239 return self.diff(other, False) 

1240 

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

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

1243 return NotImplemented 

1244 

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

1246 if caller == "astimezone": 

1247 return super().__add__(other) 

1248 

1249 return self._add_timedelta_(other) 

1250 

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

1252 return self.__add__(other) 

1253 

1254 # Native methods override 

1255 

1256 @classmethod 

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

1258 tzinfo = pendulum._safe_timezone(tz) 

1259 

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

1261 

1262 @classmethod 

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

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

1265 

1266 @classmethod 

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

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

1269 

1270 @classmethod 

1271 def combine( 

1272 cls, 

1273 date: datetime.date, 

1274 time: datetime.time, 

1275 tzinfo: datetime.tzinfo | None = None, 

1276 ) -> Self: 

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

1278 

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

1280 dt = super().astimezone(tz) 

1281 

1282 return self.__class__( 

1283 dt.year, 

1284 dt.month, 

1285 dt.day, 

1286 dt.hour, 

1287 dt.minute, 

1288 dt.second, 

1289 dt.microsecond, 

1290 fold=dt.fold, 

1291 tzinfo=dt.tzinfo, 

1292 ) 

1293 

1294 def replace( 

1295 self, 

1296 year: SupportsIndex | None = None, 

1297 month: SupportsIndex | None = None, 

1298 day: SupportsIndex | None = None, 

1299 hour: SupportsIndex | None = None, 

1300 minute: SupportsIndex | None = None, 

1301 second: SupportsIndex | None = None, 

1302 microsecond: SupportsIndex | None = None, 

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

1304 fold: int | None = None, 

1305 ) -> Self: 

1306 if year is None: 

1307 year = self.year 

1308 if month is None: 

1309 month = self.month 

1310 if day is None: 

1311 day = self.day 

1312 if hour is None: 

1313 hour = self.hour 

1314 if minute is None: 

1315 minute = self.minute 

1316 if second is None: 

1317 second = self.second 

1318 if microsecond is None: 

1319 microsecond = self.microsecond 

1320 if tzinfo is True: 

1321 tzinfo = self.tzinfo 

1322 if fold is None: 

1323 fold = self.fold 

1324 

1325 if tzinfo is not None: 

1326 tzinfo = pendulum._safe_timezone(tzinfo) 

1327 

1328 return self.__class__.create( 

1329 year, 

1330 month, 

1331 day, 

1332 hour, 

1333 minute, 

1334 second, 

1335 microsecond, 

1336 tz=tzinfo, 

1337 fold=fold, 

1338 ) 

1339 

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

1341 return (self,) 

1342 

1343 def _getstate( 

1344 self, protocol: SupportsIndex = 3 

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

1346 return ( 

1347 self.year, 

1348 self.month, 

1349 self.day, 

1350 self.hour, 

1351 self.minute, 

1352 self.second, 

1353 self.microsecond, 

1354 self.tzinfo, 

1355 ) 

1356 

1357 def __reduce__( 

1358 self, 

1359 ) -> tuple[ 

1360 type[Self], tuple[int, int, int, int, int, int, int, datetime.tzinfo | None] 

1361 ]: 

1362 return self.__reduce_ex__(2) 

1363 

1364 def __reduce_ex__( 

1365 self, protocol: SupportsIndex 

1366 ) -> tuple[ 

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

1368 ]: 

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

1370 

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

1372 # Fix for pypy which compares using this method 

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

1374 dt = datetime.datetime( 

1375 self.year, 

1376 self.month, 

1377 self.day, 

1378 self.hour, 

1379 self.minute, 

1380 self.second, 

1381 self.microsecond, 

1382 tzinfo=self.tz, 

1383 fold=self.fold, 

1384 ) 

1385 

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

1387 

1388 

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

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

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