opendate 0.1.19__tar.gz → 0.1.21__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opendate
3
- Version: 0.1.19
3
+ Version: 0.1.21
4
4
  Summary: Python business datetimes
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "opendate"
3
- version = "0.1.19"
3
+ version = "0.1.21"
4
4
  description = "Python business datetimes"
5
5
  authors = ["bissli <bissli.xyz@protonmail.com>"]
6
6
  readme = "README.md"
@@ -1,4 +1,4 @@
1
- __version__ = '0.1.19'
1
+ __version__ = '0.1.21'
2
2
 
3
3
  import datetime as _datetime
4
4
 
@@ -26,11 +26,13 @@ from date.date import WEEKDAY_SHORTNAME
26
26
  from date.date import expect_date
27
27
  from date.date import expect_datetime
28
28
  from date.date import expect_time
29
+ from date.date import expect_date_or_datetime
29
30
  from date.date import expect_native_timezone
30
31
  from date.date import expect_utc_timezone
31
32
  from date.date import prefer_native_timezone
32
33
  from date.date import prefer_utc_timezone
33
34
  from date.date import Timezone
35
+ from date.extras import create_ics
34
36
  from date.extras import is_business_day
35
37
  from date.extras import is_within_business_hours
36
38
  from date.extras import overlap_days
@@ -128,6 +130,7 @@ __all__ = [
128
130
  'expect_date',
129
131
  'expect_datetime',
130
132
  'expect_time',
133
+ 'expect_date_or_datetime',
131
134
  'expect_native_timezone',
132
135
  'expect_utc_timezone',
133
136
  'instance',
@@ -151,4 +154,5 @@ __all__ = [
151
154
  'GMT',
152
155
  'UTC',
153
156
  'WEEKDAY_SHORTNAME',
157
+ 'create_ics',
154
158
  ]
@@ -40,6 +40,7 @@ __all__ = [
40
40
  'expect_date',
41
41
  'expect_datetime',
42
42
  'expect_time',
43
+ 'expect_date_or_datetime',
43
44
  'Entity',
44
45
  'NYSE',
45
46
  'WEEKDAY_SHORTNAME',
@@ -113,17 +114,34 @@ def isdateish(x) -> bool:
113
114
 
114
115
 
115
116
  def parse_arg(typ, arg):
116
- if isdateish(arg):
117
- if typ == _datetime.datetime:
117
+ """Parse argument to specified type or 'smart' to preserve Date/DateTime.
118
+ """
119
+ if not isdateish(arg):
120
+ return arg
121
+
122
+ if typ == 'smart':
123
+ if isinstance(arg, Date | DateTime):
124
+ return arg
125
+ if isinstance(arg, _datetime.datetime | pd.Timestamp | np.datetime64):
118
126
  return DateTime.instance(arg)
119
- if typ == _datetime.date:
127
+ if isinstance(arg, _datetime.date):
120
128
  return Date.instance(arg)
121
- if typ == _datetime.time:
129
+ if isinstance(arg, _datetime.time):
122
130
  return Time.instance(arg)
131
+ return arg
132
+
133
+ if typ == _datetime.datetime:
134
+ return DateTime.instance(arg)
135
+ if typ == _datetime.date:
136
+ return Date.instance(arg)
137
+ if typ == _datetime.time:
138
+ return Time.instance(arg)
123
139
  return arg
124
140
 
125
141
 
126
142
  def parse_args(typ, *args):
143
+ """Parse args to specified type or 'smart' mode.
144
+ """
127
145
  this = []
128
146
  for a in args:
129
147
  if isinstance(a, Sequence) and not isinstance(a, str):
@@ -133,30 +151,31 @@ def parse_args(typ, *args):
133
151
  return this
134
152
 
135
153
 
136
- def expect(func, typ: type[_datetime.date], exclkw: bool = False) -> Callable:
137
- """Decorator to force input type of date/datetime inputs
154
+ def expect(func=None, *, typ: type[_datetime.date] | str = None, exclkw: bool = False) -> Callable:
155
+ """Decorator to force input type of date/datetime inputs.
156
+
157
+ typ can be _datetime.date, _datetime.datetime, _datetime.time, or 'smart'
138
158
  """
139
- @wraps(func)
140
- def wrapper(*args, **kwargs):
141
- args = parse_args(typ, *args)
142
- if not exclkw:
143
- for k, v in kwargs.items():
144
- if isdateish(v):
145
- if typ == _datetime.datetime:
146
- kwargs[k] = DateTime.instance(v)
147
- continue
148
- if typ == _datetime.date:
149
- kwargs[k] = Date.instance(v)
150
- continue
151
- if typ == _datetime.time:
152
- kwargs[k] = Time.instance(v)
153
- return func(*args, **kwargs)
154
- return wrapper
159
+ def decorator(func):
160
+ @wraps(func)
161
+ def wrapper(*args, **kwargs):
162
+ args = parse_args(typ, *args)
163
+ if not exclkw:
164
+ for k, v in kwargs.items():
165
+ if isdateish(v):
166
+ kwargs[k] = parse_arg(typ, v)
167
+ return func(*args, **kwargs)
168
+ return wrapper
169
+
170
+ if func is None:
171
+ return decorator
172
+ return decorator(func)
155
173
 
156
174
 
157
175
  expect_date = partial(expect, typ=_datetime.date)
158
176
  expect_datetime = partial(expect, typ=_datetime.datetime)
159
177
  expect_time = partial(expect, typ=_datetime.time)
178
+ expect_date_or_datetime = partial(expect, typ='smart')
160
179
 
161
180
 
162
181
  def type_class(typ, obj):
@@ -218,6 +237,34 @@ def reset_business(func):
218
237
  return wrapper
219
238
 
220
239
 
240
+ def normalize_date_datetime_pairs(func):
241
+ """Decorator to normalize mixed Date/DateTime pairs to DateTime.
242
+ """
243
+ @wraps(func)
244
+ def wrapper(*args, **kwargs):
245
+ if len(args) >= 3:
246
+ cls_or_self, begdate, enddate = args[0], args[1], args[2]
247
+ rest_args = args[3:]
248
+
249
+ tz = UTC
250
+ if isinstance(begdate, DateTime) and begdate.tzinfo:
251
+ tz = begdate.tzinfo
252
+ elif isinstance(enddate, DateTime) and enddate.tzinfo:
253
+ tz = enddate.tzinfo
254
+
255
+ if isinstance(begdate, Date) and not isinstance(begdate, DateTime):
256
+ if isinstance(enddate, DateTime):
257
+ begdate = DateTime(begdate.year, begdate.month, begdate.day, tzinfo=tz)
258
+ elif isinstance(enddate, Date) and not isinstance(enddate, DateTime):
259
+ if isinstance(begdate, DateTime):
260
+ enddate = DateTime(enddate.year, enddate.month, enddate.day, tzinfo=tz)
261
+
262
+ args = (cls_or_self, begdate, enddate) + rest_args
263
+
264
+ return func(*args, **kwargs)
265
+ return wrapper
266
+
267
+
221
268
  def prefer_utc_timezone(func, force:bool = False) -> Callable:
222
269
  """Return datetime as UTC.
223
270
  """
@@ -1436,11 +1483,15 @@ class Interval(_pendulum.Interval):
1436
1483
  _business: bool = False
1437
1484
  _entity: type[NYSE] = NYSE
1438
1485
 
1486
+ @expect_date_or_datetime
1487
+ @normalize_date_datetime_pairs
1439
1488
  def __new__(cls, begdate: Date | DateTime, enddate: Date | DateTime) -> Self:
1440
1489
  assert begdate and enddate, 'Interval dates cannot be None'
1441
1490
  instance = super().__new__(cls, begdate, enddate, False)
1442
1491
  return instance
1443
1492
 
1493
+ @expect_date_or_datetime
1494
+ @normalize_date_datetime_pairs
1444
1495
  def __init__(self, begdate: Date | DateTime, enddate: Date | DateTime) -> None:
1445
1496
  super().__init__(begdate, enddate, False)
1446
1497
  self._sign = 1 if begdate <= enddate else -1
@@ -1785,19 +1836,3 @@ class Interval(_pendulum.Interval):
1785
1836
  current = handlers['advance'](current)
1786
1837
 
1787
1838
  return result
1788
-
1789
-
1790
- def create_ics(begdate, enddate, summary, location) -> str:
1791
- """Create a simple .ics file per RFC 5545 guidelines."""
1792
-
1793
- return f"""BEGIN:VCALENDAR
1794
- VERSION:2.0
1795
- PRODID:-//hacksw/handcal//NONSGML v1.0//EN
1796
- BEGIN:VEVENT
1797
- DTSTART;TZID=America/New_York:{begdate:%Y%m%dT%H%M%S}
1798
- DTEND;TZID=America/New_York:{enddate:%Y%m%dT%H%M%S}
1799
- SUMMARY:{summary}
1800
- LOCATION:{location}
1801
- END:VEVENT
1802
- END:VCALENDAR
1803
- """
@@ -15,6 +15,7 @@ __all__ = [
15
15
  'is_within_business_hours',
16
16
  'is_business_day',
17
17
  'overlap_days',
18
+ 'create_ics',
18
19
  ]
19
20
 
20
21
 
@@ -56,3 +57,19 @@ def overlap_days(
56
57
  if days:
57
58
  return overlap
58
59
  return overlap >= 0
60
+
61
+
62
+ def create_ics(begdate: Date | DateTime, enddate: Date | DateTime, summary: str, location: str) -> str:
63
+ """Create a simple .ics file per RFC 5545 guidelines."""
64
+
65
+ return f"""BEGIN:VCALENDAR
66
+ VERSION:2.0
67
+ PRODID:-//hacksw/handcal//NONSGML v1.0//EN
68
+ BEGIN:VEVENT
69
+ DTSTART;TZID=America/New_York:{begdate:%Y%m%dT%H%M%S}
70
+ DTEND;TZID=America/New_York:{enddate:%Y%m%dT%H%M%S}
71
+ SUMMARY:{summary}
72
+ LOCATION:{location}
73
+ END:VEVENT
74
+ END:VCALENDAR
75
+ """
File without changes
File without changes