Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pendulum/datetime.py : 33%

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
5import calendar
6import datetime
8from typing import Optional
9from typing import TypeVar
10from typing import Union
12import pendulum
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
42_D = TypeVar("_D", bound="DateTime")
45class DateTime(datetime.datetime, Date):
47 EPOCH = None # type: DateTime
49 # Formats
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 }
65 _EPOCH = datetime.datetime(1970, 1, 1, tzinfo=UTC)
67 _MODIFIERS_VALID_UNITS = [
68 "second",
69 "minute",
70 "hour",
71 "day",
72 "week",
73 "month",
74 "year",
75 "decade",
76 "century",
77 ]
79 if not _HAS_FOLD:
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 )
97 self._fold = fold
99 return self
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)
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)
115 @classmethod
116 def today(cls): # type: () -> DateTime
117 return pendulum.now()
119 @classmethod
120 def strptime(cls, time, fmt): # type: (str, str) -> DateTime
121 return pendulum.instance(datetime.datetime.strptime(time, fmt))
123 # Getters/Setters
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
153 return pendulum.datetime(
154 year, month, day, hour, minute, second, microsecond, tz=tz
155 )
157 if not _HAS_FOLD:
159 @property
160 def fold(self):
161 return self._fold
163 def timestamp(self):
164 if self.tzinfo is None:
165 s = timestamp(self)
167 return s + self.microsecond / 1e6
168 else:
169 kwargs = {"tzinfo": self.tzinfo}
171 if _HAS_FOLD:
172 kwargs["fold"] = self.fold
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()
186 @property
187 def float_timestamp(self):
188 return self.timestamp()
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}
196 if _HAS_FOLD:
197 kwargs["fold"] = self.fold
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 )
210 delta = dt - self._EPOCH
212 return delta.days * SECONDS_PER_DAY + delta.seconds
214 @property
215 def offset(self):
216 return self.get_offset()
218 @property
219 def offset_hours(self):
220 return self.get_offset() / SECONDS_PER_MINUTE / MINUTES_PER_HOUR
222 @property
223 def timezone(self): # type: () -> Optional[Timezone]
224 if not isinstance(self.tzinfo, Timezone):
225 return
227 return self.tzinfo
229 @property
230 def tz(self): # type: () -> Optional[Timezone]
231 return self.timezone
233 @property
234 def timezone_name(self): # type: () -> Optional[str]
235 tz = self.timezone
237 if tz is None:
238 return None
240 return tz.name
242 @property
243 def age(self):
244 return self.date().diff(self.now(self.tz).date(), abs=False).in_years()
246 def is_local(self):
247 return self.offset == self.in_timezone(pendulum.local_timezone()).offset
249 def is_utc(self):
250 return self.offset == UTC.offset
252 def is_dst(self):
253 return self.dst() != datetime.timedelta()
255 def get_offset(self):
256 return int(self.utcoffset().total_seconds())
258 def date(self):
259 return Date(self.year, self.month, self.day)
261 def time(self):
262 return Time(self.hour, self.minute, self.second, self.microsecond)
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 )
278 def on(self, year, month, day):
279 """
280 Returns a new instance with the current date set to a different date.
282 :param year: The year
283 :type year: int
285 :param month: The month
286 :type month: int
288 :param day: The day
289 :type day: int
291 :rtype: DateTime
292 """
293 return self.set(year=int(year), month=int(month), day=int(day))
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.
299 :param hour: The hour
300 :type hour: int
302 :param minute: The minute
303 :type minute: int
305 :param second: The second
306 :type second: int
308 :param microsecond: The microsecond
309 :type microsecond: int
311 :rtype: DateTime
312 """
313 return self.set(
314 hour=hour, minute=minute, second=second, microsecond=microsecond
315 )
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)
323 return tz.convert(self, dst_rule=pendulum.POST_TRANSITION)
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)
331 # STRING FORMATTING
333 def to_time_string(self):
334 """
335 Format the instance as time.
337 :rtype: str
338 """
339 return self.format("HH:mm:ss")
341 def to_datetime_string(self):
342 """
343 Format the instance as date and time.
345 :rtype: str
346 """
347 return self.format("YYYY-MM-DD HH:mm:ss")
349 def to_day_datetime_string(self):
350 """
351 Format the instance as day, date and time (in english).
353 :rtype: str
354 """
355 return self.format("ddd, MMM D, YYYY h:mm A", locale="en")
357 def to_atom_string(self):
358 """
359 Format the instance as ATOM.
361 :rtype: str
362 """
363 return self._to_string("atom")
365 def to_cookie_string(self):
366 """
367 Format the instance as COOKIE.
369 :rtype: str
370 """
371 return self._to_string("cookie", locale="en")
373 def to_iso8601_string(self):
374 """
375 Format the instance as ISO 8601.
377 :rtype: str
378 """
379 string = self._to_string("iso8601")
381 if self.tz and self.tz.name == "UTC":
382 string = string.replace("+00:00", "Z")
384 return string
386 def to_rfc822_string(self):
387 """
388 Format the instance as RFC 822.
390 :rtype: str
391 """
392 return self._to_string("rfc822")
394 def to_rfc850_string(self):
395 """
396 Format the instance as RFC 850.
398 :rtype: str
399 """
400 return self._to_string("rfc850")
402 def to_rfc1036_string(self):
403 """
404 Format the instance as RFC 1036.
406 :rtype: str
407 """
408 return self._to_string("rfc1036")
410 def to_rfc1123_string(self):
411 """
412 Format the instance as RFC 1123.
414 :rtype: str
415 """
416 return self._to_string("rfc1123")
418 def to_rfc2822_string(self):
419 """
420 Format the instance as RFC 2822.
422 :rtype: str
423 """
424 return self._to_string("rfc2822")
426 def to_rfc3339_string(self):
427 """
428 Format the instance as RFC 3339.
430 :rtype: str
431 """
432 return self._to_string("rfc3339")
434 def to_rss_string(self):
435 """
436 Format the instance as RSS.
438 :rtype: str
439 """
440 return self._to_string("rss")
442 def to_w3c_string(self):
443 """
444 Format the instance as W3C.
446 :rtype: str
447 """
448 return self._to_string("w3c")
450 def _to_string(self, fmt, locale=None):
451 """
452 Format the instance to a common string format.
454 :param fmt: The name of the string format
455 :type fmt: string
457 :param locale: The locale to use
458 :type locale: str or None
460 :rtype: str
461 """
462 if fmt not in self._FORMATS:
463 raise ValueError("Format [{}] is not supported".format(fmt))
465 fmt = self._FORMATS[fmt]
466 if callable(fmt):
467 return fmt(self)
469 return self.format(fmt, locale=locale)
471 def __str__(self):
472 return self.isoformat("T")
474 def __repr__(self):
475 us = ""
476 if self.microsecond:
477 us = ", {}".format(self.microsecond)
479 repr_ = "{klass}(" "{year}, {month}, {day}, " "{hour}, {minute}, {second}{us}"
481 if self.tzinfo is not None:
482 repr_ += ", tzinfo={tzinfo}"
484 repr_ += ")"
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 )
498 # Comparisons
499 def closest(self, dt1, dt2, *dts):
500 """
501 Get the farthest date from the instance.
503 :type dt1: datetime.datetime
504 :type dt2: datetime.datetime
505 :type dts: list[datetime.datetime,]
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]
514 return min(dts)[1]
516 def farthest(self, dt1, dt2, *dts):
517 """
518 Get the farthest date from the instance.
520 :type dt1: datetime.datetime
521 :type dt2: datetime.datetime
522 :type dts: list[datetime.datetime,]
524 :rtype: DateTime
525 """
526 dt1 = pendulum.instance(dt1)
527 dt2 = pendulum.instance(dt2)
529 dts = [dt1, dt2] + [pendulum.instance(x) for x in dts]
530 dts = [(abs(self - dt), dt) for dt in dts]
532 return max(dts)[1]
534 def is_future(self):
535 """
536 Determines if the instance is in the future, ie. greater than now.
538 :rtype: bool
539 """
540 return self > self.now(self.timezone)
542 def is_past(self):
543 """
544 Determines if the instance is in the past, ie. less than now.
546 :rtype: bool
547 """
548 return self < self.now(self.timezone)
550 def is_long_year(self):
551 """
552 Determines if the instance is a long year
554 See link `https://en.wikipedia.org/wiki/ISO_8601#Week_dates`_
556 :rtype: bool
557 """
558 return (
559 pendulum.datetime(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1]
560 == 53
561 )
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.
568 :type dt: DateTime or datetime or str or int
570 :rtype: bool
571 """
572 dt = pendulum.instance(dt)
574 return self.to_date_string() == dt.to_date_string()
576 def is_anniversary(self, dt=None):
577 """
578 Check if its the anniversary.
579 Compares the date/month values of the two dates.
581 :rtype: bool
582 """
583 if dt is None:
584 dt = self.now(self.tz)
586 instance = pendulum.instance(dt)
588 return (self.month, self.day) == (instance.month, instance.day)
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
595 # ADDITIONS AND SUBSTRACTIONS
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.
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])
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
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 )
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 )
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 )
667 dt = self.tz.convert(dt)
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 )
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.
695 :param years: The number of years
696 :type years: int
698 :param months: The number of months
699 :type months: int
701 :param weeks: The number of weeks
702 :type weeks: int
704 :param days: The number of days
705 :type days: int
707 :param hours: The number of hours
708 :type hours: int
710 :param minutes: The number of minutes
711 :type minutes: int
713 :param seconds: The number of seconds
714 :type seconds: int
716 :param microseconds: The number of microseconds
717 :type microseconds: int
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 )
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.
739 :param delta: The timedelta instance
740 :type delta: pendulum.Duration or datetime.timedelta
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 )
760 return self.add(seconds=delta.total_seconds())
762 def _subtract_timedelta(self, delta):
763 """
764 Remove timedelta duration from the instance.
766 :param delta: The timedelta instance
767 :type delta: pendulum.Duration or datetime.timedelta
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 )
783 return self.subtract(
784 days=delta.days, seconds=delta.seconds, microseconds=delta.microseconds
785 )
787 # DIFFERENCES
789 def diff(self, dt=None, abs=True):
790 """
791 Returns the difference between two DateTime objects represented as a Duration.
793 :type dt: DateTime or None
795 :param abs: Whether to return an absolute interval or not
796 :type abs: bool
798 :rtype: Period
799 """
800 if dt is None:
801 dt = self.now(self.tz)
803 return Period(self, dt, absolute=abs)
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.
814 When comparing a value in the past to default now:
815 1 day ago
816 5 months ago
818 When comparing a value in the future to default now:
819 1 day from now
820 5 months from now
822 When comparing a value in the past to another value:
823 1 day before
824 5 months before
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
832 if is_now:
833 other = self.now()
835 diff = self.diff(other)
837 return pendulum.format_diff(diff, is_now, absolute, locale)
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:
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
855 :param unit: The unit to reset to
856 :type unit: str
858 :rtype: DateTime
859 """
860 if unit not in self._MODIFIERS_VALID_UNITS:
861 raise ValueError('Invalid unit "{}" for start_of()'.format(unit))
863 return getattr(self, "_start_of_{}".format(unit))()
865 def end_of(self, unit):
866 """
867 Returns a copy of the instance with the time reset
868 with the following rules:
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
880 :param unit: The unit to reset to
881 :type unit: str
883 :rtype: DateTime
884 """
885 if unit not in self._MODIFIERS_VALID_UNITS:
886 raise ValueError('Invalid unit "%s" for end_of()' % unit)
888 return getattr(self, "_end_of_%s" % unit)()
890 def _start_of_second(self):
891 """
892 Reset microseconds to 0.
894 :rtype: DateTime
895 """
896 return self.set(microsecond=0)
898 def _end_of_second(self):
899 """
900 Set microseconds to 999999.
902 :rtype: DateTime
903 """
904 return self.set(microsecond=999999)
906 def _start_of_minute(self):
907 """
908 Reset seconds and microseconds to 0.
910 :rtype: DateTime
911 """
912 return self.set(second=0, microsecond=0)
914 def _end_of_minute(self):
915 """
916 Set seconds to 59 and microseconds to 999999.
918 :rtype: DateTime
919 """
920 return self.set(second=59, microsecond=999999)
922 def _start_of_hour(self):
923 """
924 Reset minutes, seconds and microseconds to 0.
926 :rtype: DateTime
927 """
928 return self.set(minute=0, second=0, microsecond=0)
930 def _end_of_hour(self):
931 """
932 Set minutes and seconds to 59 and microseconds to 999999.
934 :rtype: DateTime
935 """
936 return self.set(minute=59, second=59, microsecond=999999)
938 def _start_of_day(self):
939 """
940 Reset the time to 00:00:00
942 :rtype: DateTime
943 """
944 return self.at(0, 0, 0, 0)
946 def _end_of_day(self):
947 """
948 Reset the time to 23:59:59.999999
950 :rtype: DateTime
951 """
952 return self.at(23, 59, 59, 999999)
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.
958 :rtype: DateTime
959 """
960 return self.set(self.year, self.month, 1, 0, 0, 0, 0)
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.
967 :rtype: DateTime
968 """
969 return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999)
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.
975 :rtype: DateTime
976 """
977 return self.set(self.year, 1, 1, 0, 0, 0, 0)
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
984 :rtype: DateTime
985 """
986 return self.set(self.year, 12, 31, 23, 59, 59, 999999)
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.
993 :rtype: DateTime
994 """
995 year = self.year - self.year % YEARS_PER_DECADE
996 return self.set(year, 1, 1, 0, 0, 0, 0)
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.
1003 :rtype: DateTime
1004 """
1005 year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1
1007 return self.set(year, 12, 31, 23, 59, 59, 999999)
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.
1014 :rtype: DateTime
1015 """
1016 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1
1018 return self.set(year, 1, 1, 0, 0, 0, 0)
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.
1025 :rtype: DateTime
1026 """
1027 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY
1029 return self.set(year, 12, 31, 23, 59, 59, 999999)
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.
1036 :rtype: DateTime
1037 """
1038 dt = self
1040 if self.day_of_week != pendulum._WEEK_STARTS_AT:
1041 dt = self.previous(pendulum._WEEK_STARTS_AT)
1043 return dt.start_of("day")
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.
1050 :rtype: DateTime
1051 """
1052 dt = self
1054 if self.day_of_week != pendulum._WEEK_ENDS_AT:
1055 dt = self.next(pendulum._WEEK_ENDS_AT)
1057 return dt.end_of("day")
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.
1066 :param day_of_week: The next day of week to reset to.
1067 :type day_of_week: int or None
1069 :param keep_time: Whether to keep the time information or not.
1070 :type keep_time: bool
1072 :rtype: DateTime
1073 """
1074 if day_of_week is None:
1075 day_of_week = self.day_of_week
1077 if day_of_week < SUNDAY or day_of_week > SATURDAY:
1078 raise ValueError("Invalid day of week")
1080 if keep_time:
1081 dt = self
1082 else:
1083 dt = self.start_of("day")
1085 dt = dt.add(days=1)
1086 while dt.day_of_week != day_of_week:
1087 dt = dt.add(days=1)
1089 return dt
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.
1098 :param day_of_week: The previous day of week to reset to.
1099 :type day_of_week: int or None
1101 :param keep_time: Whether to keep the time information or not.
1102 :type keep_time: bool
1104 :rtype: DateTime
1105 """
1106 if day_of_week is None:
1107 day_of_week = self.day_of_week
1109 if day_of_week < SUNDAY or day_of_week > SATURDAY:
1110 raise ValueError("Invalid day of week")
1112 if keep_time:
1113 dt = self
1114 else:
1115 dt = self.start_of("day")
1117 dt = dt.subtract(days=1)
1118 while dt.day_of_week != day_of_week:
1119 dt = dt.subtract(days=1)
1121 return dt
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.
1130 Supported units are month, quarter and year.
1132 :param unit: The unit to use
1133 :type unit: str
1135 :type day_of_week: int or None
1137 :rtype: DateTime
1138 """
1139 if unit not in ["month", "quarter", "year"]:
1140 raise ValueError('Invalid unit "{}" for first_of()'.format(unit))
1142 return getattr(self, "_first_of_{}".format(unit))(day_of_week)
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.
1151 Supported units are month, quarter and year.
1153 :param unit: The unit to use
1154 :type unit: str
1156 :type day_of_week: int or None
1158 :rtype: DateTime
1159 """
1160 if unit not in ["month", "quarter", "year"]:
1161 raise ValueError('Invalid unit "{}" for first_of()'.format(unit))
1163 return getattr(self, "_last_of_{}".format(unit))(day_of_week)
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.
1173 Supported units are month, quarter and year.
1175 :param unit: The unit to use
1176 :type unit: str
1178 :type nth: int
1180 :type day_of_week: int or None
1182 :rtype: DateTime
1183 """
1184 if unit not in ["month", "quarter", "year"]:
1185 raise ValueError('Invalid unit "{}" for first_of()'.format(unit))
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 )
1195 return dt
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.
1204 :type day_of_week: int
1206 :rtype: DateTime
1207 """
1208 dt = self.start_of("day")
1210 if day_of_week is None:
1211 return dt.set(day=1)
1213 month = calendar.monthcalendar(dt.year, dt.month)
1215 calendar_day = (day_of_week - 1) % 7
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]
1222 return dt.set(day=day_of_month)
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.
1231 :type day_of_week: int or None
1233 :rtype: DateTime
1234 """
1235 dt = self.start_of("day")
1237 if day_of_week is None:
1238 return dt.set(day=self.days_in_month)
1240 month = calendar.monthcalendar(dt.year, dt.month)
1242 calendar_day = (day_of_week - 1) % 7
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]
1249 return dt.set(day=day_of_month)
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.
1259 :type nth: int
1261 :type day_of_week: int or None
1263 :rtype: DateTime
1264 """
1265 if nth == 1:
1266 return self.first_of("month", day_of_week)
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)
1273 if dt.format("%Y-%M") == check:
1274 return self.set(day=dt.day).start_of("day")
1276 return False
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.
1285 :type day_of_week: int or None
1287 :rtype: DateTime
1288 """
1289 return self.on(self.year, self.quarter * 3 - 2, 1).first_of(
1290 "month", day_of_week
1291 )
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.
1300 :type day_of_week: int or None
1302 :rtype: DateTime
1303 """
1304 return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
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.
1314 :type nth: int
1316 :type day_of_week: int or None
1318 :rtype: DateTime
1319 """
1320 if nth == 1:
1321 return self.first_of("quarter", day_of_week)
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)
1330 if last_month < dt.month or year != dt.year:
1331 return False
1333 return self.on(self.year, dt.month, dt.day).start_of("day")
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.
1342 :type day_of_week: int or None
1344 :rtype: DateTime
1345 """
1346 return self.set(month=1).first_of("month", day_of_week)
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.
1355 :type day_of_week: int or None
1357 :rtype: DateTime
1358 """
1359 return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
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.
1369 :type nth: int
1371 :type day_of_week: int or None
1373 :rtype: DateTime
1374 """
1375 if nth == 1:
1376 return self.first_of("year", day_of_week)
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)
1383 if year != dt.year:
1384 return False
1386 return self.on(self.year, dt.month, dt.day).start_of("day")
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.
1393 :type dt: DateTime or datetime
1395 :rtype: DateTime
1396 """
1397 if dt is None:
1398 dt = self.now(self.tz)
1400 diff = self.diff(dt, False)
1401 return self.add(
1402 microseconds=(diff.in_seconds() * 1000000 + diff.microseconds) // 2
1403 )
1405 def __sub__(self, other):
1406 if isinstance(other, datetime.timedelta):
1407 return self._subtract_timedelta(other)
1409 if not isinstance(other, datetime.datetime):
1410 return NotImplemented
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)
1426 return other.diff(self, False)
1428 def __rsub__(self, other):
1429 if not isinstance(other, datetime.datetime):
1430 return NotImplemented
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)
1446 return self.diff(other, False)
1448 def __add__(self, other):
1449 if not isinstance(other, datetime.timedelta):
1450 return NotImplemented
1452 return self._add_timedelta_(other)
1454 def __radd__(self, other):
1455 return self.__add__(other)
1457 # Native methods override
1459 @classmethod
1460 def fromtimestamp(cls, t, tz=None):
1461 return pendulum.instance(datetime.datetime.fromtimestamp(t, tz=tz), tz=tz)
1463 @classmethod
1464 def utcfromtimestamp(cls, t):
1465 return pendulum.instance(datetime.datetime.utcfromtimestamp(t), tz=None)
1467 @classmethod
1468 def fromordinal(cls, n):
1469 return pendulum.instance(datetime.datetime.fromordinal(n), tz=None)
1471 @classmethod
1472 def combine(cls, date, time):
1473 return pendulum.instance(datetime.datetime.combine(date, time), tz=None)
1475 def astimezone(self, tz=None):
1476 return pendulum.instance(super(DateTime, self).astimezone(tz))
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
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
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 )
1525 def __getnewargs__(self):
1526 return (self,)
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 )
1540 def __reduce__(self):
1541 return self.__reduce_ex__(2)
1543 def __reduce_ex__(self, protocol):
1544 return self.__class__, self._getstate(protocol)
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}
1551 if _HAS_FOLD:
1552 kwargs["fold"] = self.fold
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 )
1565 return 0 if dt == other else 1 if dt > other else -1
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)