dateutilx 0.1.1__tar.gz → 0.3.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.3.0}/.gitignore +1 -4
  2. {dateutilx-0.1.1 → dateutilx-0.3.0}/PKG-INFO +10 -27
  3. {dateutilx-0.1.1 → dateutilx-0.3.0}/README.md +7 -23
  4. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/__init__.py +1 -1
  5. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/easter.py +2 -2
  6. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/parser/_parser.py +65 -67
  7. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/parser/isoparser.py +23 -28
  8. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/parser/utils.py +3 -4
  9. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/relativedelta.py +172 -249
  10. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/rrule.py +131 -153
  11. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/tz/__init__.py +5 -14
  12. dateutilx-0.3.0/dateutilx/tz/_factories.py +61 -0
  13. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/tz/_win.py +20 -19
  14. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/tz/tz.py +111 -114
  15. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/tz/win.py +2 -5
  16. dateutilx-0.3.0/dateutilx/utils/__init__.py +10 -0
  17. {dateutilx-0.1.1/src/dateutilx/helper → dateutilx-0.3.0/dateutilx/utils}/constants.py +0 -5
  18. dateutilx-0.1.1/src/dateutilx/utils.py → dateutilx-0.3.0/dateutilx/utils/funcs.py +5 -8
  19. {dateutilx-0.1.1/src/dateutilx → dateutilx-0.3.0/dateutilx/utils}/weekday.py +11 -12
  20. {dateutilx-0.1.1 → dateutilx-0.3.0}/pyproject.toml +7 -7
  21. dateutilx-0.1.1/src/dateutil/__init__.py +0 -5
  22. dateutilx-0.1.1/src/dateutilx/helper/__init__.py +0 -1
  23. dateutilx-0.1.1/src/dateutilx/tz/_factories.py +0 -67
  24. dateutilx-0.1.1/src/dateutilx/tzwin.py +0 -4
  25. {dateutilx-0.1.1 → dateutilx-0.3.0}/.base_project/LICENSE +0 -0
  26. {dateutilx-0.1.1 → dateutilx-0.3.0}/LICENSE +0 -0
  27. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/errors.py +0 -0
  28. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/info.py +0 -0
  29. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/parser/__init__.py +0 -0
  30. {dateutilx-0.1.1/src → dateutilx-0.3.0}/dateutilx/tz/_common.py +0 -0
@@ -13,13 +13,10 @@ dist/
13
13
  .tox/
14
14
  .pytest_cache/
15
15
  venv/
16
- .venv/
16
+ .venv*/
17
17
  .hypothesis/
18
18
  .coverage
19
19
 
20
- # Autogenerated version information
21
- src/dateutilx/_version.py
22
-
23
20
  # Sphinx documentation
24
21
  docs/_build/
25
22
  docs/docs_venv
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dateutilx
3
- Version: 0.1.1
3
+ Version: 0.3.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
@@ -13,7 +13,7 @@ def __getattr__(name):
13
13
 
14
14
  if name in __all__:
15
15
  return importlib.import_module(f".{name}", __name__)
16
- raise AttributeError(f"module {__name__!r} has not attribute {name!r}")
16
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
17
17
 
18
18
 
19
19
  def __dir__():
@@ -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)
@@ -34,6 +34,7 @@ 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
@@ -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
@@ -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.
@@ -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]
@@ -1135,9 +1132,9 @@ class Parser:
1135
1132
  if isinstance(tzdata, datetime.tzinfo) or tzdata is None:
1136
1133
  tzinfo = tzdata
1137
1134
  elif isinstance(tzdata, str):
1138
- tzinfo = tz.tzstr(tzdata)
1135
+ tzinfo = tz.TzStr(tzdata)
1139
1136
  elif isinstance(tzdata, int):
1140
- tzinfo = tz.tzoffset(tzname, tzdata)
1137
+ tzinfo = tz.TzOffset(tzname, tzdata)
1141
1138
  else:
1142
1139
  raise TypeError("Offset must be tzinfo subclass, tz string, or int offset.")
1143
1140
  return tzinfo
@@ -1149,7 +1146,7 @@ class Parser:
1149
1146
  aware = self._assign_tzname(aware, res.tzname)
1150
1147
 
1151
1148
  elif res.tzname and res.tzname in time.tzname:
1152
- aware = naive.replace(tzinfo=tz.tzlocal())
1149
+ aware = naive.replace(tzinfo=tz.TzLocal())
1153
1150
 
1154
1151
  # Handle ambiguous local datetime
1155
1152
  aware = self._assign_tzname(aware, res.tzname)
@@ -1162,7 +1159,7 @@ class Parser:
1162
1159
  aware = naive.replace(tzinfo=tz.UTC)
1163
1160
 
1164
1161
  elif res.tzoffset:
1165
- aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
1162
+ aware = naive.replace(tzinfo=tz.TzOffset(res.tzname, res.tzoffset))
1166
1163
 
1167
1164
  elif not res.tzname:
1168
1165
  # i.e. no timezone information was found.
@@ -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,8 +8,8 @@ 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
15
  from dateutilx.tz import UTC, TzOffset
@@ -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)
@@ -1,5 +1,6 @@
1
1
  import datetime as dt
2
2
  from concurrent.futures import ThreadPoolExecutor, as_completed
3
+ from typing import Any
3
4
 
4
5
  _SEP_TABLE = str.maketrans(" :.T-/\\", "_" * 7)
5
6
 
@@ -49,14 +50,12 @@ def _try_parse(timestr: str, fmt: str) -> dt.datetime | None:
49
50
  # normalise microsecond fields: strftime always emits 6 digits
50
51
  if "%f" in fmt:
51
52
  reconstructed = reconstructed.rstrip("0") or "0"
52
- if reconstructed != timestr:
53
- return None
54
- return parsed
53
+ return None if reconstructed != timestr else parsed
55
54
  except ValueError:
56
55
  return None
57
56
 
58
57
 
59
- def standard_dt_parser(timestr: str, max_workers: int = 3) -> dt.datetime:
58
+ def standard_dt_parser(timestr: Any, max_workers: int = 3) -> dt.datetime:
60
59
  timestr = str(timestr).strip()
61
60
  timestr = timestr.translate(_SEP_TABLE)
62
61
  timestr = timestr.lower()