opendate 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of opendate might be problematic. Click here for more details.
- date/__init__.py +15 -13
- date/date.py +166 -185
- date/extras.py +90 -0
- {opendate-0.1.2.dist-info → opendate-0.1.4.dist-info}/METADATA +2 -2
- opendate-0.1.4.dist-info/RECORD +7 -0
- date/business.py +0 -42
- opendate-0.1.2.dist-info/RECORD +0 -7
- {opendate-0.1.2.dist-info → opendate-0.1.4.dist-info}/LICENSE +0 -0
- {opendate-0.1.2.dist-info → opendate-0.1.4.dist-info}/WHEEL +0 -0
date/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = '0.1.
|
|
1
|
+
__version__ = '0.1.4'
|
|
2
2
|
|
|
3
3
|
import datetime as _datetime
|
|
4
4
|
|
|
@@ -31,7 +31,9 @@ from date.date import expect_utc_timezone
|
|
|
31
31
|
from date.date import prefer_native_timezone
|
|
32
32
|
from date.date import prefer_utc_timezone
|
|
33
33
|
from date.date import Timezone
|
|
34
|
-
from date.
|
|
34
|
+
from date.extras import overlap_days
|
|
35
|
+
from date.extras import is_business_day
|
|
36
|
+
from date.extras import is_within_business_hours
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
timezone = Timezone
|
|
@@ -49,16 +51,14 @@ def time(*args, **kwargs):
|
|
|
49
51
|
return Time(*args, **kwargs)
|
|
50
52
|
|
|
51
53
|
|
|
52
|
-
def parse():
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
54
|
+
def parse(s: str | None, fmt: str = None, entity: Entity = NYSE, raise_err: bool = False) -> DateTime | None:
|
|
55
|
+
"""Parse using DateTime.parse
|
|
56
|
+
"""
|
|
57
|
+
return DateTime.parse(s, entity=entity, raise_err=True)
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
def instance(obj: _datetime.date | _datetime.datetime | _datetime.time) -> DateTime | Date | Time:
|
|
60
|
-
"""
|
|
61
|
-
Create a DateTime/Date/Time instance from a datetime/date/time native one.
|
|
61
|
+
"""Create a DateTime/Date/Time instance from a datetime/date/time native one.
|
|
62
62
|
"""
|
|
63
63
|
if isinstance(obj, DateTime | Date | Time):
|
|
64
64
|
return obj
|
|
@@ -71,13 +71,13 @@ def instance(obj: _datetime.date | _datetime.datetime | _datetime.time) -> DateT
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
def now(tz: str | _zoneinfo.ZoneInfo | None = None) -> DateTime:
|
|
74
|
-
"""
|
|
74
|
+
"""Returns Datetime.now
|
|
75
75
|
"""
|
|
76
76
|
return DateTime.now(tz)
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
def today(tz: str | _zoneinfo.ZoneInfo = None) -> DateTime:
|
|
80
|
-
"""
|
|
80
|
+
"""Returns DateTime.today
|
|
81
81
|
"""
|
|
82
82
|
return DateTime.today(tz)
|
|
83
83
|
|
|
@@ -104,6 +104,8 @@ __all__ = [
|
|
|
104
104
|
'NYSE',
|
|
105
105
|
'date',
|
|
106
106
|
'datetime',
|
|
107
|
-
'time'
|
|
108
|
-
'
|
|
107
|
+
'time',
|
|
108
|
+
'overlap_days',
|
|
109
|
+
'is_within_business_hours',
|
|
110
|
+
'is_business_day',
|
|
109
111
|
]
|
date/date.py
CHANGED
|
@@ -8,9 +8,7 @@ import time
|
|
|
8
8
|
import warnings
|
|
9
9
|
import zoneinfo as _zoneinfo
|
|
10
10
|
from abc import ABC, abstractmethod
|
|
11
|
-
from collections import namedtuple
|
|
12
11
|
from collections.abc import Callable, Sequence
|
|
13
|
-
from enum import IntEnum
|
|
14
12
|
from functools import lru_cache, partial, wraps
|
|
15
13
|
from typing import Self
|
|
16
14
|
|
|
@@ -31,8 +29,6 @@ __all__ = [
|
|
|
31
29
|
'IntervalError',
|
|
32
30
|
'Time',
|
|
33
31
|
'Timezone',
|
|
34
|
-
'WeekDay',
|
|
35
|
-
'WEEKDAY_SHORTNAME',
|
|
36
32
|
'EST',
|
|
37
33
|
'UTC',
|
|
38
34
|
'GMT',
|
|
@@ -45,11 +41,33 @@ __all__ = [
|
|
|
45
41
|
'expect_datetime',
|
|
46
42
|
'Entity',
|
|
47
43
|
'NYSE'
|
|
44
|
+
'WEEKDAY_SHORTNAME',
|
|
48
45
|
]
|
|
49
46
|
|
|
50
47
|
|
|
51
48
|
def Timezone(name:str = 'US/Eastern') -> _zoneinfo.ZoneInfo:
|
|
52
49
|
"""Simple wrapper around Pendulum `Timezone`
|
|
50
|
+
|
|
51
|
+
Ex: sanity check US/Eastern == America/New_York
|
|
52
|
+
|
|
53
|
+
>>> winter1 = DateTime(2000, 1, 1, 12, tzinfo=Timezone('US/Eastern'))
|
|
54
|
+
>>> winter2 = DateTime(2000, 1, 1, 12, tzinfo=Timezone('America/New_York'))
|
|
55
|
+
|
|
56
|
+
>>> summer1 = DateTime(2000, 7, 1, 12, tzinfo=Timezone('US/Eastern'))
|
|
57
|
+
>>> summer2 = DateTime(2000, 7, 1, 12, tzinfo=Timezone('America/New_York'))
|
|
58
|
+
|
|
59
|
+
>>> winter = [winter1, winter2,
|
|
60
|
+
... winter1.astimezone(Timezone('America/New_York')),
|
|
61
|
+
... winter2.astimezone(Timezone('US/Eastern')),
|
|
62
|
+
... ]
|
|
63
|
+
>>> assert all(x==winter[0] for x in winter)
|
|
64
|
+
|
|
65
|
+
>>> summer = [summer1, summer2,
|
|
66
|
+
... summer1.astimezone(Timezone('America/New_York')),
|
|
67
|
+
... summer2.astimezone(Timezone('US/Eastern')),
|
|
68
|
+
... ]
|
|
69
|
+
>>> assert all(x==summer[0] for x in summer)
|
|
70
|
+
|
|
53
71
|
"""
|
|
54
72
|
return _pendulum.tz.Timezone(name)
|
|
55
73
|
|
|
@@ -59,16 +77,7 @@ GMT = Timezone('GMT')
|
|
|
59
77
|
EST = Timezone('US/Eastern')
|
|
60
78
|
LCL = _pendulum.tz.Timezone(_pendulum.tz.get_local_timezone().name)
|
|
61
79
|
|
|
62
|
-
|
|
63
|
-
class WeekDay(IntEnum):
|
|
64
|
-
MONDAY = 0
|
|
65
|
-
TUESDAY = 1
|
|
66
|
-
WEDNESDAY = 2
|
|
67
|
-
THURSDAY = 3
|
|
68
|
-
FRIDAY = 4
|
|
69
|
-
SATURDAY = 5
|
|
70
|
-
SUNDAY = 6
|
|
71
|
-
|
|
80
|
+
WeekDay = _pendulum.day.WeekDay
|
|
72
81
|
|
|
73
82
|
WEEKDAY_SHORTNAME = {
|
|
74
83
|
'MO': WeekDay.MONDAY,
|
|
@@ -281,7 +290,7 @@ class NYSE(Entity):
|
|
|
281
290
|
if begdate <= d <= enddate}
|
|
282
291
|
|
|
283
292
|
|
|
284
|
-
class
|
|
293
|
+
class DateBusinessMixin:
|
|
285
294
|
|
|
286
295
|
_entity: type[NYSE] = NYSE
|
|
287
296
|
_business: bool = False
|
|
@@ -509,7 +518,141 @@ class PendulumBusinessDateMixin:
|
|
|
509
518
|
return self
|
|
510
519
|
|
|
511
520
|
|
|
512
|
-
class
|
|
521
|
+
class DateExtrasMixin:
|
|
522
|
+
"""Legacy support functionality well outside the
|
|
523
|
+
scope of Pendulum. Ideally these should be removed.
|
|
524
|
+
|
|
525
|
+
See how pendulum does end_of and next_ with getattr
|
|
526
|
+
|
|
527
|
+
Create a nearest [start_of, end_of] [week, day, month, quarter, year]
|
|
528
|
+
|
|
529
|
+
combo that accounts for whatever prefix and unit is passed in
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
def nearest_start_of_month(self):
|
|
533
|
+
"""Get `nearest` start of month
|
|
534
|
+
|
|
535
|
+
1/1/2015 -> Thursday (New Year's Day)
|
|
536
|
+
2/1/2015 -> Sunday
|
|
537
|
+
|
|
538
|
+
>>> from date import Date
|
|
539
|
+
>>> Date(2015, 1, 1).nearest_start_of_month()
|
|
540
|
+
Date(2015, 1, 1)
|
|
541
|
+
>>> Date(2015, 1, 15).nearest_start_of_month()
|
|
542
|
+
Date(2015, 1, 1)
|
|
543
|
+
>>> Date(2015, 1, 15).b.nearest_start_of_month()
|
|
544
|
+
Date(2015, 1, 2)
|
|
545
|
+
>>> Date(2015, 1, 16).nearest_start_of_month()
|
|
546
|
+
Date(2015, 2, 1)
|
|
547
|
+
>>> Date(2015, 1, 31).nearest_start_of_month()
|
|
548
|
+
Date(2015, 2, 1)
|
|
549
|
+
>>> Date(2015, 1, 31).b.nearest_start_of_month()
|
|
550
|
+
Date(2015, 2, 2)
|
|
551
|
+
"""
|
|
552
|
+
_business = self._business
|
|
553
|
+
self._business = False
|
|
554
|
+
if self.day > 15:
|
|
555
|
+
d = self.end_of('month')
|
|
556
|
+
if _business:
|
|
557
|
+
return d.business().add(days=1)
|
|
558
|
+
return d.add(days=1)
|
|
559
|
+
d = self.start_of('month')
|
|
560
|
+
if _business:
|
|
561
|
+
return d.business().add(days=1)
|
|
562
|
+
return d
|
|
563
|
+
|
|
564
|
+
def nearest_end_of_month(self):
|
|
565
|
+
"""Get `nearest` end of month
|
|
566
|
+
|
|
567
|
+
12/31/2014 -> Wednesday
|
|
568
|
+
1/31/2015 -> Saturday
|
|
569
|
+
|
|
570
|
+
>>> from date import Date
|
|
571
|
+
>>> Date(2015, 1, 1).nearest_end_of_month()
|
|
572
|
+
Date(2014, 12, 31)
|
|
573
|
+
>>> Date(2015, 1, 15).nearest_end_of_month()
|
|
574
|
+
Date(2014, 12, 31)
|
|
575
|
+
>>> Date(2015, 1, 15).b.nearest_end_of_month()
|
|
576
|
+
Date(2014, 12, 31)
|
|
577
|
+
>>> Date(2015, 1, 16).nearest_end_of_month()
|
|
578
|
+
Date(2015, 1, 31)
|
|
579
|
+
>>> Date(2015, 1, 31).nearest_end_of_month()
|
|
580
|
+
Date(2015, 1, 31)
|
|
581
|
+
>>> Date(2015, 1, 31).b.nearest_end_of_month()
|
|
582
|
+
Date(2015, 1, 30)
|
|
583
|
+
"""
|
|
584
|
+
_business = self._business
|
|
585
|
+
self._business = False
|
|
586
|
+
if self.day <= 15:
|
|
587
|
+
d = self.start_of('month')
|
|
588
|
+
if _business:
|
|
589
|
+
return d.business().subtract(days=1)
|
|
590
|
+
return d.subtract(days=1)
|
|
591
|
+
d = self.end_of('month')
|
|
592
|
+
if _business:
|
|
593
|
+
return d.business().subtract(days=1)
|
|
594
|
+
return d
|
|
595
|
+
|
|
596
|
+
def next_relative_date_of_week_by_day(self, day='MO'):
|
|
597
|
+
"""Get next relative day of week by relativedelta code
|
|
598
|
+
|
|
599
|
+
>>> from date import Date
|
|
600
|
+
>>> Date(2020, 5, 18).next_relative_date_of_week_by_day('SU')
|
|
601
|
+
Date(2020, 5, 24)
|
|
602
|
+
>>> Date(2020, 5, 24).next_relative_date_of_week_by_day('SU')
|
|
603
|
+
Date(2020, 5, 24)
|
|
604
|
+
"""
|
|
605
|
+
if self.weekday() == WEEKDAY_SHORTNAME.get(day):
|
|
606
|
+
return self
|
|
607
|
+
return self.next(WEEKDAY_SHORTNAME.get(day))
|
|
608
|
+
|
|
609
|
+
def weekday_or_previous_friday(self):
|
|
610
|
+
"""Return the date if it is a weekday, else previous Friday
|
|
611
|
+
|
|
612
|
+
>>> from date import Date
|
|
613
|
+
>>> Date(2019, 10, 6).weekday_or_previous_friday() # Sunday
|
|
614
|
+
Date(2019, 10, 4)
|
|
615
|
+
>>> Date(2019, 10, 5).weekday_or_previous_friday() # Saturday
|
|
616
|
+
Date(2019, 10, 4)
|
|
617
|
+
>>> Date(2019, 10, 4).weekday_or_previous_friday() # Friday
|
|
618
|
+
Date(2019, 10, 4)
|
|
619
|
+
>>> Date(2019, 10, 3).weekday_or_previous_friday() # Thursday
|
|
620
|
+
Date(2019, 10, 3)
|
|
621
|
+
"""
|
|
622
|
+
dnum = self.weekday()
|
|
623
|
+
if dnum in {WeekDay.SATURDAY, WeekDay.SUNDAY}:
|
|
624
|
+
return self.subtract(days=dnum - 4)
|
|
625
|
+
return self
|
|
626
|
+
|
|
627
|
+
"""
|
|
628
|
+
create a simple nth weekday function that accounts for
|
|
629
|
+
[1,2,3,4] and weekday as options
|
|
630
|
+
or weekday, [1,2,3,4]
|
|
631
|
+
|
|
632
|
+
"""
|
|
633
|
+
|
|
634
|
+
@classmethod
|
|
635
|
+
def third_wednesday(cls, year, month):
|
|
636
|
+
"""Third Wednesday date of a given month/year
|
|
637
|
+
|
|
638
|
+
>>> from date import Date
|
|
639
|
+
>>> Date.third_wednesday(2022, 6)
|
|
640
|
+
Date(2022, 6, 15)
|
|
641
|
+
>>> Date.third_wednesday(2023, 3)
|
|
642
|
+
Date(2023, 3, 15)
|
|
643
|
+
>>> Date.third_wednesday(2022, 12)
|
|
644
|
+
Date(2022, 12, 21)
|
|
645
|
+
>>> Date.third_wednesday(2023, 6)
|
|
646
|
+
Date(2023, 6, 21)
|
|
647
|
+
"""
|
|
648
|
+
third = cls(year, month, 15) # lowest 3rd day
|
|
649
|
+
w = third.weekday()
|
|
650
|
+
if w != WeekDay.WEDNESDAY:
|
|
651
|
+
third = third.replace(day=(15 + (WeekDay.WEDNESDAY - w) % 7))
|
|
652
|
+
return third
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
class Date(DateExtrasMixin, DateBusinessMixin, _pendulum.Date):
|
|
513
656
|
"""Inherits and wraps pendulum.Date
|
|
514
657
|
"""
|
|
515
658
|
|
|
@@ -768,105 +911,6 @@ class Date(PendulumBusinessDateMixin, _pendulum.Date):
|
|
|
768
911
|
with contextlib.suppress(Exception):
|
|
769
912
|
return self.isocalendar()[1]
|
|
770
913
|
|
|
771
|
-
"""
|
|
772
|
-
See how pendulum does end_of and next_ with getattr
|
|
773
|
-
|
|
774
|
-
Create a nearest [start_of, end_of] [week, day, month, quarter, year]
|
|
775
|
-
|
|
776
|
-
combo that accounts for whatever prefix and unit is passed in
|
|
777
|
-
"""
|
|
778
|
-
|
|
779
|
-
def nearest_start_of_month(self):
|
|
780
|
-
"""Get `nearest` start of month
|
|
781
|
-
|
|
782
|
-
1/1/2015 -> Thursday (New Year's Day)
|
|
783
|
-
2/1/2015 -> Sunday
|
|
784
|
-
|
|
785
|
-
>>> Date(2015, 1, 1).nearest_start_of_month()
|
|
786
|
-
Date(2015, 1, 1)
|
|
787
|
-
>>> Date(2015, 1, 15).nearest_start_of_month()
|
|
788
|
-
Date(2015, 1, 1)
|
|
789
|
-
>>> Date(2015, 1, 15).b.nearest_start_of_month()
|
|
790
|
-
Date(2015, 1, 2)
|
|
791
|
-
>>> Date(2015, 1, 16).nearest_start_of_month()
|
|
792
|
-
Date(2015, 2, 1)
|
|
793
|
-
>>> Date(2015, 1, 31).nearest_start_of_month()
|
|
794
|
-
Date(2015, 2, 1)
|
|
795
|
-
>>> Date(2015, 1, 31).b.nearest_start_of_month()
|
|
796
|
-
Date(2015, 2, 2)
|
|
797
|
-
"""
|
|
798
|
-
_business = self._business
|
|
799
|
-
self._business = False
|
|
800
|
-
if self.day > 15:
|
|
801
|
-
d = self.end_of('month')
|
|
802
|
-
if _business:
|
|
803
|
-
return d.business().add(days=1)
|
|
804
|
-
return d.add(days=1)
|
|
805
|
-
d = self.start_of('month')
|
|
806
|
-
if _business:
|
|
807
|
-
return d.business().add(days=1)
|
|
808
|
-
return d
|
|
809
|
-
|
|
810
|
-
def nearest_end_of_month(self):
|
|
811
|
-
"""Get `nearest` end of month
|
|
812
|
-
|
|
813
|
-
12/31/2014 -> Wednesday
|
|
814
|
-
1/31/2015 -> Saturday
|
|
815
|
-
|
|
816
|
-
>>> Date(2015, 1, 1).nearest_end_of_month()
|
|
817
|
-
Date(2014, 12, 31)
|
|
818
|
-
>>> Date(2015, 1, 15).nearest_end_of_month()
|
|
819
|
-
Date(2014, 12, 31)
|
|
820
|
-
>>> Date(2015, 1, 15).b.nearest_end_of_month()
|
|
821
|
-
Date(2014, 12, 31)
|
|
822
|
-
>>> Date(2015, 1, 16).nearest_end_of_month()
|
|
823
|
-
Date(2015, 1, 31)
|
|
824
|
-
>>> Date(2015, 1, 31).nearest_end_of_month()
|
|
825
|
-
Date(2015, 1, 31)
|
|
826
|
-
>>> Date(2015, 1, 31).b.nearest_end_of_month()
|
|
827
|
-
Date(2015, 1, 30)
|
|
828
|
-
"""
|
|
829
|
-
_business = self._business
|
|
830
|
-
self._business = False
|
|
831
|
-
if self.day <= 15:
|
|
832
|
-
d = self.start_of('month')
|
|
833
|
-
if _business:
|
|
834
|
-
return d.business().subtract(days=1)
|
|
835
|
-
return d.subtract(days=1)
|
|
836
|
-
d = self.end_of('month')
|
|
837
|
-
if _business:
|
|
838
|
-
return d.business().subtract(days=1)
|
|
839
|
-
return d
|
|
840
|
-
|
|
841
|
-
def next_relative_date_of_week_by_day(self, day='MO'):
|
|
842
|
-
"""Get next relative day of week by relativedelta code
|
|
843
|
-
|
|
844
|
-
>>> Date(2020, 5, 18).next_relative_date_of_week_by_day('SU')
|
|
845
|
-
Date(2020, 5, 24)
|
|
846
|
-
>>> Date(2020, 5, 24).next_relative_date_of_week_by_day('SU')
|
|
847
|
-
Date(2020, 5, 24)
|
|
848
|
-
"""
|
|
849
|
-
if self.weekday() == WEEKDAY_SHORTNAME.get(day):
|
|
850
|
-
return self
|
|
851
|
-
return self.next(WEEKDAY_SHORTNAME.get(day))
|
|
852
|
-
|
|
853
|
-
def weekday_or_previous_friday(self):
|
|
854
|
-
"""Return the date if it is a weekday, else previous Friday
|
|
855
|
-
|
|
856
|
-
>>> Date(2019, 10, 6).weekday_or_previous_friday() # Sunday
|
|
857
|
-
Date(2019, 10, 4)
|
|
858
|
-
>>> Date(2019, 10, 5).weekday_or_previous_friday() # Saturday
|
|
859
|
-
Date(2019, 10, 4)
|
|
860
|
-
>>> Date(2019, 10, 4).weekday_or_previous_friday() # Friday
|
|
861
|
-
Date(2019, 10, 4)
|
|
862
|
-
>>> Date(2019, 10, 3).weekday_or_previous_friday() # Thursday
|
|
863
|
-
Date(2019, 10, 3)
|
|
864
|
-
"""
|
|
865
|
-
dnum = self.weekday()
|
|
866
|
-
if dnum in {WeekDay.SATURDAY, WeekDay.SUNDAY}:
|
|
867
|
-
return self.subtract(days=dnum - 4)
|
|
868
|
-
return self
|
|
869
|
-
|
|
870
914
|
def lookback(self, unit='last') -> Self:
|
|
871
915
|
"""Date back based on lookback string, ie last, week, month.
|
|
872
916
|
|
|
@@ -897,32 +941,6 @@ class Date(PendulumBusinessDateMixin, _pendulum.Date):
|
|
|
897
941
|
'year': _lookback(years=1),
|
|
898
942
|
}.get(unit)
|
|
899
943
|
|
|
900
|
-
"""
|
|
901
|
-
create a simple nth weekday function that accounts for
|
|
902
|
-
[1,2,3,4] and weekday as options
|
|
903
|
-
or weekday, [1,2,3,4]
|
|
904
|
-
|
|
905
|
-
"""
|
|
906
|
-
|
|
907
|
-
@staticmethod
|
|
908
|
-
def third_wednesday(year, month):
|
|
909
|
-
"""Third Wednesday date of a given month/year
|
|
910
|
-
|
|
911
|
-
>>> Date.third_wednesday(2022, 6)
|
|
912
|
-
Date(2022, 6, 15)
|
|
913
|
-
>>> Date.third_wednesday(2023, 3)
|
|
914
|
-
Date(2023, 3, 15)
|
|
915
|
-
>>> Date.third_wednesday(2022, 12)
|
|
916
|
-
Date(2022, 12, 21)
|
|
917
|
-
>>> Date.third_wednesday(2023, 6)
|
|
918
|
-
Date(2023, 6, 21)
|
|
919
|
-
"""
|
|
920
|
-
third = Date(year, month, 15) # lowest 3rd day
|
|
921
|
-
w = third.weekday()
|
|
922
|
-
if w != WeekDay.WEDNESDAY:
|
|
923
|
-
third = third.replace(day=(15 + (WeekDay.WEDNESDAY - w) % 7))
|
|
924
|
-
return third
|
|
925
|
-
|
|
926
944
|
|
|
927
945
|
class Time(_pendulum.Time):
|
|
928
946
|
|
|
@@ -1050,7 +1068,7 @@ class Time(_pendulum.Time):
|
|
|
1050
1068
|
tzinfo=obj.tzinfo or tz)
|
|
1051
1069
|
|
|
1052
1070
|
|
|
1053
|
-
class DateTime(
|
|
1071
|
+
class DateTime(DateBusinessMixin, _pendulum.DateTime):
|
|
1054
1072
|
"""Inherits and wraps pendulum.DateTime
|
|
1055
1073
|
"""
|
|
1056
1074
|
|
|
@@ -1118,7 +1136,11 @@ class DateTime(PendulumBusinessDateMixin, _pendulum.DateTime):
|
|
|
1118
1136
|
return Time.instance(self)
|
|
1119
1137
|
|
|
1120
1138
|
@classmethod
|
|
1121
|
-
def parse(
|
|
1139
|
+
def parse(
|
|
1140
|
+
cls, s: str | int | None,
|
|
1141
|
+
entity: Entity = NYSE,
|
|
1142
|
+
raise_err: bool = False
|
|
1143
|
+
) -> Self | None:
|
|
1122
1144
|
"""Thin layer on Date parser and our custom `Date.parse``
|
|
1123
1145
|
|
|
1124
1146
|
>>> DateTime.parse('2022/1/1')
|
|
@@ -1176,7 +1198,7 @@ class DateTime(PendulumBusinessDateMixin, _pendulum.DateTime):
|
|
|
1176
1198
|
if d is not None and t is not None:
|
|
1177
1199
|
return DateTime.combine(d, t, LCL)
|
|
1178
1200
|
|
|
1179
|
-
d = Date.parse(s)
|
|
1201
|
+
d = Date.parse(s, entity=entity)
|
|
1180
1202
|
if d is not None:
|
|
1181
1203
|
return cls(d.year, d.month, d.day, 0, 0, 0)
|
|
1182
1204
|
|
|
@@ -1613,47 +1635,6 @@ class Interval:
|
|
|
1613
1635
|
raise ValueError('Basis range [0, 4]. Unknown basis {basis}.')
|
|
1614
1636
|
|
|
1615
1637
|
|
|
1616
|
-
Range = namedtuple('Range', ['start', 'end'])
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
def overlap_days(range_one, range_two, days=False):
|
|
1620
|
-
"""Test by how much two date ranges overlap
|
|
1621
|
-
if `days=True`, we return an actual day count,
|
|
1622
|
-
otherwise we just return if it overlaps True/False
|
|
1623
|
-
poached from Raymond Hettinger http://stackoverflow.com/a/9044111
|
|
1624
|
-
|
|
1625
|
-
>>> date1 = Date(2016, 3, 1)
|
|
1626
|
-
>>> date2 = Date(2016, 3, 2)
|
|
1627
|
-
>>> date3 = Date(2016, 3, 29)
|
|
1628
|
-
>>> date4 = Date(2016, 3, 30)
|
|
1629
|
-
|
|
1630
|
-
>>> assert overlap_days((date1, date3), (date2, date4))
|
|
1631
|
-
>>> assert overlap_days((date2, date4), (date1, date3))
|
|
1632
|
-
>>> assert not overlap_days((date1, date2), (date3, date4))
|
|
1633
|
-
|
|
1634
|
-
>>> assert overlap_days((date1, date4), (date1, date4))
|
|
1635
|
-
>>> assert overlap_days((date1, date4), (date2, date3))
|
|
1636
|
-
>>> overlap_days((date1, date4), (date1, date4), True)
|
|
1637
|
-
30
|
|
1638
|
-
|
|
1639
|
-
>>> assert overlap_days((date2, date3), (date1, date4))
|
|
1640
|
-
>>> overlap_days((date2, date3), (date1, date4), True)
|
|
1641
|
-
28
|
|
1642
|
-
|
|
1643
|
-
>>> assert not overlap_days((date3, date4), (date1, date2))
|
|
1644
|
-
>>> overlap_days((date3, date4), (date1, date2), True)
|
|
1645
|
-
-26
|
|
1646
|
-
"""
|
|
1647
|
-
r1 = Range(*range_one)
|
|
1648
|
-
r2 = Range(*range_two)
|
|
1649
|
-
latest_start = max(r1.start, r2.start)
|
|
1650
|
-
earliest_end = min(r1.end, r2.end)
|
|
1651
|
-
overlap = (earliest_end - latest_start).days + 1
|
|
1652
|
-
if days:
|
|
1653
|
-
return overlap
|
|
1654
|
-
return overlap >= 0
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
1638
|
def create_ics(begdate, enddate, summary, location):
|
|
1658
1639
|
"""Create a simple .ics file per RFC 5545 guidelines."""
|
|
1659
1640
|
|
date/extras.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
|
|
3
|
+
from date import NYSE, DateTime, Entity
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
'is_within_business_hours',
|
|
7
|
+
'is_business_day',
|
|
8
|
+
'overlap_days',
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_within_business_hours(entity: Entity = NYSE) -> bool:
|
|
13
|
+
"""Return whether the current native datetime is between
|
|
14
|
+
open and close of business hours.
|
|
15
|
+
|
|
16
|
+
>>> from unittest.mock import patch
|
|
17
|
+
>>> tz = NYSE.tz
|
|
18
|
+
|
|
19
|
+
>>> with patch('date.DateTime.now') as mock:
|
|
20
|
+
... mock.return_value = DateTime(2000, 5, 1, 12, 30, 0, 0, tzinfo=tz)
|
|
21
|
+
... is_within_business_hours()
|
|
22
|
+
True
|
|
23
|
+
|
|
24
|
+
>>> with patch('date.DateTime.now') as mock:
|
|
25
|
+
... mock.return_value = DateTime(2000, 7, 2, 12, 15, 0, 0, tzinfo=tz) # Sunday
|
|
26
|
+
... is_within_business_hours()
|
|
27
|
+
False
|
|
28
|
+
|
|
29
|
+
>>> with patch('date.DateTime.now') as mock:
|
|
30
|
+
... mock.return_value = DateTime(2000, 11, 1, 1, 15, 0, 0, tzinfo=tz)
|
|
31
|
+
... is_within_business_hours()
|
|
32
|
+
False
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
this = DateTime.now()
|
|
36
|
+
this_entity = DateTime.now(tz=entity.tz).entity(entity)
|
|
37
|
+
bounds = this_entity.business_hours()
|
|
38
|
+
return this_entity.business_open() and (bounds[0] <= this.astimezone(entity.tz) <= bounds[1])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_business_day(entity: Entity = NYSE) -> bool:
|
|
42
|
+
"""Return whether the current native datetime is a business day.
|
|
43
|
+
"""
|
|
44
|
+
return DateTime.now(tz=entity.tz).entity(entity).is_business_day()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
Range = namedtuple('Range', ['start', 'end'])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def overlap_days(range_one, range_two, days=False):
|
|
51
|
+
"""Test by how much two date ranges overlap
|
|
52
|
+
if `days=True`, we return an actual day count,
|
|
53
|
+
otherwise we just return if it overlaps True/False
|
|
54
|
+
poached from Raymond Hettinger http://stackoverflow.com/a/9044111
|
|
55
|
+
|
|
56
|
+
>>> from date import Date
|
|
57
|
+
>>> date1 = Date(2016, 3, 1)
|
|
58
|
+
>>> date2 = Date(2016, 3, 2)
|
|
59
|
+
>>> date3 = Date(2016, 3, 29)
|
|
60
|
+
>>> date4 = Date(2016, 3, 30)
|
|
61
|
+
|
|
62
|
+
>>> assert overlap_days((date1, date3), (date2, date4))
|
|
63
|
+
>>> assert overlap_days((date2, date4), (date1, date3))
|
|
64
|
+
>>> assert not overlap_days((date1, date2), (date3, date4))
|
|
65
|
+
|
|
66
|
+
>>> assert overlap_days((date1, date4), (date1, date4))
|
|
67
|
+
>>> assert overlap_days((date1, date4), (date2, date3))
|
|
68
|
+
>>> overlap_days((date1, date4), (date1, date4), True)
|
|
69
|
+
30
|
|
70
|
+
|
|
71
|
+
>>> assert overlap_days((date2, date3), (date1, date4))
|
|
72
|
+
>>> overlap_days((date2, date3), (date1, date4), True)
|
|
73
|
+
28
|
|
74
|
+
|
|
75
|
+
>>> assert not overlap_days((date3, date4), (date1, date2))
|
|
76
|
+
>>> overlap_days((date3, date4), (date1, date2), True)
|
|
77
|
+
-26
|
|
78
|
+
"""
|
|
79
|
+
r1 = Range(*range_one)
|
|
80
|
+
r2 = Range(*range_two)
|
|
81
|
+
latest_start = max(r1.start, r2.start)
|
|
82
|
+
earliest_end = min(r1.end, r2.end)
|
|
83
|
+
overlap = (earliest_end - latest_start).days + 1
|
|
84
|
+
if days:
|
|
85
|
+
return overlap
|
|
86
|
+
return overlap >= 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == '__main__':
|
|
90
|
+
__import__('doctest').testmod(optionflags=4 | 8 | 32)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: opendate
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary:
|
|
5
5
|
Home-page: https://github.com/bissli/opendate
|
|
6
6
|
License: MIT
|
|
@@ -49,7 +49,7 @@ thedate.business().add(days=5) # add 5 business day
|
|
|
49
49
|
|
|
50
50
|
# subtract days
|
|
51
51
|
thedate.subtract(days=5)
|
|
52
|
-
thedate.business().
|
|
52
|
+
thedate.business().subtract(days=5) # subtract 5 business day
|
|
53
53
|
|
|
54
54
|
# start of month
|
|
55
55
|
thedate.start_of('month')
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
date/__init__.py,sha256=eB3ZluWh9M_QVfMOpmjeigLItgUl7mywdby88ffQfbE,2747
|
|
2
|
+
date/date.py,sha256=Z-8-5T8TfLtgiGe0ahK7QIgXjbBEDmrGRw8zEhiQD2Y,54819
|
|
3
|
+
date/extras.py,sha256=7xsOsdhKrmGoyLl5W4Xhg9TfuytaaIH7uKWW9PvR5sE,2832
|
|
4
|
+
opendate-0.1.4.dist-info/LICENSE,sha256=V4Rx8WWy7v8Fim6PHcEBszpZkDLbCHeorz1e_gr0Cbk,1111
|
|
5
|
+
opendate-0.1.4.dist-info/METADATA,sha256=up550EjO3j1IFI7F_iUrlugXe6rkcfpQpeRCQrEqgN8,1816
|
|
6
|
+
opendate-0.1.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
7
|
+
opendate-0.1.4.dist-info/RECORD,,
|
date/business.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from date import NYSE, DateTime, Entity
|
|
2
|
-
|
|
3
|
-
__all__ = ['is_within_business_hours', 'is_business_day']
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def is_within_business_hours(entity: Entity = NYSE) -> bool:
|
|
7
|
-
"""Return whether the current native datetime is between
|
|
8
|
-
open and close of business hours.
|
|
9
|
-
|
|
10
|
-
>>> from unittest.mock import patch
|
|
11
|
-
>>> tz = NYSE.tz
|
|
12
|
-
|
|
13
|
-
>>> with patch('date.DateTime.now') as mock:
|
|
14
|
-
... mock.return_value = DateTime(2000, 5, 1, 12, 30, 0, 0, tzinfo=tz)
|
|
15
|
-
... is_within_business_hours()
|
|
16
|
-
True
|
|
17
|
-
|
|
18
|
-
>>> with patch('date.DateTime.now') as mock:
|
|
19
|
-
... mock.return_value = DateTime(2000, 7, 2, 12, 15, 0, 0, tzinfo=tz) # Sunday
|
|
20
|
-
... is_within_business_hours()
|
|
21
|
-
False
|
|
22
|
-
|
|
23
|
-
>>> with patch('date.DateTime.now') as mock:
|
|
24
|
-
... mock.return_value = DateTime(2000, 11, 1, 1, 15, 0, 0, tzinfo=tz)
|
|
25
|
-
... is_within_business_hours()
|
|
26
|
-
False
|
|
27
|
-
|
|
28
|
-
"""
|
|
29
|
-
this = DateTime.now()
|
|
30
|
-
this_entity = DateTime.now(tz=entity.tz).entity(entity)
|
|
31
|
-
bounds = this_entity.business_hours()
|
|
32
|
-
return this_entity.business_open() and (bounds[0] <= this.astimezone(entity.tz) <= bounds[1])
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def is_business_day(entity: Entity = NYSE) -> bool:
|
|
36
|
-
"""Return whether the current native datetime is a business day.
|
|
37
|
-
"""
|
|
38
|
-
return DateTime.now(tz=entity.tz).entity(entity).is_business_day()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if __name__ == '__main__':
|
|
42
|
-
__import__('doctest').testmod(optionflags=4 | 8 | 32)
|
opendate-0.1.2.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
date/__init__.py,sha256=KRdrThrkKiQrf8b2VqAHx78hPCT1p0Tn0251k9ERFiE,2552
|
|
2
|
-
date/business.py,sha256=g4dHpmEaUuADgMxIcTG96AEviaRo9NvGDJ8IPKXD7xQ,1386
|
|
3
|
-
date/date.py,sha256=DrjvL_c3hYSHXaVVkccOpEyr7nDZvc02XUfSP8n_tNI,55210
|
|
4
|
-
opendate-0.1.2.dist-info/LICENSE,sha256=V4Rx8WWy7v8Fim6PHcEBszpZkDLbCHeorz1e_gr0Cbk,1111
|
|
5
|
-
opendate-0.1.2.dist-info/METADATA,sha256=qFurRfzxKJl1bx36as2o8KNXGufuQPYIhobsQOyFaDA,1815
|
|
6
|
-
opendate-0.1.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
7
|
-
opendate-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|