dateutilx 0.3.0__tar.gz → 0.4.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 (26) hide show
  1. {dateutilx-0.3.0 → dateutilx-0.4.0}/PKG-INFO +2 -2
  2. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/parser/_parser.py +7 -7
  3. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/parser/isoparser.py +23 -12
  4. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/relativedelta.py +8 -7
  5. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/rrule.py +15 -16
  6. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/tz/tz.py +11 -4
  7. {dateutilx-0.3.0 → dateutilx-0.4.0}/.base_project/LICENSE +0 -0
  8. {dateutilx-0.3.0 → dateutilx-0.4.0}/.gitignore +0 -0
  9. {dateutilx-0.3.0 → dateutilx-0.4.0}/LICENSE +0 -0
  10. {dateutilx-0.3.0 → dateutilx-0.4.0}/README.md +0 -0
  11. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/__init__.py +0 -0
  12. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/easter.py +0 -0
  13. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/errors.py +0 -0
  14. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/info.py +0 -0
  15. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/parser/__init__.py +0 -0
  16. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/parser/utils.py +0 -0
  17. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/tz/__init__.py +0 -0
  18. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/tz/_common.py +0 -0
  19. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/tz/_factories.py +0 -0
  20. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/tz/_win.py +0 -0
  21. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/tz/win.py +0 -0
  22. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/utils/__init__.py +0 -0
  23. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/utils/constants.py +0 -0
  24. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/utils/funcs.py +0 -0
  25. {dateutilx-0.3.0 → dateutilx-0.4.0}/dateutilx/utils/weekday.py +0 -0
  26. {dateutilx-0.3.0 → dateutilx-0.4.0}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: dateutilx
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Modern extension to the standard Python datetime module
5
5
  Project-URL: Homepage, https://github.com/rsb-23/dateutilx
6
6
  Project-URL: Documentation, https://dateutilx.readthedocs.io/en/stable/
@@ -391,15 +391,16 @@ class _YMD(list):
391
391
  return 1 <= value <= monthrange(year, month)[1]
392
392
 
393
393
  def append(self, val, label=None):
394
+ _valid_labels = (None, "Y")
394
395
  if hasattr(val, "__len__"):
395
396
  if val.isdigit() and len(val) > 2:
396
397
  self.century_specified = True
397
- if label not in [None, "Y"]: # pragma: no cover
398
+ if label not in _valid_labels: # pragma: no cover
398
399
  raise ValueError(label)
399
400
  label = "Y"
400
401
  elif val > 100:
401
402
  self.century_specified = True
402
- if label not in [None, "Y"]: # pragma: no cover
403
+ if label not in _valid_labels: # pragma: no cover
403
404
  raise ValueError(label)
404
405
  label = "Y"
405
406
 
@@ -426,7 +427,7 @@ class _YMD(list):
426
427
  if len(self) == 3 and len(strids) == 2:
427
428
  # we can back out the remaining stridx value
428
429
  missing = [x for x in range(3) if x not in strids.values()]
429
- key = [x for x in ["y", "m", "d"] if x not in strids]
430
+ key = [x for x in ("y", "m", "d") if x not in strids]
430
431
  assert len(missing) == len(key) == 1
431
432
  key = key[0]
432
433
  val = missing[0]
@@ -1022,8 +1023,7 @@ class Parser:
1022
1023
  def _could_be_tzname(self, hour, tzname, tzoffset, token):
1023
1024
  return (
1024
1025
  hour is not None
1025
- and tzname is None
1026
- and tzoffset is None
1026
+ and tzname is tzoffset is None
1027
1027
  and len(token) <= 5
1028
1028
  and (all(x in string.ascii_uppercase for x in token) or token in self.info.UTCZONE)
1029
1029
  )
@@ -1370,7 +1370,7 @@ class _TzParser:
1370
1370
 
1371
1371
  used_idxs.extend([*range(j)])
1372
1372
  i = j
1373
- if i < len_l and (_tmp_list[i] in ("+", "-") or _tmp_list[i][0] in "0123456789"):
1373
+ if i < len_l and (_tmp_list[i] in ("+", "-") or _tmp_list[i][0] in string.digits):
1374
1374
  if _tmp_list[i] in ("+", "-"):
1375
1375
  # Yes, that's right. See the TZ variable documentation.
1376
1376
  signal = (1, -1)[_tmp_list[i] == "+"]
@@ -1462,7 +1462,7 @@ class _TzParser:
1462
1462
  for x in _tmp_list[i:]
1463
1463
  if x not in (",", "/", "J", "M", ".", "-", ":")
1464
1464
  for y in x
1465
- if y not in "0123456789"
1465
+ if y not in string.digits
1466
1466
  ]
1467
1467
  ):
1468
1468
  for x in (res.start, res.end):
@@ -10,6 +10,7 @@ ISO-8601 specification.
10
10
  import calendar
11
11
  import datetime as dt
12
12
  import re
13
+ import string
13
14
  from functools import wraps
14
15
 
15
16
  from dateutilx.tz import UTC, TzOffset
@@ -49,7 +50,7 @@ class IsoParser:
49
50
  For strict ISO-8601 adherence, pass ``'T'``.
50
51
  """
51
52
  if sep is not None:
52
- if len(sep) != 1 or ord(sep) >= 128 or sep in "0123456789":
53
+ if len(sep) != 1 or ord(sep) >= 128 or sep in string.digits:
53
54
  raise ValueError("Separator must be a single, non-numeric " + "ASCII character")
54
55
 
55
56
  sep = sep.encode("ascii")
@@ -202,6 +203,13 @@ class IsoParser:
202
203
  _TIME_SEP = b":"
203
204
  _FRACTION_REGEX = re.compile(b"[.,]([0-9]+)")
204
205
 
206
+ def _parse_digits(self, s, pos, n):
207
+ """Parse exactly ``n`` ASCII digits starting at ``pos``."""
208
+ digits = s[pos : pos + n]
209
+ if len(digits) != n or not digits.isdigit():
210
+ raise ValueError("Expected {} digits, got {!r}".format(n, digits.decode("ascii")))
211
+ return int(digits)
212
+
205
213
  def _parse_isodate(self, dt_str: str) -> tuple:
206
214
  if len(dt_str) < 4:
207
215
  raise ValueError("ISO string too short")
@@ -216,7 +224,7 @@ class IsoParser:
216
224
  components = [1, 1, 1]
217
225
 
218
226
  # Year
219
- components[0] = int(dt_str[:4])
227
+ components[0] = self._parse_digits(dt_str, 0, 4)
220
228
  pos = 4
221
229
  if pos >= len_str:
222
230
  return components, pos
@@ -229,7 +237,7 @@ class IsoParser:
229
237
  if len_str - pos < 2:
230
238
  raise ValueError("Invalid common month")
231
239
 
232
- components[1] = int(dt_str[pos : pos + 2])
240
+ components[1] = self._parse_digits(dt_str, pos, 2)
233
241
  pos += 2
234
242
 
235
243
  if pos >= len_str:
@@ -245,12 +253,12 @@ class IsoParser:
245
253
  # Day
246
254
  if len_str - pos < 2:
247
255
  raise ValueError("Invalid common day")
248
- components[2] = int(dt_str[pos : pos + 2])
256
+ components[2] = self._parse_digits(dt_str, pos, 2)
249
257
  return components, pos + 2
250
258
 
251
259
  def _parse_isodate_uncommon(self, dt_str: str) -> tuple:
252
260
  # All ISO formats start with the year
253
- year = int(dt_str[:4])
261
+ year = self._parse_digits(dt_str, 0, 4)
254
262
 
255
263
  has_sep = dt_str[4:5] == self._DATE_SEP
256
264
 
@@ -258,7 +266,7 @@ class IsoParser:
258
266
  if dt_str[pos : pos + 1] == b"W":
259
267
  # YYYY-?Www-?D?
260
268
  pos += 1
261
- weekno = int(dt_str[pos : pos + 2])
269
+ weekno = self._parse_digits(dt_str, pos, 2)
262
270
  pos += 2
263
271
 
264
272
  dayno = 1
@@ -268,7 +276,7 @@ class IsoParser:
268
276
 
269
277
  pos += has_sep
270
278
 
271
- dayno = int(dt_str[pos : pos + 1])
279
+ dayno = self._parse_digits(dt_str, pos, 1)
272
280
  pos += 1
273
281
 
274
282
  base_date = self._calculate_weekdate(year, weekno, dayno)
@@ -277,7 +285,7 @@ class IsoParser:
277
285
  if len(dt_str) - pos < 3:
278
286
  raise ValueError("Invalid ordinal day")
279
287
 
280
- ordinal_day = int(dt_str[pos : pos + 3])
288
+ ordinal_day = self._parse_digits(dt_str, pos, 3)
281
289
  pos += 3
282
290
 
283
291
  if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
@@ -347,7 +355,7 @@ class IsoParser:
347
355
 
348
356
  if comp < 3:
349
357
  # Hour, minute, second
350
- components[comp] = int(timestr[pos : pos + 2])
358
+ components[comp] = self._parse_digits(timestr, pos, 2)
351
359
  pos += 2
352
360
 
353
361
  if comp == 3:
@@ -383,13 +391,16 @@ class IsoParser:
383
391
  else:
384
392
  raise ValueError("Time zone offset requires sign")
385
393
 
386
- hours = int(tzstr[1:3])
394
+ hours = self._parse_digits(tzstr, 1, 2)
387
395
  if len(tzstr) == 3:
388
396
  minutes = 0
389
397
  else:
390
- minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3) :])
398
+ pos = 4 if tzstr[3:4] == self._TIME_SEP else 3
399
+ if len(tzstr) != pos + 2:
400
+ raise ValueError("Invalid minutes in time zone offset")
401
+ minutes = self._parse_digits(tzstr, pos, 2)
391
402
 
392
- if zero_as_utc and hours == 0 and minutes == 0:
403
+ if zero_as_utc and hours == minutes == 0:
393
404
  return UTC
394
405
 
395
406
  if minutes > 59:
@@ -172,7 +172,7 @@ class RelativeDelta:
172
172
 
173
173
  # Remove the year/month delta so the timedelta is just well-defined
174
174
  # time units (seconds, days and microseconds)
175
- dtm = self.__radd__(dt2)
175
+ dtm = self + dt2
176
176
 
177
177
  # If we've overshot our target, make an adjustment
178
178
  if dt1 < dt2:
@@ -185,7 +185,7 @@ class RelativeDelta:
185
185
  while compare(dt1, dtm):
186
186
  months += increment
187
187
  self._set_months(months)
188
- dtm = self.__radd__(dt2)
188
+ dtm = self + dt2
189
189
 
190
190
  # Get the timedelta between the "months-adjusted" date and dt1
191
191
  delta = dt1 - dtm
@@ -212,14 +212,15 @@ class RelativeDelta:
212
212
 
213
213
  yday = nlyearday or yearday
214
214
 
215
- if yearday and not nlyearday and yearday > 59:
215
+ if yearday and not nlyearday and (59 < yearday < 366):
216
+ # Shifting for leapday except Dec 32/31
216
217
  self.leapdays = -1
217
218
 
218
219
  if yday:
219
220
  if not 1 <= yday <= 366:
220
221
  raise ValueError(f"invalid year day ({yday})")
221
222
 
222
- resolved = dt.date(2001, 1, 1) + dt.timedelta(days=yday - 1)
223
+ resolved = dt.date(2001, 1, 1) + dt.timedelta(days=min(yday, 365) - 1)
223
224
  self.day, self.month = resolved.day, resolved.month
224
225
 
225
226
  def _absolutes(self) -> dict[str, Number]:
@@ -393,7 +394,7 @@ class RelativeDelta:
393
394
  month += 12
394
395
  day = min(calendar.monthrange(year, month)[1], self.day or other.day)
395
396
  repl = {"year": year, "month": month, "day": day}
396
- for attr in ["hour", "minute", "second", "microsecond"]:
397
+ for attr in ("hour", "minute", "second", "microsecond"):
397
398
  value = getattr(self, attr)
398
399
  if value is not None:
399
400
  repl[attr] = value
@@ -512,11 +513,11 @@ class RelativeDelta:
512
513
 
513
514
  def __repr__(self):
514
515
  _tmp_list = []
515
- for attr in ["years", "months", "days", "leapdays", "hours", "minutes", "seconds", "microseconds"]:
516
+ for attr in ("years", "months", "days", "leapdays", "hours", "minutes", "seconds", "microseconds"):
516
517
  value = getattr(self, attr)
517
518
  if value:
518
519
  _tmp_list.append(f"{attr}={value:+g}")
519
- for attr in ["year", "month", "day", "weekday", "hour", "minute", "second", "microsecond"]:
520
+ for attr in ("year", "month", "day", "weekday", "hour", "minute", "second", "microsecond"):
520
521
  value = getattr(self, attr)
521
522
  if value is not None:
522
523
  _tmp_list.append(f"{attr}={value!r}")
@@ -106,21 +106,20 @@ class RruleBase:
106
106
  i = 0
107
107
  gen = self._cache_gen
108
108
  cache = self._cache
109
- acquire = self._cache_lock.acquire
110
- release = self._cache_lock.release
109
+
111
110
  while gen:
112
111
  if i == len(cache):
113
- acquire()
114
- if self._cache_complete:
115
- break
116
- try:
117
- for _ in range(10):
118
- cache.append(next(gen))
119
- except StopIteration:
120
- self._cache_gen = gen = None
121
- self._cache_complete = True
122
- break
123
- release()
112
+ with self._cache_lock:
113
+ if self._cache_complete:
114
+ break
115
+ try:
116
+ for _ in range(10):
117
+ cache.append(next(gen))
118
+ except StopIteration:
119
+ self._cache_gen = gen = None
120
+ self._cache_complete = True
121
+ break
122
+
124
123
  yield cache[i]
125
124
  i += 1
126
125
  while i < self._len:
@@ -459,7 +458,7 @@ class Rrule(RruleBase):
459
458
  if self._bysetpos:
460
459
  self._original_rule["bysetpos"] = self._bysetpos
461
460
 
462
- if byweekno is None and byyearday is None and bymonthday is None and byweekday is None and byeaster is None:
461
+ if all(x is None for x in (byweekno, byyearday, bymonthday, byweekday, byeaster)):
463
462
  if freq == YEARLY:
464
463
  if bymonth is None:
465
464
  bymonth = dtstart.month
@@ -660,7 +659,7 @@ class Rrule(RruleBase):
660
659
  original_rule = self._original_rule
661
660
 
662
661
  partfmt = "{name}={vals}"
663
- for name, key in [
662
+ for name, key in (
664
663
  ("BYSETPOS", "bysetpos"),
665
664
  ("BYMONTH", "bymonth"),
666
665
  ("BYMONTHDAY", "bymonthday"),
@@ -671,7 +670,7 @@ class Rrule(RruleBase):
671
670
  ("BYMINUTE", "byminute"),
672
671
  ("BYSECOND", "bysecond"),
673
672
  ("BYEASTER", "byeaster"),
674
- ]:
673
+ ):
675
674
  value = original_rule.get(key)
676
675
  if value:
677
676
  parts.append(partfmt.format(name=name, vals=(",".join(str(v) for v in value))))
@@ -11,6 +11,7 @@ import bisect
11
11
  import contextlib
12
12
  import datetime
13
13
  import os
14
+ import string
14
15
  import struct
15
16
  import threading
16
17
  import time
@@ -67,7 +68,9 @@ class TzUTC(datetime.tzinfo, metaclass=_TzSingleton):
67
68
 
68
69
  __name__ = "tzutc"
69
70
  __hash__ = None
70
- __reduce__ = object.__reduce__
71
+
72
+ def __reduce__(self):
73
+ return self.__class__, tuple()
71
74
 
72
75
  def utcoffset(self, dt):
73
76
  return ZERO
@@ -172,7 +175,8 @@ class TzOffset(datetime.tzinfo, metaclass=_TzOffsetFactory):
172
175
  def __repr__(self):
173
176
  return f"{self.__class__.__name__}({self._name!r}, {int(self._offset.total_seconds())})"
174
177
 
175
- __reduce__ = object.__reduce__
178
+ def __reduce__(self):
179
+ return self.__class__, (self._name, self._offset)
176
180
 
177
181
 
178
182
  class TzLocal(_TzInfo):
@@ -191,7 +195,7 @@ class TzLocal(_TzInfo):
191
195
 
192
196
  self._dst_saved = self._dst_offset - self._std_offset
193
197
  self._hasdst = bool(self._dst_saved)
194
- self._tznames = tuple(time.tzname)
198
+ self._tznames = time.tzname
195
199
 
196
200
  def utcoffset(self, dt):
197
201
  if dt is None and self._hasdst:
@@ -1231,6 +1235,9 @@ class TzIcal:
1231
1235
  else:
1232
1236
  signal = +1
1233
1237
 
1238
+ if not (s.isascii() and s.isdigit()):
1239
+ raise ValueError("invalid offset: " + s)
1240
+
1234
1241
  if len(s) == 4:
1235
1242
  return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal
1236
1243
  if len(s) == 6:
@@ -1537,7 +1544,7 @@ def __get_gettz():
1537
1544
  # one offset. For short values of "name", an
1538
1545
  # explicit for loop seems to be the fastest way
1539
1546
  # To determine if a string contains a digit
1540
- if c in "0123456789":
1547
+ if c in string.digits:
1541
1548
  with contextlib.suppress(ValueError):
1542
1549
  tz = TzStr(name)
1543
1550
  break
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes