Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

2from __future__ import absolute_import 

3from __future__ import division 

4 

5import calendar 

6import datetime 

7 

8from typing import Optional 

9from typing import TypeVar 

10from typing import Union 

11 

12import pendulum 

13 

14from .constants import ATOM 

15from .constants import COOKIE 

16from .constants import MINUTES_PER_HOUR 

17from .constants import MONTHS_PER_YEAR 

18from .constants import RFC822 

19from .constants import RFC850 

20from .constants import RFC1036 

21from .constants import RFC1123 

22from .constants import RFC2822 

23from .constants import RSS 

24from .constants import SATURDAY 

25from .constants import SECONDS_PER_DAY 

26from .constants import SECONDS_PER_MINUTE 

27from .constants import SUNDAY 

28from .constants import W3C 

29from .constants import YEARS_PER_CENTURY 

30from .constants import YEARS_PER_DECADE 

31from .date import Date 

32from .exceptions import PendulumException 

33from .helpers import add_duration 

34from .helpers import timestamp 

35from .period import Period 

36from .time import Time 

37from .tz import UTC 

38from .tz.timezone import Timezone 

39from .utils._compat import _HAS_FOLD 

40 

41 

42_D = TypeVar("_D", bound="DateTime") 

43 

44 

45class DateTime(datetime.datetime, Date): 

46 

47 EPOCH = None # type: DateTime 

48 

49 # Formats 

50 

51 _FORMATS = { 

52 "atom": ATOM, 

53 "cookie": COOKIE, 

54 "iso8601": lambda dt: dt.isoformat(), 

55 "rfc822": RFC822, 

56 "rfc850": RFC850, 

57 "rfc1036": RFC1036, 

58 "rfc1123": RFC1123, 

59 "rfc2822": RFC2822, 

60 "rfc3339": lambda dt: dt.isoformat(), 

61 "rss": RSS, 

62 "w3c": W3C, 

63 } 

64 

65 _EPOCH = datetime.datetime(1970, 1, 1, tzinfo=UTC) 

66 

67 _MODIFIERS_VALID_UNITS = [ 

68 "second", 

69 "minute", 

70 "hour", 

71 "day", 

72 "week", 

73 "month", 

74 "year", 

75 "decade", 

76 "century", 

77 ] 

78 

79 if not _HAS_FOLD: 

80 

81 def __new__( 

82 cls, 

83 year, 

84 month, 

85 day, 

86 hour=0, 

87 minute=0, 

88 second=0, 

89 microsecond=0, 

90 tzinfo=None, 

91 fold=0, 

92 ): 

93 self = datetime.datetime.__new__( 

94 cls, year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo 

95 ) 

96 

97 self._fold = fold 

98 

99 return self 

100 

101 @classmethod 

102 def now(cls, tz=None): # type: (Optional[Union[str, Timezone]]) -> DateTime 

103 """ 

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

105 """ 

106 return pendulum.now(tz) 

107 

108 @classmethod 

109 def utcnow(cls): # type: () -> DateTime 

110 """ 

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

112 """ 

113 return pendulum.now(UTC) 

114 

115 @classmethod 

116 def today(cls): # type: () -> DateTime 

117 return pendulum.now() 

118 

119 @classmethod 

120 def strptime(cls, time, fmt): # type: (str, str) -> DateTime 

121 return pendulum.instance(datetime.datetime.strptime(time, fmt)) 

122 

123 # Getters/Setters 

124 

125 def set( 

126 self, 

127 year=None, 

128 month=None, 

129 day=None, 

130 hour=None, 

131 minute=None, 

132 second=None, 

133 microsecond=None, 

134 tz=None, 

135 ): 

136 if year is None: 

137 year = self.year 

138 if month is None: 

139 month = self.month 

140 if day is None: 

141 day = self.day 

142 if hour is None: 

143 hour = self.hour 

144 if minute is None: 

145 minute = self.minute 

146 if second is None: 

147 second = self.second 

148 if microsecond is None: 

149 microsecond = self.microsecond 

150 if tz is None: 

151 tz = self.tz 

152 

153 return pendulum.datetime( 

154 year, month, day, hour, minute, second, microsecond, tz=tz 

155 ) 

156 

157 if not _HAS_FOLD: 

158 

159 @property 

160 def fold(self): 

161 return self._fold 

162 

163 def timestamp(self): 

164 if self.tzinfo is None: 

165 s = timestamp(self) 

166 

167 return s + self.microsecond / 1e6 

168 else: 

169 kwargs = {"tzinfo": self.tzinfo} 

170 

171 if _HAS_FOLD: 

172 kwargs["fold"] = self.fold 

173 

174 dt = datetime.datetime( 

175 self.year, 

176 self.month, 

177 self.day, 

178 self.hour, 

179 self.minute, 

180 self.second, 

181 self.microsecond, 

182 **kwargs 

183 ) 

184 return (dt - self._EPOCH).total_seconds() 

185 

186 @property 

187 def float_timestamp(self): 

188 return self.timestamp() 

189 

190 @property 

191 def int_timestamp(self): 

192 # Workaround needed to avoid inaccuracy 

193 # for far into the future datetimes 

194 kwargs = {"tzinfo": self.tzinfo} 

195 

196 if _HAS_FOLD: 

197 kwargs["fold"] = self.fold 

198 

199 dt = datetime.datetime( 

200 self.year, 

201 self.month, 

202 self.day, 

203 self.hour, 

204 self.minute, 

205 self.second, 

206 self.microsecond, 

207 **kwargs 

208 ) 

209 

210 delta = dt - self._EPOCH 

211 

212 return delta.days * SECONDS_PER_DAY + delta.seconds 

213 

214 @property 

215 def offset(self): 

216 return self.get_offset() 

217 

218 @property 

219 def offset_hours(self): 

220 return self.get_offset() / SECONDS_PER_MINUTE / MINUTES_PER_HOUR 

221 

222 @property 

223 def timezone(self): # type: () -> Optional[Timezone] 

224 if not isinstance(self.tzinfo, Timezone): 

225 return 

226 

227 return self.tzinfo 

228 

229 @property 

230 def tz(self): # type: () -> Optional[Timezone] 

231 return self.timezone 

232 

233 @property 

234 def timezone_name(self): # type: () -> Optional[str] 

235 tz = self.timezone 

236 

237 if tz is None: 

238 return None 

239 

240 return tz.name 

241 

242 @property 

243 def age(self): 

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

245 

246 def is_local(self): 

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

248 

249 def is_utc(self): 

250 return self.offset == UTC.offset 

251 

252 def is_dst(self): 

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

254 

255 def get_offset(self): 

256 return int(self.utcoffset().total_seconds()) 

257 

258 def date(self): 

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

260 

261 def time(self): 

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

263 

264 def naive(self): # type: (_D) -> _D 

265 """ 

266 Return the DateTime without timezone information. 

267 """ 

268 return self.__class__( 

269 self.year, 

270 self.month, 

271 self.day, 

272 self.hour, 

273 self.minute, 

274 self.second, 

275 self.microsecond, 

276 ) 

277 

278 def on(self, year, month, day): 

279 """ 

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

281 

282 :param year: The year 

283 :type year: int 

284 

285 :param month: The month 

286 :type month: int 

287 

288 :param day: The day 

289 :type day: int 

290 

291 :rtype: DateTime 

292 """ 

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

294 

295 def at(self, hour, minute=0, second=0, microsecond=0): 

296 """ 

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

298 

299 :param hour: The hour 

300 :type hour: int 

301 

302 :param minute: The minute 

303 :type minute: int 

304 

305 :param second: The second 

306 :type second: int 

307 

308 :param microsecond: The microsecond 

309 :type microsecond: int 

310 

311 :rtype: DateTime 

312 """ 

313 return self.set( 

314 hour=hour, minute=minute, second=second, microsecond=microsecond 

315 ) 

316 

317 def in_timezone(self, tz): # type: (Union[str, Timezone]) -> DateTime 

318 """ 

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

320 """ 

321 tz = pendulum._safe_timezone(tz) 

322 

323 return tz.convert(self, dst_rule=pendulum.POST_TRANSITION) 

324 

325 def in_tz(self, tz): # type: (Union[str, Timezone]) -> DateTime 

326 """ 

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

328 """ 

329 return self.in_timezone(tz) 

330 

331 # STRING FORMATTING 

332 

333 def to_time_string(self): 

334 """ 

335 Format the instance as time. 

336 

337 :rtype: str 

338 """ 

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

340 

341 def to_datetime_string(self): 

342 """ 

343 Format the instance as date and time. 

344 

345 :rtype: str 

346 """ 

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

348 

349 def to_day_datetime_string(self): 

350 """ 

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

352 

353 :rtype: str 

354 """ 

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

356 

357 def to_atom_string(self): 

358 """ 

359 Format the instance as ATOM. 

360 

361 :rtype: str 

362 """ 

363 return self._to_string("atom") 

364 

365 def to_cookie_string(self): 

366 """ 

367 Format the instance as COOKIE. 

368 

369 :rtype: str 

370 """ 

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

372 

373 def to_iso8601_string(self): 

374 """ 

375 Format the instance as ISO 8601. 

376 

377 :rtype: str 

378 """ 

379 string = self._to_string("iso8601") 

380 

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

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

383 

384 return string 

385 

386 def to_rfc822_string(self): 

387 """ 

388 Format the instance as RFC 822. 

389 

390 :rtype: str 

391 """ 

392 return self._to_string("rfc822") 

393 

394 def to_rfc850_string(self): 

395 """ 

396 Format the instance as RFC 850. 

397 

398 :rtype: str 

399 """ 

400 return self._to_string("rfc850") 

401 

402 def to_rfc1036_string(self): 

403 """ 

404 Format the instance as RFC 1036. 

405 

406 :rtype: str 

407 """ 

408 return self._to_string("rfc1036") 

409 

410 def to_rfc1123_string(self): 

411 """ 

412 Format the instance as RFC 1123. 

413 

414 :rtype: str 

415 """ 

416 return self._to_string("rfc1123") 

417 

418 def to_rfc2822_string(self): 

419 """ 

420 Format the instance as RFC 2822. 

421 

422 :rtype: str 

423 """ 

424 return self._to_string("rfc2822") 

425 

426 def to_rfc3339_string(self): 

427 """ 

428 Format the instance as RFC 3339. 

429 

430 :rtype: str 

431 """ 

432 return self._to_string("rfc3339") 

433 

434 def to_rss_string(self): 

435 """ 

436 Format the instance as RSS. 

437 

438 :rtype: str 

439 """ 

440 return self._to_string("rss") 

441 

442 def to_w3c_string(self): 

443 """ 

444 Format the instance as W3C. 

445 

446 :rtype: str 

447 """ 

448 return self._to_string("w3c") 

449 

450 def _to_string(self, fmt, locale=None): 

451 """ 

452 Format the instance to a common string format. 

453 

454 :param fmt: The name of the string format 

455 :type fmt: string 

456 

457 :param locale: The locale to use 

458 :type locale: str or None 

459 

460 :rtype: str 

461 """ 

462 if fmt not in self._FORMATS: 

463 raise ValueError("Format [{}] is not supported".format(fmt)) 

464 

465 fmt = self._FORMATS[fmt] 

466 if callable(fmt): 

467 return fmt(self) 

468 

469 return self.format(fmt, locale=locale) 

470 

471 def __str__(self): 

472 return self.isoformat("T") 

473 

474 def __repr__(self): 

475 us = "" 

476 if self.microsecond: 

477 us = ", {}".format(self.microsecond) 

478 

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

480 

481 if self.tzinfo is not None: 

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

483 

484 repr_ += ")" 

485 

486 return repr_.format( 

487 klass=self.__class__.__name__, 

488 year=self.year, 

489 month=self.month, 

490 day=self.day, 

491 hour=self.hour, 

492 minute=self.minute, 

493 second=self.second, 

494 us=us, 

495 tzinfo=self.tzinfo, 

496 ) 

497 

498 # Comparisons 

499 def closest(self, dt1, dt2, *dts): 

500 """ 

501 Get the farthest date from the instance. 

502 

503 :type dt1: datetime.datetime 

504 :type dt2: datetime.datetime 

505 :type dts: list[datetime.datetime,] 

506 

507 :rtype: DateTime 

508 """ 

509 dt1 = pendulum.instance(dt1) 

510 dt2 = pendulum.instance(dt2) 

511 dts = [dt1, dt2] + [pendulum.instance(x) for x in dts] 

512 dts = [(abs(self - dt), dt) for dt in dts] 

513 

514 return min(dts)[1] 

515 

516 def farthest(self, dt1, dt2, *dts): 

517 """ 

518 Get the farthest date from the instance. 

519 

520 :type dt1: datetime.datetime 

521 :type dt2: datetime.datetime 

522 :type dts: list[datetime.datetime,] 

523 

524 :rtype: DateTime 

525 """ 

526 dt1 = pendulum.instance(dt1) 

527 dt2 = pendulum.instance(dt2) 

528 

529 dts = [dt1, dt2] + [pendulum.instance(x) for x in dts] 

530 dts = [(abs(self - dt), dt) for dt in dts] 

531 

532 return max(dts)[1] 

533 

534 def is_future(self): 

535 """ 

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

537 

538 :rtype: bool 

539 """ 

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

541 

542 def is_past(self): 

543 """ 

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

545 

546 :rtype: bool 

547 """ 

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

549 

550 def is_long_year(self): 

551 """ 

552 Determines if the instance is a long year 

553 

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

555 

556 :rtype: bool 

557 """ 

558 return ( 

559 pendulum.datetime(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1] 

560 == 53 

561 ) 

562 

563 def is_same_day(self, dt): 

564 """ 

565 Checks if the passed in date is the same day 

566 as the instance current day. 

567 

568 :type dt: DateTime or datetime or str or int 

569 

570 :rtype: bool 

571 """ 

572 dt = pendulum.instance(dt) 

573 

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

575 

576 def is_anniversary(self, dt=None): 

577 """ 

578 Check if its the anniversary. 

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

580 

581 :rtype: bool 

582 """ 

583 if dt is None: 

584 dt = self.now(self.tz) 

585 

586 instance = pendulum.instance(dt) 

587 

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

589 

590 # the additional method for checking if today is the anniversary day 

591 # the alias is provided to start using a new name and keep the backward compatibility 

592 # the old name can be completely replaced with the new in one of the future versions 

593 is_birthday = is_anniversary 

594 

595 # ADDITIONS AND SUBSTRACTIONS 

596 

597 def add( 

598 self, 

599 years=0, 

600 months=0, 

601 weeks=0, 

602 days=0, 

603 hours=0, 

604 minutes=0, 

605 seconds=0, 

606 microseconds=0, 

607 ): # type: (_D, int, int, int, int, int, int, int, int) -> _D 

608 """ 

609 Add a duration to the instance. 

610 

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

612 move forward from curren time, 

613 otherwise move forward from utc, for accuracy 

614 when moving across DST boundaries. 

615 """ 

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

617 

618 current_dt = datetime.datetime( 

619 self.year, 

620 self.month, 

621 self.day, 

622 self.hour, 

623 self.minute, 

624 self.second, 

625 self.microsecond, 

626 ) 

627 if not units_of_variable_length: 

628 offset = self.utcoffset() 

629 if offset: 

630 current_dt = current_dt - offset 

631 

632 dt = add_duration( 

633 current_dt, 

634 years=years, 

635 months=months, 

636 weeks=weeks, 

637 days=days, 

638 hours=hours, 

639 minutes=minutes, 

640 seconds=seconds, 

641 microseconds=microseconds, 

642 ) 

643 

644 if units_of_variable_length or self.tzinfo is None: 

645 return pendulum.datetime( 

646 dt.year, 

647 dt.month, 

648 dt.day, 

649 dt.hour, 

650 dt.minute, 

651 dt.second, 

652 dt.microsecond, 

653 tz=self.tz, 

654 ) 

655 

656 dt = self.__class__( 

657 dt.year, 

658 dt.month, 

659 dt.day, 

660 dt.hour, 

661 dt.minute, 

662 dt.second, 

663 dt.microsecond, 

664 tzinfo=UTC, 

665 ) 

666 

667 dt = self.tz.convert(dt) 

668 

669 return self.__class__( 

670 dt.year, 

671 dt.month, 

672 dt.day, 

673 dt.hour, 

674 dt.minute, 

675 dt.second, 

676 dt.microsecond, 

677 tzinfo=self.tz, 

678 fold=dt.fold, 

679 ) 

680 

681 def subtract( 

682 self, 

683 years=0, 

684 months=0, 

685 weeks=0, 

686 days=0, 

687 hours=0, 

688 minutes=0, 

689 seconds=0, 

690 microseconds=0, 

691 ): 

692 """ 

693 Remove duration from the instance. 

694 

695 :param years: The number of years 

696 :type years: int 

697 

698 :param months: The number of months 

699 :type months: int 

700 

701 :param weeks: The number of weeks 

702 :type weeks: int 

703 

704 :param days: The number of days 

705 :type days: int 

706 

707 :param hours: The number of hours 

708 :type hours: int 

709 

710 :param minutes: The number of minutes 

711 :type minutes: int 

712 

713 :param seconds: The number of seconds 

714 :type seconds: int 

715 

716 :param microseconds: The number of microseconds 

717 :type microseconds: int 

718 

719 :rtype: DateTime 

720 """ 

721 return self.add( 

722 years=-years, 

723 months=-months, 

724 weeks=-weeks, 

725 days=-days, 

726 hours=-hours, 

727 minutes=-minutes, 

728 seconds=-seconds, 

729 microseconds=-microseconds, 

730 ) 

731 

732 # Adding a final underscore to the method name 

733 # to avoid errors for PyPy which already defines 

734 # a _add_timedelta method 

735 def _add_timedelta_(self, delta): 

736 """ 

737 Add timedelta duration to the instance. 

738 

739 :param delta: The timedelta instance 

740 :type delta: pendulum.Duration or datetime.timedelta 

741 

742 :rtype: DateTime 

743 """ 

744 if isinstance(delta, pendulum.Period): 

745 return self.add( 

746 years=delta.years, 

747 months=delta.months, 

748 weeks=delta.weeks, 

749 days=delta.remaining_days, 

750 hours=delta.hours, 

751 minutes=delta.minutes, 

752 seconds=delta.remaining_seconds, 

753 microseconds=delta.microseconds, 

754 ) 

755 elif isinstance(delta, pendulum.Duration): 

756 return self.add( 

757 years=delta.years, months=delta.months, seconds=delta.total_seconds() 

758 ) 

759 

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

761 

762 def _subtract_timedelta(self, delta): 

763 """ 

764 Remove timedelta duration from the instance. 

765 

766 :param delta: The timedelta instance 

767 :type delta: pendulum.Duration or datetime.timedelta 

768 

769 :rtype: DateTime 

770 """ 

771 if isinstance(delta, pendulum.Duration): 

772 return self.subtract( 

773 years=delta.years, 

774 months=delta.months, 

775 weeks=delta.weeks, 

776 days=delta.remaining_days, 

777 hours=delta.hours, 

778 minutes=delta.minutes, 

779 seconds=delta.remaining_seconds, 

780 microseconds=delta.microseconds, 

781 ) 

782 

783 return self.subtract( 

784 days=delta.days, seconds=delta.seconds, microseconds=delta.microseconds 

785 ) 

786 

787 # DIFFERENCES 

788 

789 def diff(self, dt=None, abs=True): 

790 """ 

791 Returns the difference between two DateTime objects represented as a Duration. 

792 

793 :type dt: DateTime or None 

794 

795 :param abs: Whether to return an absolute interval or not 

796 :type abs: bool 

797 

798 :rtype: Period 

799 """ 

800 if dt is None: 

801 dt = self.now(self.tz) 

802 

803 return Period(self, dt, absolute=abs) 

804 

805 def diff_for_humans( 

806 self, 

807 other=None, # type: Optional[DateTime] 

808 absolute=False, # type: bool 

809 locale=None, # type: Optional[str] 

810 ): # type: (...) -> str 

811 """ 

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

813 

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

815 1 day ago 

816 5 months ago 

817 

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

819 1 day from now 

820 5 months from now 

821 

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

823 1 day before 

824 5 months before 

825 

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

827 1 day after 

828 5 months after 

829 """ 

830 is_now = other is None 

831 

832 if is_now: 

833 other = self.now() 

834 

835 diff = self.diff(other) 

836 

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

838 

839 # Modifiers 

840 def start_of(self, unit): 

841 """ 

842 Returns a copy of the instance with the time reset 

843 with the following rules: 

844 

845 * second: microsecond set to 0 

846 * minute: second and microsecond set to 0 

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

848 * day: time to 00:00:00 

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

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

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

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

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

854 

855 :param unit: The unit to reset to 

856 :type unit: str 

857 

858 :rtype: DateTime 

859 """ 

860 if unit not in self._MODIFIERS_VALID_UNITS: 

861 raise ValueError('Invalid unit "{}" for start_of()'.format(unit)) 

862 

863 return getattr(self, "_start_of_{}".format(unit))() 

864 

865 def end_of(self, unit): 

866 """ 

867 Returns a copy of the instance with the time reset 

868 with the following rules: 

869 

870 * second: microsecond set to 999999 

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

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

873 * day: time to 23:59:59.999999 

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

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

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

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

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

879 

880 :param unit: The unit to reset to 

881 :type unit: str 

882 

883 :rtype: DateTime 

884 """ 

885 if unit not in self._MODIFIERS_VALID_UNITS: 

886 raise ValueError('Invalid unit "%s" for end_of()' % unit) 

887 

888 return getattr(self, "_end_of_%s" % unit)() 

889 

890 def _start_of_second(self): 

891 """ 

892 Reset microseconds to 0. 

893 

894 :rtype: DateTime 

895 """ 

896 return self.set(microsecond=0) 

897 

898 def _end_of_second(self): 

899 """ 

900 Set microseconds to 999999. 

901 

902 :rtype: DateTime 

903 """ 

904 return self.set(microsecond=999999) 

905 

906 def _start_of_minute(self): 

907 """ 

908 Reset seconds and microseconds to 0. 

909 

910 :rtype: DateTime 

911 """ 

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

913 

914 def _end_of_minute(self): 

915 """ 

916 Set seconds to 59 and microseconds to 999999. 

917 

918 :rtype: DateTime 

919 """ 

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

921 

922 def _start_of_hour(self): 

923 """ 

924 Reset minutes, seconds and microseconds to 0. 

925 

926 :rtype: DateTime 

927 """ 

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

929 

930 def _end_of_hour(self): 

931 """ 

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

933 

934 :rtype: DateTime 

935 """ 

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

937 

938 def _start_of_day(self): 

939 """ 

940 Reset the time to 00:00:00 

941 

942 :rtype: DateTime 

943 """ 

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

945 

946 def _end_of_day(self): 

947 """ 

948 Reset the time to 23:59:59.999999 

949 

950 :rtype: DateTime 

951 """ 

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

953 

954 def _start_of_month(self): 

955 """ 

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

957 

958 :rtype: DateTime 

959 """ 

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

961 

962 def _end_of_month(self): 

963 """ 

964 Reset the date to the last day of the month 

965 and the time to 23:59:59.999999. 

966 

967 :rtype: DateTime 

968 """ 

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

970 

971 def _start_of_year(self): 

972 """ 

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

974 

975 :rtype: DateTime 

976 """ 

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

978 

979 def _end_of_year(self): 

980 """ 

981 Reset the date to the last day of the year 

982 and the time to 23:59:59.999999 

983 

984 :rtype: DateTime 

985 """ 

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

987 

988 def _start_of_decade(self): 

989 """ 

990 Reset the date to the first day of the decade 

991 and the time to 00:00:00. 

992 

993 :rtype: DateTime 

994 """ 

995 year = self.year - self.year % YEARS_PER_DECADE 

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

997 

998 def _end_of_decade(self): 

999 """ 

1000 Reset the date to the last day of the decade 

1001 and the time to 23:59:59.999999. 

1002 

1003 :rtype: DateTime 

1004 """ 

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

1006 

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

1008 

1009 def _start_of_century(self): 

1010 """ 

1011 Reset the date to the first day of the century 

1012 and the time to 00:00:00. 

1013 

1014 :rtype: DateTime 

1015 """ 

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

1017 

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

1019 

1020 def _end_of_century(self): 

1021 """ 

1022 Reset the date to the last day of the century 

1023 and the time to 23:59:59.999999. 

1024 

1025 :rtype: DateTime 

1026 """ 

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

1028 

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

1030 

1031 def _start_of_week(self): 

1032 """ 

1033 Reset the date to the first day of the week 

1034 and the time to 00:00:00. 

1035 

1036 :rtype: DateTime 

1037 """ 

1038 dt = self 

1039 

1040 if self.day_of_week != pendulum._WEEK_STARTS_AT: 

1041 dt = self.previous(pendulum._WEEK_STARTS_AT) 

1042 

1043 return dt.start_of("day") 

1044 

1045 def _end_of_week(self): 

1046 """ 

1047 Reset the date to the last day of the week 

1048 and the time to 23:59:59. 

1049 

1050 :rtype: DateTime 

1051 """ 

1052 dt = self 

1053 

1054 if self.day_of_week != pendulum._WEEK_ENDS_AT: 

1055 dt = self.next(pendulum._WEEK_ENDS_AT) 

1056 

1057 return dt.end_of("day") 

1058 

1059 def next(self, day_of_week=None, keep_time=False): 

1060 """ 

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

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

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

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

1065 

1066 :param day_of_week: The next day of week to reset to. 

1067 :type day_of_week: int or None 

1068 

1069 :param keep_time: Whether to keep the time information or not. 

1070 :type keep_time: bool 

1071 

1072 :rtype: DateTime 

1073 """ 

1074 if day_of_week is None: 

1075 day_of_week = self.day_of_week 

1076 

1077 if day_of_week < SUNDAY or day_of_week > SATURDAY: 

1078 raise ValueError("Invalid day of week") 

1079 

1080 if keep_time: 

1081 dt = self 

1082 else: 

1083 dt = self.start_of("day") 

1084 

1085 dt = dt.add(days=1) 

1086 while dt.day_of_week != day_of_week: 

1087 dt = dt.add(days=1) 

1088 

1089 return dt 

1090 

1091 def previous(self, day_of_week=None, keep_time=False): 

1092 """ 

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

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

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

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

1097 

1098 :param day_of_week: The previous day of week to reset to. 

1099 :type day_of_week: int or None 

1100 

1101 :param keep_time: Whether to keep the time information or not. 

1102 :type keep_time: bool 

1103 

1104 :rtype: DateTime 

1105 """ 

1106 if day_of_week is None: 

1107 day_of_week = self.day_of_week 

1108 

1109 if day_of_week < SUNDAY or day_of_week > SATURDAY: 

1110 raise ValueError("Invalid day of week") 

1111 

1112 if keep_time: 

1113 dt = self 

1114 else: 

1115 dt = self.start_of("day") 

1116 

1117 dt = dt.subtract(days=1) 

1118 while dt.day_of_week != day_of_week: 

1119 dt = dt.subtract(days=1) 

1120 

1121 return dt 

1122 

1123 def first_of(self, unit, day_of_week=None): 

1124 """ 

1125 Returns an instance set to the first occurrence 

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

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

1128 Use the supplied consts to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1129 

1130 Supported units are month, quarter and year. 

1131 

1132 :param unit: The unit to use 

1133 :type unit: str 

1134 

1135 :type day_of_week: int or None 

1136 

1137 :rtype: DateTime 

1138 """ 

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

1140 raise ValueError('Invalid unit "{}" for first_of()'.format(unit)) 

1141 

1142 return getattr(self, "_first_of_{}".format(unit))(day_of_week) 

1143 

1144 def last_of(self, unit, day_of_week=None): 

1145 """ 

1146 Returns an instance set to the last occurrence 

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

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

1149 Use the supplied consts to indicate the desired day_of_week, ex. DateTime.MONDAY. 

1150 

1151 Supported units are month, quarter and year. 

1152 

1153 :param unit: The unit to use 

1154 :type unit: str 

1155 

1156 :type day_of_week: int or None 

1157 

1158 :rtype: DateTime 

1159 """ 

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

1161 raise ValueError('Invalid unit "{}" for first_of()'.format(unit)) 

1162 

1163 return getattr(self, "_last_of_{}".format(unit))(day_of_week) 

1164 

1165 def nth_of(self, unit, nth, day_of_week): 

1166 """ 

1167 Returns a new instance set to the given occurrence 

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

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

1170 then raise an error. Use the supplied consts 

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

1172 

1173 Supported units are month, quarter and year. 

1174 

1175 :param unit: The unit to use 

1176 :type unit: str 

1177 

1178 :type nth: int 

1179 

1180 :type day_of_week: int or None 

1181 

1182 :rtype: DateTime 

1183 """ 

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

1185 raise ValueError('Invalid unit "{}" for first_of()'.format(unit)) 

1186 

1187 dt = getattr(self, "_nth_of_{}".format(unit))(nth, day_of_week) 

1188 if dt is False: 

1189 raise PendulumException( 

1190 "Unable to find occurence {} of {} in {}".format( 

1191 nth, self._days[day_of_week], unit 

1192 ) 

1193 ) 

1194 

1195 return dt 

1196 

1197 def _first_of_month(self, day_of_week): 

1198 """ 

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

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

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

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

1203 

1204 :type day_of_week: int 

1205 

1206 :rtype: DateTime 

1207 """ 

1208 dt = self.start_of("day") 

1209 

1210 if day_of_week is None: 

1211 return dt.set(day=1) 

1212 

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

1214 

1215 calendar_day = (day_of_week - 1) % 7 

1216 

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

1218 day_of_month = month[0][calendar_day] 

1219 else: 

1220 day_of_month = month[1][calendar_day] 

1221 

1222 return dt.set(day=day_of_month) 

1223 

1224 def _last_of_month(self, day_of_week=None): 

1225 """ 

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

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

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

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

1230 

1231 :type day_of_week: int or None 

1232 

1233 :rtype: DateTime 

1234 """ 

1235 dt = self.start_of("day") 

1236 

1237 if day_of_week is None: 

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

1239 

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

1241 

1242 calendar_day = (day_of_week - 1) % 7 

1243 

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

1245 day_of_month = month[-1][calendar_day] 

1246 else: 

1247 day_of_month = month[-2][calendar_day] 

1248 

1249 return dt.set(day=day_of_month) 

1250 

1251 def _nth_of_month(self, nth, day_of_week): 

1252 """ 

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

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

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

1256 modifications are made. Use the supplied consts 

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

1258 

1259 :type nth: int 

1260 

1261 :type day_of_week: int or None 

1262 

1263 :rtype: DateTime 

1264 """ 

1265 if nth == 1: 

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

1267 

1268 dt = self.first_of("month") 

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

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

1271 dt = dt.next(day_of_week) 

1272 

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

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

1275 

1276 return False 

1277 

1278 def _first_of_quarter(self, day_of_week=None): 

1279 """ 

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

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

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

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

1284 

1285 :type day_of_week: int or None 

1286 

1287 :rtype: DateTime 

1288 """ 

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

1290 "month", day_of_week 

1291 ) 

1292 

1293 def _last_of_quarter(self, day_of_week=None): 

1294 """ 

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

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

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

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

1299 

1300 :type day_of_week: int or None 

1301 

1302 :rtype: DateTime 

1303 """ 

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

1305 

1306 def _nth_of_quarter(self, nth, day_of_week): 

1307 """ 

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

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

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

1311 modifications are made. Use the supplied consts 

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

1313 

1314 :type nth: int 

1315 

1316 :type day_of_week: int or None 

1317 

1318 :rtype: DateTime 

1319 """ 

1320 if nth == 1: 

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

1322 

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

1324 last_month = dt.month 

1325 year = dt.year 

1326 dt = dt.first_of("quarter") 

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

1328 dt = dt.next(day_of_week) 

1329 

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

1331 return False 

1332 

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

1334 

1335 def _first_of_year(self, day_of_week=None): 

1336 """ 

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

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

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

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

1341 

1342 :type day_of_week: int or None 

1343 

1344 :rtype: DateTime 

1345 """ 

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

1347 

1348 def _last_of_year(self, day_of_week=None): 

1349 """ 

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

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

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

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

1354 

1355 :type day_of_week: int or None 

1356 

1357 :rtype: DateTime 

1358 """ 

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

1360 

1361 def _nth_of_year(self, nth, day_of_week): 

1362 """ 

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

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

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

1366 modifications are made. Use the supplied consts 

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

1368 

1369 :type nth: int 

1370 

1371 :type day_of_week: int or None 

1372 

1373 :rtype: DateTime 

1374 """ 

1375 if nth == 1: 

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

1377 

1378 dt = self.first_of("year") 

1379 year = dt.year 

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

1381 dt = dt.next(day_of_week) 

1382 

1383 if year != dt.year: 

1384 return False 

1385 

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

1387 

1388 def average(self, dt=None): 

1389 """ 

1390 Modify the current instance to the average 

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

1392 

1393 :type dt: DateTime or datetime 

1394 

1395 :rtype: DateTime 

1396 """ 

1397 if dt is None: 

1398 dt = self.now(self.tz) 

1399 

1400 diff = self.diff(dt, False) 

1401 return self.add( 

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

1403 ) 

1404 

1405 def __sub__(self, other): 

1406 if isinstance(other, datetime.timedelta): 

1407 return self._subtract_timedelta(other) 

1408 

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

1410 return NotImplemented 

1411 

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

1413 if other.tzinfo is None: 

1414 other = pendulum.naive( 

1415 other.year, 

1416 other.month, 

1417 other.day, 

1418 other.hour, 

1419 other.minute, 

1420 other.second, 

1421 other.microsecond, 

1422 ) 

1423 else: 

1424 other = pendulum.instance(other) 

1425 

1426 return other.diff(self, False) 

1427 

1428 def __rsub__(self, other): 

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

1430 return NotImplemented 

1431 

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

1433 if other.tzinfo is None: 

1434 other = pendulum.naive( 

1435 other.year, 

1436 other.month, 

1437 other.day, 

1438 other.hour, 

1439 other.minute, 

1440 other.second, 

1441 other.microsecond, 

1442 ) 

1443 else: 

1444 other = pendulum.instance(other) 

1445 

1446 return self.diff(other, False) 

1447 

1448 def __add__(self, other): 

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

1450 return NotImplemented 

1451 

1452 return self._add_timedelta_(other) 

1453 

1454 def __radd__(self, other): 

1455 return self.__add__(other) 

1456 

1457 # Native methods override 

1458 

1459 @classmethod 

1460 def fromtimestamp(cls, t, tz=None): 

1461 return pendulum.instance(datetime.datetime.fromtimestamp(t, tz=tz), tz=tz) 

1462 

1463 @classmethod 

1464 def utcfromtimestamp(cls, t): 

1465 return pendulum.instance(datetime.datetime.utcfromtimestamp(t), tz=None) 

1466 

1467 @classmethod 

1468 def fromordinal(cls, n): 

1469 return pendulum.instance(datetime.datetime.fromordinal(n), tz=None) 

1470 

1471 @classmethod 

1472 def combine(cls, date, time): 

1473 return pendulum.instance(datetime.datetime.combine(date, time), tz=None) 

1474 

1475 def astimezone(self, tz=None): 

1476 return pendulum.instance(super(DateTime, self).astimezone(tz)) 

1477 

1478 def replace( 

1479 self, 

1480 year=None, 

1481 month=None, 

1482 day=None, 

1483 hour=None, 

1484 minute=None, 

1485 second=None, 

1486 microsecond=None, 

1487 tzinfo=True, 

1488 fold=None, 

1489 ): 

1490 if year is None: 

1491 year = self.year 

1492 if month is None: 

1493 month = self.month 

1494 if day is None: 

1495 day = self.day 

1496 if hour is None: 

1497 hour = self.hour 

1498 if minute is None: 

1499 minute = self.minute 

1500 if second is None: 

1501 second = self.second 

1502 if microsecond is None: 

1503 microsecond = self.microsecond 

1504 if tzinfo is True: 

1505 tzinfo = self.tzinfo 

1506 

1507 transition_rule = pendulum.POST_TRANSITION 

1508 if fold is not None: 

1509 transition_rule = pendulum.PRE_TRANSITION 

1510 if fold: 

1511 transition_rule = pendulum.POST_TRANSITION 

1512 

1513 return pendulum.datetime( 

1514 year, 

1515 month, 

1516 day, 

1517 hour, 

1518 minute, 

1519 second, 

1520 microsecond, 

1521 tz=tzinfo, 

1522 dst_rule=transition_rule, 

1523 ) 

1524 

1525 def __getnewargs__(self): 

1526 return (self,) 

1527 

1528 def _getstate(self, protocol=3): 

1529 return ( 

1530 self.year, 

1531 self.month, 

1532 self.day, 

1533 self.hour, 

1534 self.minute, 

1535 self.second, 

1536 self.microsecond, 

1537 self.tzinfo, 

1538 ) 

1539 

1540 def __reduce__(self): 

1541 return self.__reduce_ex__(2) 

1542 

1543 def __reduce_ex__(self, protocol): 

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

1545 

1546 def _cmp(self, other, **kwargs): 

1547 # Fix for pypy which compares using this method 

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

1549 kwargs = {"tzinfo": self.tz} 

1550 

1551 if _HAS_FOLD: 

1552 kwargs["fold"] = self.fold 

1553 

1554 dt = datetime.datetime( 

1555 self.year, 

1556 self.month, 

1557 self.day, 

1558 self.hour, 

1559 self.minute, 

1560 self.second, 

1561 self.microsecond, 

1562 **kwargs 

1563 ) 

1564 

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

1566 

1567 

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

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

1570DateTime.EPOCH = DateTime(1970, 1, 1)