dateutilx 0.1.1__tar.gz → 0.2.0__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.
Files changed (30) hide show
  1. {dateutilx-0.1.1 → dateutilx-0.2.0}/.gitignore +1 -1
  2. {dateutilx-0.1.1 → dateutilx-0.2.0}/PKG-INFO +10 -27
  3. {dateutilx-0.1.1 → dateutilx-0.2.0}/README.md +7 -23
  4. {dateutilx-0.1.1 → dateutilx-0.2.0}/pyproject.toml +7 -7
  5. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/easter.py +2 -2
  6. dateutilx-0.2.0/src/helper/__init__.py +4 -0
  7. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/helper/constants.py +0 -5
  8. dateutilx-0.2.0/src/helper/funcs.py +5 -0
  9. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/parser/_parser.py +69 -71
  10. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/parser/isoparser.py +24 -29
  11. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/parser/utils.py +3 -4
  12. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/relativedelta.py +150 -244
  13. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/rrule.py +131 -152
  14. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/tz/_win.py +22 -21
  15. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/tz/tz.py +111 -110
  16. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/tz/win.py +1 -1
  17. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/tzwin.py +1 -1
  18. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/utils.py +5 -6
  19. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/weekday.py +6 -6
  20. dateutilx-0.1.1/src/dateutil/__init__.py +0 -5
  21. dateutilx-0.1.1/src/dateutilx/helper/__init__.py +0 -1
  22. {dateutilx-0.1.1 → dateutilx-0.2.0}/.base_project/LICENSE +0 -0
  23. {dateutilx-0.1.1 → dateutilx-0.2.0}/LICENSE +0 -0
  24. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/__init__.py +0 -0
  25. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/errors.py +0 -0
  26. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/info.py +0 -0
  27. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/parser/__init__.py +0 -0
  28. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/tz/__init__.py +0 -0
  29. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/tz/_common.py +0 -0
  30. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.2.0/src}/tz/_factories.py +0 -0
@@ -13,7 +13,7 @@ dist/
13
13
  .tox/
14
14
  .pytest_cache/
15
15
  venv/
16
- .venv/
16
+ .venv*/
17
17
  .hypothesis/
18
18
  .coverage
19
19
 
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dateutilx
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Modern extension to the standard Python datetime module
5
5
  Project-URL: Homepage, https://github.com/rsb-23/dateutilx
6
- Project-URL: Documentation, https://dateutil.readthedocs.io/en/stable/
6
+ Project-URL: Documentation, https://dateutilx.readthedocs.io/en/stable/
7
7
  Project-URL: Source, https://github.com/rsb-23/dateutilx
8
8
  Author: Rishabh B
9
9
  License: Copyright 2017- Paul Ganssle <paul@ganssle.io>
@@ -72,20 +72,19 @@ Classifier: License :: OSI Approved :: Apache Software License
72
72
  Classifier: License :: OSI Approved :: BSD License
73
73
  Classifier: Programming Language :: Python
74
74
  Classifier: Programming Language :: Python :: 3
75
- Classifier: Programming Language :: Python :: 3.10
76
75
  Classifier: Programming Language :: Python :: 3.11
77
76
  Classifier: Programming Language :: Python :: 3.12
78
77
  Classifier: Programming Language :: Python :: 3.13
79
78
  Classifier: Programming Language :: Python :: 3.14
80
79
  Classifier: Programming Language :: Python :: 3.15
81
80
  Classifier: Topic :: Software Development :: Libraries
82
- Requires-Python: >=3.10
81
+ Requires-Python: >=3.11
83
82
  Requires-Dist: tzdata; sys_platform == 'win32'
84
83
  Description-Content-Type: text/markdown
85
84
 
86
85
  > [!NOTE]
87
- > This package is rewrite of python-dateutil package with v2.9.0.post0 as base, to support active python versions only.
88
- > It is not related with the base project and its authors.
86
+ > This package is rewrite of `python-dateutil v2.9.0.post0` as base, to support active python versions only.
87
+ > It is not related with the base project and its authors.
89
88
  > Docs of base project are under `.base_project/`
90
89
 
91
90
  # dateutilx - powerful extensions to datetime
@@ -127,7 +126,7 @@ https://github.com/rsb-23/dateutilx/
127
126
  * Computing of dates based on very flexible recurrence rules, using a superset of
128
127
  the [iCalendar](https://www.ietf.org/rfc/rfc2445.txt) specification. Parsing of RFC strings is supported as well.
129
128
  * Generic parsing of dates in almost any string format;
130
- * Timezone (tzinfo) implementations for tzfile(5) format files (/etc/localtime, /usr/share/zoneinfo, etc), TZ
129
+ * Timezone (tzinfo) implementations for tzfile (5) format files (/etc/localtime, /usr/share/zoneinfo, etc), TZ
131
130
  environment string (in all known formats), iCalendar format files, given ranges (with help from relative deltas),
132
131
  local machine timezone, fixed offset timezone, UTC timezone, and Windows registry-based time zones.
133
132
  * Internal up-to-date world timezone information based on Olson's database.
@@ -136,13 +135,10 @@ https://github.com/rsb-23/dateutilx/
136
135
 
137
136
  ## Quick example
138
137
 
139
- Here's a snapshot, just to give an idea about the power of the
140
- package. For more examples, look at the documentation.
138
+ Here's a snapshot, just to give an idea about the power of the package. For more examples, look at the documentation.
141
139
 
142
- Suppose you want to know how much time is left, in
143
- years/months/days/etc, before the next easter happening on a
144
- year with a Friday 13th in August, and you want to get today's
145
- date out of the "date" unix system command. Here is the code:
140
+ Suppose you want to know how much time is left, in years/months/days/etc, before the next easter happening on a year
141
+ with a Friday 13th in August, and you want to get today's date out of the "date" unix system command. Here is the code:
146
142
 
147
143
  ```pycon
148
144
  >>> from datetime import *
@@ -174,14 +170,7 @@ more information about how to contribute to the project, see the `CONTRIBUTING.m
174
170
 
175
171
  ## Author
176
172
 
177
- The dateutil module was written by Gustavo Niemeyer <gustavo@niemeyer.net> in 2003.
178
-
179
- It was maintained by:
180
-
181
- * Gustavo Niemeyer <gustavo@niemeyer.net> 2003-2011
182
- * Tomi Pieviläinen <tomi.pievilainen@iki.fi> 2012-2014
183
- * Yaron de Leeuw <me@jarondl.net> 2014-2016
184
- * Paul Ganssle <paul@ganssle.io> 2015-
173
+ The dateutilx module is improved/re-written by rsb-23 in 2026.
185
174
 
186
175
  ## License
187
176
 
@@ -192,17 +181,11 @@ those those explicitly relicensed - are released only under the BSD 3-Clause Lic
192
181
 
193
182
 
194
183
  [license]: https://github.com/rsb-23/dateutilx/blob/main/LICENSE
195
-
196
184
  [lint-badge]: https://github.com/rsb-23/dateutilx/actions/workflows/code-lint.yml/badge.svg
197
-
198
185
  [tests-badge]: https://github.com/rsb-23/dateutilx/actions/workflows/code-test.yml/badge.svg
199
186
 
200
187
  [license-badge]: https://img.shields.io/pypi/l/dateutilx.svg?style=flat-square
201
-
202
188
  [pre-commit-badge]: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
203
-
204
189
  [py-version]: https://img.shields.io/pypi/pyversions/dateutilx
205
-
206
190
  [pypi-downloads]: https://img.shields.io/pypi/dm/dateutilx?label=Downloads
207
-
208
191
  [pypi-v]: https://img.shields.io/pypi/v/dateutilx?label=pypi
@@ -1,6 +1,6 @@
1
1
  > [!NOTE]
2
- > This package is rewrite of python-dateutil package with v2.9.0.post0 as base, to support active python versions only.
3
- > It is not related with the base project and its authors.
2
+ > This package is rewrite of `python-dateutil v2.9.0.post0` as base, to support active python versions only.
3
+ > It is not related with the base project and its authors.
4
4
  > Docs of base project are under `.base_project/`
5
5
 
6
6
  # dateutilx - powerful extensions to datetime
@@ -42,7 +42,7 @@ https://github.com/rsb-23/dateutilx/
42
42
  * Computing of dates based on very flexible recurrence rules, using a superset of
43
43
  the [iCalendar](https://www.ietf.org/rfc/rfc2445.txt) specification. Parsing of RFC strings is supported as well.
44
44
  * Generic parsing of dates in almost any string format;
45
- * Timezone (tzinfo) implementations for tzfile(5) format files (/etc/localtime, /usr/share/zoneinfo, etc), TZ
45
+ * Timezone (tzinfo) implementations for tzfile (5) format files (/etc/localtime, /usr/share/zoneinfo, etc), TZ
46
46
  environment string (in all known formats), iCalendar format files, given ranges (with help from relative deltas),
47
47
  local machine timezone, fixed offset timezone, UTC timezone, and Windows registry-based time zones.
48
48
  * Internal up-to-date world timezone information based on Olson's database.
@@ -51,13 +51,10 @@ https://github.com/rsb-23/dateutilx/
51
51
 
52
52
  ## Quick example
53
53
 
54
- Here's a snapshot, just to give an idea about the power of the
55
- package. For more examples, look at the documentation.
54
+ Here's a snapshot, just to give an idea about the power of the package. For more examples, look at the documentation.
56
55
 
57
- Suppose you want to know how much time is left, in
58
- years/months/days/etc, before the next easter happening on a
59
- year with a Friday 13th in August, and you want to get today's
60
- date out of the "date" unix system command. Here is the code:
56
+ Suppose you want to know how much time is left, in years/months/days/etc, before the next easter happening on a year
57
+ with a Friday 13th in August, and you want to get today's date out of the "date" unix system command. Here is the code:
61
58
 
62
59
  ```pycon
63
60
  >>> from datetime import *
@@ -89,14 +86,7 @@ more information about how to contribute to the project, see the `CONTRIBUTING.m
89
86
 
90
87
  ## Author
91
88
 
92
- The dateutil module was written by Gustavo Niemeyer <gustavo@niemeyer.net> in 2003.
93
-
94
- It was maintained by:
95
-
96
- * Gustavo Niemeyer <gustavo@niemeyer.net> 2003-2011
97
- * Tomi Pieviläinen <tomi.pievilainen@iki.fi> 2012-2014
98
- * Yaron de Leeuw <me@jarondl.net> 2014-2016
99
- * Paul Ganssle <paul@ganssle.io> 2015-
89
+ The dateutilx module is improved/re-written by rsb-23 in 2026.
100
90
 
101
91
  ## License
102
92
 
@@ -107,17 +97,11 @@ those those explicitly relicensed - are released only under the BSD 3-Clause Lic
107
97
 
108
98
 
109
99
  [license]: https://github.com/rsb-23/dateutilx/blob/main/LICENSE
110
-
111
100
  [lint-badge]: https://github.com/rsb-23/dateutilx/actions/workflows/code-lint.yml/badge.svg
112
-
113
101
  [tests-badge]: https://github.com/rsb-23/dateutilx/actions/workflows/code-test.yml/badge.svg
114
102
 
115
103
  [license-badge]: https://img.shields.io/pypi/l/dateutilx.svg?style=flat-square
116
-
117
104
  [pre-commit-badge]: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
118
-
119
105
  [py-version]: https://img.shields.io/pypi/pyversions/dateutilx
120
-
121
106
  [pypi-downloads]: https://img.shields.io/pypi/dm/dateutilx?label=Downloads
122
-
123
107
  [pypi-v]: https://img.shields.io/pypi/v/dateutilx?label=pypi
@@ -18,7 +18,6 @@ classifiers = [
18
18
  "License :: OSI Approved :: Apache Software License",
19
19
  "Programming Language :: Python",
20
20
  "Programming Language :: Python :: 3",
21
- "Programming Language :: Python :: 3.10",
22
21
  "Programming Language :: Python :: 3.11",
23
22
  "Programming Language :: Python :: 3.12",
24
23
  "Programming Language :: Python :: 3.13",
@@ -27,7 +26,7 @@ classifiers = [
27
26
  "Topic :: Software Development :: Libraries",
28
27
  ]
29
28
 
30
- requires-python = ">=3.10"
29
+ requires-python = ">=3.11"
31
30
  dependencies = ["tzdata; sys_platform == 'win32'"]
32
31
 
33
32
  [dependency-groups]
@@ -37,21 +36,22 @@ test = ["hypothesis", "pytest", "pytest-cov"]
37
36
 
38
37
  [project.urls]
39
38
  Homepage = "https://github.com/rsb-23/dateutilx"
40
- Documentation = "https://dateutil.readthedocs.io/en/stable/"
39
+ Documentation = "https://dateutilx.readthedocs.io/en/stable/"
41
40
  Source = "https://github.com/rsb-23/dateutilx"
42
41
 
43
42
  # Build Config
44
43
  [tool.hatch.version]
45
44
  source = "vcs"
46
45
  [tool.hatch.build.targets.sdist]
47
- include = ["src/dateutilx", "src/dateutil", "LICENSE", "README.md", "pyproject.toml"]
46
+ include = ["src", "LICENSE", "README.md", "pyproject.toml"]
48
47
  [tool.hatch.build.targets.wheel]
49
- packages = ["src/dateutilx", "src/dateutil"]
48
+ packages = ["src"]
50
49
 
50
+ [tool.coverage.run]
51
+ source = ["src"]
51
52
  [tool.coverage.report]
52
53
  fail_under = 85.0
53
- [tool.coverage.run]
54
- source = ["src/"]
54
+ skip_covered = true
55
55
 
56
56
  [tool.pytest.ini_options]
57
57
  python_files = ["test_*.py", "*_test.py", "*_solution.py", ]
@@ -66,7 +66,7 @@ def easter(year, method: EasterAlgo | int = EasterAlgo.WESTERN):
66
66
  # e - Extra days to add for method 2 (converting Julian
67
67
  # date to Gregorian date)
68
68
 
69
- y = year
69
+ y = int(year)
70
70
  g = y % 19
71
71
  e = 0
72
72
  if method < 3:
@@ -90,4 +90,4 @@ def easter(year, method: EasterAlgo | int = EasterAlgo.WESTERN):
90
90
  p = i - j + e
91
91
  d = 1 + (p + 27 + (p + 6) // 40) % 31
92
92
  m = 3 + (p + 26) // 30
93
- return datetime.date(int(y), int(m), int(d))
93
+ return datetime.date(y, m, d)
@@ -0,0 +1,4 @@
1
+ from .constants import Day, Frequency, Month
2
+ from .funcs import is_windows_os
3
+
4
+ __all__ = ["Day", "Frequency", "Month", "is_windows_os"]
@@ -1,12 +1,7 @@
1
1
  import calendar
2
- import sys
3
2
  from enum import IntEnum
4
3
 
5
4
 
6
- def is_windows_os() -> bool:
7
- return sys.platform.startswith("win")
8
-
9
-
10
5
  class Frequency(IntEnum):
11
6
  YEARLY = 0
12
7
  MONTHLY = 1
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+
4
+ def is_windows_os() -> bool:
5
+ return sys.platform.startswith("win")
@@ -34,12 +34,13 @@ import string
34
34
  import time
35
35
  import warnings
36
36
  from calendar import monthrange
37
+ from collections import deque
37
38
  from decimal import Decimal
38
39
  from io import StringIO
39
40
  from warnings import warn
40
41
 
41
- from dateutilx import relativedelta, tz
42
- from dateutilx.errors import ParserError, UnknownTimezoneWarning
42
+ from src import relativedelta, tz
43
+ from src.errors import ParserError, UnknownTimezoneWarning
43
44
 
44
45
  from .utils import standard_dt_parser
45
46
 
@@ -65,7 +66,7 @@ class _TimeLex:
65
66
 
66
67
  self.instream = instream
67
68
  self.charstack = []
68
- self.tokenstack = []
69
+ self.tokenstack = deque()
69
70
  self.eof = False
70
71
 
71
72
  def get_token(self):
@@ -83,7 +84,7 @@ class _TimeLex:
83
84
  demands that multiple tokens be parsed at once.
84
85
  """
85
86
  if self.tokenstack:
86
- return self.tokenstack.pop(0)
87
+ return self.tokenstack.popleft()
87
88
 
88
89
  seenletters = False
89
90
  token = None
@@ -309,10 +310,7 @@ class ParserInfo:
309
310
  return self._hms.get(name.lower())
310
311
 
311
312
  def ampm(self, name):
312
- try:
313
- return self._ampm[name.lower()]
314
- except KeyError:
315
- return None
313
+ return self._ampm.get(name.lower())
316
314
 
317
315
  def pertain(self, name):
318
316
  return name.lower() in self._pertain
@@ -564,8 +562,8 @@ class Parser:
564
562
  .. doctest::
565
563
  :options: +NORMALIZE_WHITESPACE
566
564
 
567
- >>> from dateutilx.parser import parse
568
- >>> from dateutilx.tz import gettz
565
+ >>> from src.parser import parse
566
+ >>> from src.tz import gettz
569
567
  >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")}
570
568
  >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
571
569
  datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200))
@@ -634,7 +632,7 @@ class Parser:
634
632
  ]
635
633
  # fmt: on
636
634
 
637
- def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, fuzzy_with_tokens=False):
635
+ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy: bool = False, fuzzy_with_tokens: bool = False):
638
636
  """
639
637
  Private method which performs the heavy lifting of parsing, called from
640
638
  ``parse()``, which passes on its ``kwargs`` to this function.
@@ -669,7 +667,7 @@ class Parser:
669
667
 
670
668
  .. doctest::
671
669
 
672
- >>> from dateutilx.parser import parse
670
+ >>> from src.parser import parse
673
671
  >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
674
672
  (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
675
673
 
@@ -678,12 +676,8 @@ class Parser:
678
676
  fuzzy = True
679
677
 
680
678
  info = self.info
681
-
682
- if dayfirst is None:
683
- dayfirst = info.dayfirst
684
-
685
- if yearfirst is None:
686
- yearfirst = info.yearfirst
679
+ dayfirst = dayfirst or info.dayfirst
680
+ yearfirst = yearfirst or info.yearfirst
687
681
 
688
682
  res = self._Result()
689
683
  _tokens = _TimeLex.split(timestr) # Splits the timestr into tokens
@@ -706,7 +700,7 @@ class Parser:
706
700
 
707
701
  if value is not None:
708
702
  # Numeric token
709
- i = self._parse_numeric_token(_tokens, i, info, ymd, res, fuzzy)
703
+ i = self._parse_numeric_token(_tokens, i, info=info, ymd=ymd, res=res, fuzzy=fuzzy)
710
704
 
711
705
  # Check weekday
712
706
  elif info.weekday(_tokens[i]) is not None:
@@ -800,13 +794,14 @@ class Parser:
800
794
  res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60)
801
795
 
802
796
  # Look for a timezone name between parenthesis
803
- if (
804
- i + 5 < len_l
805
- and info.jump(_tokens[i + 2])
806
- and _tokens[i + 3] == "("
807
- and _tokens[i + 5] == ")"
808
- and len(_tokens[i + 4]) >= 3
809
- and self._could_be_tzname(res.hour, res.tzname, None, _tokens[i + 4])
797
+ if i + 5 < len_l and all(
798
+ (
799
+ info.jump(_tokens[i + 2]),
800
+ _tokens[i + 3] == "(",
801
+ _tokens[i + 5] == ")",
802
+ len(_tokens[i + 4]) >= 3,
803
+ self._could_be_tzname(res.hour, res.tzname, None, _tokens[i + 4]),
804
+ )
810
805
  ):
811
806
  # -0300 (BRST)
812
807
  res.tzname = _tokens[i + 4]
@@ -842,7 +837,7 @@ class Parser:
842
837
 
843
838
  return res, None
844
839
 
845
- def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy):
840
+ def _parse_numeric_token(self, tokens, idx, *, info, ymd, res, fuzzy):
846
841
  # Token is a number
847
842
  value_repr = tokens[idx]
848
843
  try:
@@ -854,11 +849,13 @@ class Parser:
854
849
 
855
850
  len_l = len(tokens)
856
851
 
857
- if (
858
- len(ymd) == 3
859
- and len_li in (2, 4)
860
- and res.hour is None
861
- and (idx + 1 >= len_l or (tokens[idx + 1] != ":" and info.hms(tokens[idx + 1]) is None))
852
+ if all(
853
+ (
854
+ len(ymd) == 3,
855
+ len_li in (2, 4),
856
+ res.hour is None,
857
+ (idx + 1 >= len_l or (tokens[idx + 1] != ":" and info.hms(tokens[idx + 1]) is None)),
858
+ )
862
859
  ):
863
860
  # 19990101T23[59]
864
861
  s = tokens[idx]
@@ -1271,8 +1268,8 @@ def parse(timestr, parser_info=None, **kwargs):
1271
1268
  .. doctest::
1272
1269
  :options: +NORMALIZE_WHITESPACE
1273
1270
 
1274
- >>> from dateutilx.parser import parse
1275
- >>> from dateutilx.tz import gettz
1271
+ >>> from src.parser import parse
1272
+ >>> from src.tz import gettz
1276
1273
  >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")}
1277
1274
  >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
1278
1275
  datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200))
@@ -1308,7 +1305,7 @@ def parse(timestr, parser_info=None, **kwargs):
1308
1305
 
1309
1306
  .. doctest::
1310
1307
 
1311
- >>> from dateutilx.parser import parse
1308
+ >>> from src.parser import parse
1312
1309
  >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
1313
1310
  (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
1314
1311
 
@@ -1349,8 +1346,9 @@ class _TzParser:
1349
1346
  self.end = self._Attr()
1350
1347
 
1351
1348
  def parse(self, tzstr):
1349
+ tzstr_pattern = re.compile(r"([,:.]|[a-zA-Z]+|[0-9]+)")
1352
1350
  res = self._Result()
1353
- _tmp_list = [x for x in re.split(r"([,:.]|[a-zA-Z]+|[0-9]+)", tzstr) if x]
1351
+ _tmp_list = [x for x in re.split(tzstr_pattern, tzstr) if x]
1354
1352
  used_idxs = []
1355
1353
  try:
1356
1354
  i, len_l = 0, len(_tmp_list)
@@ -1360,43 +1358,43 @@ class _TzParser:
1360
1358
  j = i
1361
1359
  while j < len_l and not [x for x in _tmp_list[j] if x in "0123456789:,-+"]:
1362
1360
  j += 1
1363
- if j != i:
1364
- if not res.stdabbr:
1365
- offattr = "stdoffset"
1366
- res.stdabbr = "".join(_tmp_list[i:j])
1367
- else:
1368
- offattr = "dstoffset"
1369
- res.dstabbr = "".join(_tmp_list[i:j])
1370
-
1371
- used_idxs.extend([*range(j)])
1372
- i = j
1373
- if i < len_l and (_tmp_list[i] in ("+", "-") or _tmp_list[i][0] in "0123456789"):
1374
- if _tmp_list[i] in ("+", "-"):
1375
- # Yes, that's right. See the TZ variable documentation.
1376
- signal = (1, -1)[_tmp_list[i] == "+"]
1377
- used_idxs.append(i)
1378
- i += 1
1379
- else:
1380
- signal = -1
1381
- len_li = len(_tmp_list[i])
1382
- if len_li == 4:
1383
- # -0300
1384
- setattr(res, offattr, (int(_tmp_list[i][:2]) * 3600 + int(_tmp_list[i][2:]) * 60) * signal)
1385
- elif i + 1 < len_l and _tmp_list[i + 1] == ":":
1386
- # -03:00
1387
- setattr(res, offattr, (int(_tmp_list[i]) * 3600 + int(_tmp_list[i + 2]) * 60) * signal)
1388
- used_idxs.append(i)
1389
- i += 2
1390
- elif len_li <= 2:
1391
- # -[0]3
1392
- setattr(res, offattr, int(_tmp_list[i][:2]) * 3600 * signal)
1393
- else:
1394
- return None
1361
+ if j == i:
1362
+ break
1363
+
1364
+ if not res.stdabbr:
1365
+ offattr = "stdoffset"
1366
+ res.stdabbr = "".join(_tmp_list[i:j])
1367
+ else:
1368
+ offattr = "dstoffset"
1369
+ res.dstabbr = "".join(_tmp_list[i:j])
1370
+
1371
+ used_idxs.extend([*range(j)])
1372
+ i = j
1373
+ if i < len_l and (_tmp_list[i] in ("+", "-") or _tmp_list[i][0] in "0123456789"):
1374
+ if _tmp_list[i] in ("+", "-"):
1375
+ # Yes, that's right. See the TZ variable documentation.
1376
+ signal = (1, -1)[_tmp_list[i] == "+"]
1395
1377
  used_idxs.append(i)
1396
1378
  i += 1
1397
- if res.dstabbr:
1398
- break
1399
- else:
1379
+ else:
1380
+ signal = -1
1381
+ len_li = len(_tmp_list[i])
1382
+ if len_li == 4:
1383
+ # -0300
1384
+ setattr(res, offattr, (int(_tmp_list[i][:2]) * 3600 + int(_tmp_list[i][2:]) * 60) * signal)
1385
+ elif i + 1 < len_l and _tmp_list[i + 1] == ":":
1386
+ # -03:00
1387
+ setattr(res, offattr, (int(_tmp_list[i]) * 3600 + int(_tmp_list[i + 2]) * 60) * signal)
1388
+ used_idxs.append(i)
1389
+ i += 2
1390
+ elif len_li <= 2:
1391
+ # -[0]3
1392
+ setattr(res, offattr, int(_tmp_list[i][:2]) * 3600 * signal)
1393
+ else:
1394
+ return None
1395
+ used_idxs.append(i)
1396
+ i += 1
1397
+ if res.dstabbr:
1400
1398
  break
1401
1399
 
1402
1400
  if i < len_l:
@@ -8,11 +8,11 @@ ISO-8601 specification.
8
8
  """
9
9
 
10
10
  import calendar
11
+ import datetime as dt
11
12
  import re
12
- from datetime import date, datetime, time, timedelta
13
13
  from functools import wraps
14
14
 
15
- from dateutilx.tz import UTC, TzOffset
15
+ from src.tz import UTC, TzOffset
16
16
 
17
17
  __all__ = ["isoparse", "isoparser"]
18
18
 
@@ -21,7 +21,10 @@ def _takes_ascii(f):
21
21
  @wraps(f)
22
22
  def func(self, str_in, *args, **kwargs):
23
23
  # If it's a stream, read the whole thing
24
- str_in = getattr(str_in, "read", lambda: str_in)()
24
+ read = getattr(str_in, "read", None)
25
+
26
+ if read is not None:
27
+ str_in = read()
25
28
 
26
29
  # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
27
30
  if isinstance(str_in, str):
@@ -104,10 +107,10 @@ class IsoParser:
104
107
  - `±HHMM`
105
108
  - `±HH`
106
109
 
107
- Offsets will be represented as :class:`dateutil.tz.tzoffset` objects,
110
+ Offsets will be represented as :class:`dateutilx.tz.tzoffset` objects,
108
111
  with the exception of UTC, which will be represented as
109
- :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such
110
- as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`.
112
+ :class:`dateutilx.tz.tzutc`. Time zone offsets equivalent to UTC (such
113
+ as `+00:00`) will also be represented as :class:`dateutilx.tz.tzutc`.
111
114
 
112
115
  :param dt_str:
113
116
  A string or stream containing only an ISO-8601 datetime string
@@ -138,9 +141,9 @@ class IsoParser:
138
141
 
139
142
  if len(components) > 3 and components[3] == 24:
140
143
  components[3] = 0
141
- return datetime(*components) + timedelta(days=1)
144
+ return dt.datetime(*components) + dt.timedelta(days=1)
142
145
 
143
- return datetime(*components)
146
+ return dt.datetime(*components)
144
147
 
145
148
  @_takes_ascii
146
149
  def parse_isodate(self, datestr):
@@ -156,7 +159,7 @@ class IsoParser:
156
159
  components, pos = self._parse_isodate(datestr)
157
160
  if pos < len(datestr):
158
161
  raise ValueError(f"String contains unknown ISO components: {datestr.decode('ascii')!r}")
159
- return date(*components)
162
+ return dt.date(*components)
160
163
 
161
164
  @_takes_ascii
162
165
  def parse_isotime(self, timestr):
@@ -172,7 +175,7 @@ class IsoParser:
172
175
  components = self._parse_isotime(timestr)
173
176
  if components[0] == 24:
174
177
  components[0] = 0
175
- return time(*components)
178
+ return dt.time(*components)
176
179
 
177
180
  @_takes_ascii
178
181
  def parse_tzstr(self, tzstr, zero_as_utc=True):
@@ -185,11 +188,11 @@ class IsoParser:
185
188
  A string representing an ISO time zone offset
186
189
 
187
190
  :param zero_as_utc:
188
- Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones
191
+ Whether to return :class:`dateutilx.tz.tzutc` for zero-offset zones
189
192
 
190
193
  :return:
191
- Returns :class:`dateutil.tz.tzoffset` for offsets and
192
- :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
194
+ Returns :class:`dateutilx.tz.tzoffset` for offsets and
195
+ :class:`dateutilx.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
193
196
  specified) offsets equivalent to UTC.
194
197
  """
195
198
  return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
@@ -199,19 +202,19 @@ class IsoParser:
199
202
  _TIME_SEP = b":"
200
203
  _FRACTION_REGEX = re.compile(b"[.,]([0-9]+)")
201
204
 
202
- def _parse_isodate(self, dt_str):
205
+ def _parse_isodate(self, dt_str: str) -> tuple:
206
+ if len(dt_str) < 4:
207
+ raise ValueError("ISO string too short")
208
+
203
209
  try:
204
210
  return self._parse_isodate_common(dt_str)
205
211
  except ValueError:
206
212
  return self._parse_isodate_uncommon(dt_str)
207
213
 
208
- def _parse_isodate_common(self, dt_str):
214
+ def _parse_isodate_common(self, dt_str: str) -> tuple:
209
215
  len_str = len(dt_str)
210
216
  components = [1, 1, 1]
211
217
 
212
- if len_str < 4:
213
- raise ValueError("ISO string too short")
214
-
215
218
  # Year
216
219
  components[0] = int(dt_str[:4])
217
220
  pos = 4
@@ -245,10 +248,7 @@ class IsoParser:
245
248
  components[2] = int(dt_str[pos : pos + 2])
246
249
  return components, pos + 2
247
250
 
248
- def _parse_isodate_uncommon(self, dt_str):
249
- if len(dt_str) < 4:
250
- raise ValueError("ISO string too short")
251
-
251
+ def _parse_isodate_uncommon(self, dt_str: str) -> tuple:
252
252
  # All ISO formats start with the year
253
253
  year = int(dt_str[:4])
254
254
 
@@ -283,7 +283,7 @@ class IsoParser:
283
283
  if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
284
284
  raise ValueError(f"Invalid ordinal day {ordinal_day} for year {year}")
285
285
 
286
- base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1)
286
+ base_date = dt.date(year, 1, 1) + dt.timedelta(days=ordinal_day - 1)
287
287
 
288
288
  components = [base_date.year, base_date.month, base_date.day]
289
289
  return components, pos
@@ -315,12 +315,7 @@ class IsoParser:
315
315
  raise ValueError(f"Invalid weekday: {day}")
316
316
 
317
317
  # Get week 1 for the specific year:
318
- jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it
319
- week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1)
320
-
321
- # Now add the specific number of weeks and days to get what we want
322
- week_offset = (week - 1) * 7 + (day - 1)
323
- return week_1 + timedelta(days=week_offset)
318
+ return dt.date.fromisocalendar(year, week, day)
324
319
 
325
320
  def _parse_isotime(self, timestr):
326
321
  len_str = len(timestr)