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

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

511 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 @overload 

152 @classmethod 

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

154 

155 @classmethod 

156 def now( 

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

158 ) -> Self: 

159 """ 

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

161 """ 

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

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

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

165 dt = datetime.datetime.now(UTC) 

166 else: 

167 dt = datetime.datetime.now(UTC) 

168 tz = pendulum._safe_timezone(tz) 

169 dt = dt.astimezone(tz) 

170 

171 return cls( 

172 dt.year, 

173 dt.month, 

174 dt.day, 

175 dt.hour, 

176 dt.minute, 

177 dt.second, 

178 dt.microsecond, 

179 tzinfo=dt.tzinfo, 

180 fold=dt.fold, 

181 ) 

182 

183 @classmethod 

184 def utcnow(cls) -> Self: 

185 """ 

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

187 """ 

188 return cls.now(UTC) 

189 

190 @classmethod 

191 def today(cls) -> Self: 

192 return cls.now() 

193 

194 @classmethod 

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

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

197 

198 # Getters/Setters 

199 

200 def set( 

201 self, 

202 year: int | None = None, 

203 month: int | None = None, 

204 day: int | None = None, 

205 hour: int | None = None, 

206 minute: int | None = None, 

207 second: int | None = None, 

208 microsecond: int | None = None, 

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

210 ) -> Self: 

211 if year is None: 

212 year = self.year 

213 if month is None: 

214 month = self.month 

215 if day is None: 

216 day = self.day 

217 if hour is None: 

218 hour = self.hour 

219 if minute is None: 

220 minute = self.minute 

221 if second is None: 

222 second = self.second 

223 if microsecond is None: 

224 microsecond = self.microsecond 

225 if tz is None: 

226 tz = self.tz 

227 

228 return self.__class__.create( 

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

230 ) 

231 

232 @property 

233 def float_timestamp(self) -> float: 

234 return self.timestamp() 

235 

236 @property 

237 def int_timestamp(self) -> int: 

238 # Workaround needed to avoid inaccuracy 

239 # for far into the future datetimes 

240 dt = datetime.datetime( 

241 self.year, 

242 self.month, 

243 self.day, 

244 self.hour, 

245 self.minute, 

246 self.second, 

247 self.microsecond, 

248 tzinfo=self.tzinfo, 

249 fold=self.fold, 

250 ) 

251 

252 delta = dt - self._EPOCH 

253 

254 return delta.days * SECONDS_PER_DAY + delta.seconds 

255 

256 @property 

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

258 return self.get_offset() 

259 

260 @property 

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

262 offset = self.get_offset() 

263 

264 if offset is None: 

265 return None 

266 

267 return offset / SECONDS_PER_MINUTE / MINUTES_PER_HOUR 

268 

269 @property 

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

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

272 return None 

273 

274 return self.tzinfo 

275 

276 @property 

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

278 return self.timezone 

279 

280 @property 

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

282 tz = self.timezone 

283 

284 if tz is None: 

285 return None 

286 

287 return tz.name 

288 

289 @property 

290 def age(self) -> int: 

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

292 

293 def is_local(self) -> bool: 

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

295 

296 def is_utc(self) -> bool: 

297 return self.offset == 0 

298 

299 def is_dst(self) -> bool: 

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

301 

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

303 utcoffset = self.utcoffset() 

304 if utcoffset is None: 

305 return None 

306 

307 return int(utcoffset.total_seconds()) 

308 

309 def date(self) -> Date: 

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

311 

312 def time(self) -> Time: 

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

314 

315 def naive(self) -> Self: 

316 """ 

317 Return the DateTime without timezone information. 

318 """ 

319 return self.__class__( 

320 self.year, 

321 self.month, 

322 self.day, 

323 self.hour, 

324 self.minute, 

325 self.second, 

326 self.microsecond, 

327 ) 

328 

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

330 """ 

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

332 """ 

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

334 

335 def at( 

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

337 ) -> Self: 

338 """ 

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

340 """ 

341 return self.set( 

342 hour=hour, minute=minute, second=second, microsecond=microsecond 

343 ) 

344 

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

346 """ 

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

348 """ 

349 tz = pendulum._safe_timezone(tz) 

350 

351 dt = self 

352 if not self.timezone: 

353 dt = dt.replace(fold=1) 

354 

355 return tz.convert(dt) 

356 

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

358 """ 

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

360 """ 

361 return self.in_timezone(tz) 

362 

363 # STRING FORMATTING 

364 

365 def to_time_string(self) -> str: 

366 """ 

367 Format the instance as time. 

368 """ 

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

370 

371 def to_datetime_string(self) -> str: 

372 """ 

373 Format the instance as date and time. 

374 """ 

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

376 

377 def to_day_datetime_string(self) -> str: 

378 """ 

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

380 """ 

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

382 

383 def to_atom_string(self) -> str: 

384 """ 

385 Format the instance as ATOM. 

386 """ 

387 return self._to_string("atom") 

388 

389 def to_cookie_string(self) -> str: 

390 """ 

391 Format the instance as COOKIE. 

392 """ 

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

394 

395 def to_iso8601_string(self) -> str: 

396 """ 

397 Format the instance as ISO 8601. 

398 """ 

399 string = self._to_string("iso8601") 

400 

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

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

403 

404 return string 

405 

406 def to_rfc822_string(self) -> str: 

407 """ 

408 Format the instance as RFC 822. 

409 """ 

410 return self._to_string("rfc822") 

411 

412 def to_rfc850_string(self) -> str: 

413 """ 

414 Format the instance as RFC 850. 

415 """ 

416 return self._to_string("rfc850") 

417 

418 def to_rfc1036_string(self) -> str: 

419 """ 

420 Format the instance as RFC 1036. 

421 """ 

422 return self._to_string("rfc1036") 

423 

424 def to_rfc1123_string(self) -> str: 

425 """ 

426 Format the instance as RFC 1123. 

427 """ 

428 return self._to_string("rfc1123") 

429 

430 def to_rfc2822_string(self) -> str: 

431 """ 

432 Format the instance as RFC 2822. 

433 """ 

434 return self._to_string("rfc2822") 

435 

436 def to_rfc3339_string(self) -> str: 

437 """ 

438 Format the instance as RFC 3339. 

439 """ 

440 return self._to_string("rfc3339") 

441 

442 def to_rss_string(self) -> str: 

443 """ 

444 Format the instance as RSS. 

445 """ 

446 return self._to_string("rss") 

447 

448 def to_w3c_string(self) -> str: 

449 """ 

450 Format the instance as W3C. 

451 """ 

452 return self._to_string("w3c") 

453 

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

455 """ 

456 Format the instance to a common string format. 

457 """ 

458 if fmt not in self._FORMATS: 

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

460 

461 fmt_value = self._FORMATS[fmt] 

462 if callable(fmt_value): 

463 return fmt_value(self) 

464 

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

466 

467 def __str__(self) -> str: 

468 return self.isoformat(" ") 

469 

470 def __repr__(self) -> str: 

471 us = "" 

472 if self.microsecond: 

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

474 

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

476 

477 if self.tzinfo is not None: 

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

479 

480 repr_ += ")" 

481 

482 return repr_.format( 

483 klass=self.__class__.__name__, 

484 year=self.year, 

485 month=self.month, 

486 day=self.day, 

487 hour=self.hour, 

488 minute=self.minute, 

489 second=self.second, 

490 us=us, 

491 tzinfo=repr(self.tzinfo), 

492 ) 

493 

494 # Comparisons 

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

496 """ 

497 Get the closest date to the instance. 

498 """ 

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

500 

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

502 

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

504 """ 

505 Get the farthest date from the instance. 

506 """ 

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

508 

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

510 

511 def is_future(self) -> bool: 

512 """ 

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

514 """ 

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

516 

517 def is_past(self) -> bool: 

518 """ 

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

520 """ 

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

522 

523 def is_long_year(self) -> bool: 

524 """ 

525 Determines if the instance is a long year 

526 

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

528 """ 

529 return ( 

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

531 == 53 

532 ) 

533 

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

535 """ 

536 Checks if the passed in date is the same day 

537 as the instance current day. 

538 """ 

539 dt = self.instance(dt) 

540 

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

542 

543 def is_anniversary( # type: ignore[override] 

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

545 ) -> bool: 

546 """ 

547 Check if its the anniversary. 

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

549 """ 

550 if dt is None: 

551 dt = self.now(self.tz) 

552 

553 instance = self.instance(dt) 

554 

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

556 

557 # ADDITIONS AND SUBSTRACTIONS 

558 

559 def add( 

560 self, 

561 years: int = 0, 

562 months: int = 0, 

563 weeks: int = 0, 

564 days: int = 0, 

565 hours: int = 0, 

566 minutes: int = 0, 

567 seconds: float = 0, 

568 microseconds: int = 0, 

569 ) -> Self: 

570 """ 

571 Add a duration to the instance. 

572 

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

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

575 when moving across DST boundaries. 

576 """ 

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

578 

579 current_dt = datetime.datetime( 

580 self.year, 

581 self.month, 

582 self.day, 

583 self.hour, 

584 self.minute, 

585 self.second, 

586 self.microsecond, 

587 ) 

588 if not units_of_variable_length: 

589 offset = self.utcoffset() 

590 if offset: 

591 current_dt = current_dt - offset 

592 

593 dt = add_duration( 

594 current_dt, 

595 years=years, 

596 months=months, 

597 weeks=weeks, 

598 days=days, 

599 hours=hours, 

600 minutes=minutes, 

601 seconds=seconds, 

602 microseconds=microseconds, 

603 ) 

604 

605 if units_of_variable_length or self.tz is None: 

606 return self.__class__.create( 

607 dt.year, 

608 dt.month, 

609 dt.day, 

610 dt.hour, 

611 dt.minute, 

612 dt.second, 

613 dt.microsecond, 

614 tz=self.tz, 

615 ) 

616 

617 dt = datetime.datetime( 

618 dt.year, 

619 dt.month, 

620 dt.day, 

621 dt.hour, 

622 dt.minute, 

623 dt.second, 

624 dt.microsecond, 

625 tzinfo=UTC, 

626 ) 

627 

628 dt = self.tz.convert(dt) 

629 

630 return self.__class__( 

631 dt.year, 

632 dt.month, 

633 dt.day, 

634 dt.hour, 

635 dt.minute, 

636 dt.second, 

637 dt.microsecond, 

638 tzinfo=self.tz, 

639 fold=dt.fold, 

640 ) 

641 

642 def subtract( 

643 self, 

644 years: int = 0, 

645 months: int = 0, 

646 weeks: int = 0, 

647 days: int = 0, 

648 hours: int = 0, 

649 minutes: int = 0, 

650 seconds: float = 0, 

651 microseconds: int = 0, 

652 ) -> Self: 

653 """ 

654 Remove duration from the instance. 

655 """ 

656 return self.add( 

657 years=-years, 

658 months=-months, 

659 weeks=-weeks, 

660 days=-days, 

661 hours=-hours, 

662 minutes=-minutes, 

663 seconds=-seconds, 

664 microseconds=-microseconds, 

665 ) 

666 

667 # Adding a final underscore to the method name 

668 # to avoid errors for PyPy which already defines 

669 # a _add_timedelta method 

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

671 """ 

672 Add timedelta duration to the instance. 

673 """ 

674 if isinstance(delta, pendulum.Interval): 

675 return self.add( 

676 years=delta.years, 

677 months=delta.months, 

678 weeks=delta.weeks, 

679 days=delta.remaining_days, 

680 hours=delta.hours, 

681 minutes=delta.minutes, 

682 seconds=delta.remaining_seconds, 

683 microseconds=delta.microseconds, 

684 ) 

685 elif isinstance(delta, pendulum.Duration): 

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

687 

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

689 

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

691 """ 

692 Remove timedelta duration from the instance. 

693 """ 

694 if isinstance(delta, pendulum.Duration): 

695 return self.subtract( 

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

697 ) 

698 

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

700 

701 # DIFFERENCES 

702 

703 def diff( # type: ignore[override] 

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

705 ) -> Interval[datetime.datetime]: 

706 """ 

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

708 """ 

709 if dt is None: 

710 dt = self.now(self.tz) 

711 

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

713 

714 def diff_for_humans( # type: ignore[override] 

715 self, 

716 other: DateTime | None = None, 

717 absolute: bool = False, 

718 locale: str | None = None, 

719 ) -> str: 

720 """ 

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

722 

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

724 1 day ago 

725 5 months ago 

726 

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

728 1 day from now 

729 5 months from now 

730 

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

732 1 day before 

733 5 months before 

734 

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

736 1 day after 

737 5 months after 

738 """ 

739 is_now = other is None 

740 

741 if is_now: 

742 other = self.now() 

743 

744 diff = self.diff(other) 

745 

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

747 

748 # Modifiers 

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

750 """ 

751 Returns a copy of the instance with the time reset 

752 with the following rules: 

753 

754 * second: microsecond set to 0 

755 * minute: second and microsecond set to 0 

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

757 * day: time to 00:00:00 

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

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

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

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

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

763 """ 

764 if unit not in self._MODIFIERS_VALID_UNITS: 

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

766 

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

768 

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

770 """ 

771 Returns a copy of the instance with the time reset 

772 with the following rules: 

773 

774 * second: microsecond set to 999999 

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

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

777 * day: time to 23:59:59.999999 

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

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

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

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

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

783 """ 

784 if unit not in self._MODIFIERS_VALID_UNITS: 

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

786 

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

788 

789 def _start_of_second(self) -> Self: 

790 """ 

791 Reset microseconds to 0. 

792 """ 

793 return self.set(microsecond=0) 

794 

795 def _end_of_second(self) -> Self: 

796 """ 

797 Set microseconds to 999999. 

798 """ 

799 return self.set(microsecond=999999) 

800 

801 def _start_of_minute(self) -> Self: 

802 """ 

803 Reset seconds and microseconds to 0. 

804 """ 

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

806 

807 def _end_of_minute(self) -> Self: 

808 """ 

809 Set seconds to 59 and microseconds to 999999. 

810 """ 

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

812 

813 def _start_of_hour(self) -> Self: 

814 """ 

815 Reset minutes, seconds and microseconds to 0. 

816 """ 

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

818 

819 def _end_of_hour(self) -> Self: 

820 """ 

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

822 """ 

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

824 

825 def _start_of_day(self) -> Self: 

826 """ 

827 Reset the time to 00:00:00. 

828 """ 

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

830 

831 def _end_of_day(self) -> Self: 

832 """ 

833 Reset the time to 23:59:59.999999. 

834 """ 

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

836 

837 def _start_of_month(self) -> Self: 

838 """ 

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

840 """ 

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

842 

843 def _end_of_month(self) -> Self: 

844 """ 

845 Reset the date to the last day of the month 

846 and the time to 23:59:59.999999. 

847 """ 

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

849 

850 def _start_of_year(self) -> Self: 

851 """ 

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

853 """ 

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

855 

856 def _end_of_year(self) -> Self: 

857 """ 

858 Reset the date to the last day of the year 

859 and the time to 23:59:59.999999. 

860 """ 

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

862 

863 def _start_of_decade(self) -> Self: 

864 """ 

865 Reset the date to the first day of the decade 

866 and the time to 00:00:00. 

867 """ 

868 year = self.year - self.year % YEARS_PER_DECADE 

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

870 

871 def _end_of_decade(self) -> Self: 

872 """ 

873 Reset the date to the last day of the decade 

874 and the time to 23:59:59.999999. 

875 """ 

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

877 

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

879 

880 def _start_of_century(self) -> Self: 

881 """ 

882 Reset the date to the first day of the century 

883 and the time to 00:00:00. 

884 """ 

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

886 

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

888 

889 def _end_of_century(self) -> Self: 

890 """ 

891 Reset the date to the last day of the century 

892 and the time to 23:59:59.999999. 

893 """ 

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

895 

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

897 

898 def _start_of_week(self) -> Self: 

899 """ 

900 Reset the date to the first day of the week 

901 and the time to 00:00:00. 

902 """ 

903 dt = self 

904 

905 if self.day_of_week != pendulum._WEEK_STARTS_AT: 

906 dt = self.previous(pendulum._WEEK_STARTS_AT) 

907 

908 return dt.start_of("day") 

909 

910 def _end_of_week(self) -> Self: 

911 """ 

912 Reset the date to the last day of the week 

913 and the time to 23:59:59. 

914 """ 

915 dt = self 

916 

917 if self.day_of_week != pendulum._WEEK_ENDS_AT: 

918 dt = self.next(pendulum._WEEK_ENDS_AT) 

919 

920 return dt.end_of("day") 

921 

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

923 """ 

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

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

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

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

928 """ 

929 if day_of_week is None: 

930 day_of_week = self.day_of_week 

931 

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

933 raise ValueError("Invalid day of week") 

934 

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

936 

937 dt = dt.add(days=1) 

938 while dt.day_of_week != day_of_week: 

939 dt = dt.add(days=1) 

940 

941 return dt 

942 

943 def previous( 

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

945 ) -> Self: 

946 """ 

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

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

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

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

951 """ 

952 if day_of_week is None: 

953 day_of_week = self.day_of_week 

954 

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

956 raise ValueError("Invalid day of week") 

957 

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

959 

960 dt = dt.subtract(days=1) 

961 while dt.day_of_week != day_of_week: 

962 dt = dt.subtract(days=1) 

963 

964 return dt 

965 

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

967 """ 

968 Returns an instance set to the first occurrence 

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

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

971 Use the supplied consts to indicate the desired day_of_week, 

972 ex. DateTime.MONDAY. 

973 

974 Supported units are month, quarter and year. 

975 """ 

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

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

978 

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

980 

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

982 """ 

983 Returns an instance set to the last occurrence 

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

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

986 Use the supplied consts to indicate the desired day_of_week, 

987 ex. DateTime.MONDAY. 

988 

989 Supported units are month, quarter and year. 

990 """ 

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

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

993 

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

995 

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

997 """ 

998 Returns a new instance set to the given occurrence 

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

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

1001 then raise an error. Use the supplied consts 

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

1003 

1004 Supported units are month, quarter and year. 

1005 """ 

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

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

1008 

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

1010 if not dt: 

1011 raise PendulumException( 

1012 f"Unable to find occurrence {nth}" 

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

1014 ) 

1015 

1016 return dt 

1017 

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

1019 """ 

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

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

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

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

1024 """ 

1025 dt = self.start_of("day") 

1026 

1027 if day_of_week is None: 

1028 return dt.set(day=1) 

1029 

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

1031 

1032 calendar_day = day_of_week 

1033 

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

1035 day_of_month = month[0][calendar_day] 

1036 else: 

1037 day_of_month = month[1][calendar_day] 

1038 

1039 return dt.set(day=day_of_month) 

1040 

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

1042 """ 

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

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

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

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

1047 """ 

1048 dt = self.start_of("day") 

1049 

1050 if day_of_week is None: 

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

1052 

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

1054 

1055 calendar_day = day_of_week 

1056 

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

1058 day_of_month = month[-1][calendar_day] 

1059 else: 

1060 day_of_month = month[-2][calendar_day] 

1061 

1062 return dt.set(day=day_of_month) 

1063 

1064 def _nth_of_month( 

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

1066 ) -> Self | None: 

1067 """ 

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

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

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

1071 modifications are made. Use the supplied consts 

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

1073 """ 

1074 if nth == 1: 

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

1076 

1077 dt = self.first_of("month") 

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

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

1080 dt = dt.next(day_of_week) 

1081 

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

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

1084 

1085 return None 

1086 

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

1088 """ 

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

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

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

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

1093 """ 

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

1095 "month", day_of_week 

1096 ) 

1097 

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

1099 """ 

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

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

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

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

1104 """ 

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

1106 

1107 def _nth_of_quarter( 

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

1109 ) -> Self | None: 

1110 """ 

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

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

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

1114 modifications are made. Use the supplied consts 

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

1116 """ 

1117 if nth == 1: 

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

1119 

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

1121 last_month = dt.month 

1122 year = dt.year 

1123 dt = dt.first_of("quarter") 

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

1125 dt = dt.next(day_of_week) 

1126 

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

1128 return None 

1129 

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

1131 

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

1133 """ 

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

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

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

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

1138 """ 

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

1140 

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

1142 """ 

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

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

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

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

1147 """ 

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

1149 

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

1151 """ 

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

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

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

1155 modifications are made. Use the supplied consts 

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

1157 """ 

1158 if nth == 1: 

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

1160 

1161 dt = self.first_of("year") 

1162 year = dt.year 

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

1164 dt = dt.next(day_of_week) 

1165 

1166 if year != dt.year: 

1167 return None 

1168 

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

1170 

1171 def average( # type: ignore[override] 

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

1173 ) -> Self: 

1174 """ 

1175 Modify the current instance to the average 

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

1177 """ 

1178 if dt is None: 

1179 dt = self.now(self.tz) 

1180 

1181 diff = self.diff(dt, False) 

1182 return self.add( 

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

1184 ) 

1185 

1186 @overload # type: ignore[override] 

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

1188 

1189 @overload 

1190 def __sub__(self, other: DateTime) -> Interval[datetime.datetime]: ... 

1191 

1192 def __sub__( 

1193 self, other: datetime.datetime | datetime.timedelta 

1194 ) -> Self | Interval[datetime.datetime]: 

1195 if isinstance(other, datetime.timedelta): 

1196 return self._subtract_timedelta(other) 

1197 

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

1199 return NotImplemented 

1200 

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

1202 if other.tzinfo is None: 

1203 other = pendulum.naive( 

1204 other.year, 

1205 other.month, 

1206 other.day, 

1207 other.hour, 

1208 other.minute, 

1209 other.second, 

1210 other.microsecond, 

1211 ) 

1212 else: 

1213 other = self.instance(other) 

1214 

1215 return other.diff(self, False) 

1216 

1217 def __rsub__(self, other: datetime.datetime) -> Interval[datetime.datetime]: 

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

1219 return NotImplemented 

1220 

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

1222 if other.tzinfo is None: 

1223 other = pendulum.naive( 

1224 other.year, 

1225 other.month, 

1226 other.day, 

1227 other.hour, 

1228 other.minute, 

1229 other.second, 

1230 other.microsecond, 

1231 ) 

1232 else: 

1233 other = self.instance(other) 

1234 

1235 return self.diff(other, False) 

1236 

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

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

1239 return NotImplemented 

1240 

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

1242 if caller == "astimezone": 

1243 return super().__add__(other) 

1244 

1245 return self._add_timedelta_(other) 

1246 

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

1248 return self.__add__(other) 

1249 

1250 # Native methods override 

1251 

1252 @classmethod 

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

1254 tzinfo = pendulum._safe_timezone(tz) 

1255 

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

1257 

1258 @classmethod 

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

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

1261 

1262 @classmethod 

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

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

1265 

1266 @classmethod 

1267 def combine( 

1268 cls, 

1269 date: datetime.date, 

1270 time: datetime.time, 

1271 tzinfo: datetime.tzinfo | None = None, 

1272 ) -> Self: 

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

1274 

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

1276 dt = super().astimezone(tz) 

1277 

1278 return self.__class__( 

1279 dt.year, 

1280 dt.month, 

1281 dt.day, 

1282 dt.hour, 

1283 dt.minute, 

1284 dt.second, 

1285 dt.microsecond, 

1286 fold=dt.fold, 

1287 tzinfo=dt.tzinfo, 

1288 ) 

1289 

1290 def replace( 

1291 self, 

1292 year: SupportsIndex | None = None, 

1293 month: SupportsIndex | None = None, 

1294 day: SupportsIndex | None = None, 

1295 hour: SupportsIndex | None = None, 

1296 minute: SupportsIndex | None = None, 

1297 second: SupportsIndex | None = None, 

1298 microsecond: SupportsIndex | None = None, 

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

1300 fold: int | None = None, 

1301 ) -> Self: 

1302 if year is None: 

1303 year = self.year 

1304 if month is None: 

1305 month = self.month 

1306 if day is None: 

1307 day = self.day 

1308 if hour is None: 

1309 hour = self.hour 

1310 if minute is None: 

1311 minute = self.minute 

1312 if second is None: 

1313 second = self.second 

1314 if microsecond is None: 

1315 microsecond = self.microsecond 

1316 if tzinfo is True: 

1317 tzinfo = self.tzinfo 

1318 if fold is None: 

1319 fold = self.fold 

1320 

1321 if tzinfo is not None: 

1322 tzinfo = pendulum._safe_timezone(tzinfo) 

1323 

1324 return self.__class__.create( 

1325 year, 

1326 month, 

1327 day, 

1328 hour, 

1329 minute, 

1330 second, 

1331 microsecond, 

1332 tz=tzinfo, 

1333 fold=fold, 

1334 ) 

1335 

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

1337 return (self,) 

1338 

1339 def _getstate( 

1340 self, protocol: SupportsIndex = 3 

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

1342 return ( 

1343 self.year, 

1344 self.month, 

1345 self.day, 

1346 self.hour, 

1347 self.minute, 

1348 self.second, 

1349 self.microsecond, 

1350 self.tzinfo, 

1351 ) 

1352 

1353 def __reduce__( 

1354 self, 

1355 ) -> tuple[ 

1356 type[Self], 

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

1358 ]: 

1359 return self.__reduce_ex__(2) 

1360 

1361 def __reduce_ex__( 

1362 self, protocol: SupportsIndex 

1363 ) -> tuple[ 

1364 type[Self], 

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

1366 ]: 

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

1368 

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

1370 return self.__class__( 

1371 self.year, 

1372 self.month, 

1373 self.day, 

1374 self.hour, 

1375 self.minute, 

1376 self.second, 

1377 self.microsecond, 

1378 tzinfo=self.tz, 

1379 fold=self.fold, 

1380 ) 

1381 

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

1383 # Fix for pypy which compares using this method 

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

1385 dt = datetime.datetime( 

1386 self.year, 

1387 self.month, 

1388 self.day, 

1389 self.hour, 

1390 self.minute, 

1391 self.second, 

1392 self.microsecond, 

1393 tzinfo=self.tz, 

1394 fold=self.fold, 

1395 ) 

1396 

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

1398 

1399 

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

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

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