dl1wjd2 commited on
Commit
bbae060
·
verified ·
1 Parent(s): f5fb003

Upload folder using huggingface_hub (part 4)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +11 -0
  2. hug/lib/python3.12/site-packages/dateutil/parser/__init__.py +61 -0
  3. hug/lib/python3.12/site-packages/dateutil/parser/__pycache__/__init__.cpython-312.pyc +0 -0
  4. hug/lib/python3.12/site-packages/dateutil/parser/__pycache__/_parser.cpython-312.pyc +0 -0
  5. hug/lib/python3.12/site-packages/dateutil/parser/__pycache__/isoparser.cpython-312.pyc +0 -0
  6. hug/lib/python3.12/site-packages/dateutil/parser/_parser.py +1613 -0
  7. hug/lib/python3.12/site-packages/dateutil/parser/isoparser.py +416 -0
  8. hug/lib/python3.12/site-packages/dateutil/relativedelta.py +596 -0
  9. hug/lib/python3.12/site-packages/dateutil/rrule.py +1764 -0
  10. hug/lib/python3.12/site-packages/dateutil/tz/__init__.py +60 -0
  11. hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/__init__.cpython-312.pyc +0 -0
  12. hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/_common.cpython-312.pyc +0 -0
  13. hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/_factories.cpython-312.pyc +0 -0
  14. hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/_tzfile.cpython-312.pyc +0 -0
  15. hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/_tzpath.cpython-312.pyc +0 -0
  16. hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/tz.cpython-312.pyc +0 -0
  17. hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/win.cpython-312.pyc +0 -0
  18. hug/lib/python3.12/site-packages/dateutil/tz/_common.py +419 -0
  19. hug/lib/python3.12/site-packages/dateutil/tz/_factories.py +80 -0
  20. hug/lib/python3.12/site-packages/dateutil/tz/_tzfile.py +1428 -0
  21. hug/lib/python3.12/site-packages/dateutil/tz/_tzpath.py +104 -0
  22. hug/lib/python3.12/site-packages/dateutil/tz/tz.py +1472 -0
  23. hug/lib/python3.12/site-packages/dateutil/tz/win.py +370 -0
  24. hug/lib/python3.12/site-packages/dateutil/tzwin.py +2 -0
  25. hug/lib/python3.12/site-packages/dateutil/utils.py +71 -0
  26. hug/lib/python3.12/site-packages/dateutil/zoneinfo/__init__.py +233 -0
  27. hug/lib/python3.12/site-packages/dateutil/zoneinfo/__pycache__/__init__.cpython-312.pyc +0 -0
  28. hug/lib/python3.12/site-packages/dateutil/zoneinfo/__pycache__/rebuild.cpython-312.pyc +0 -0
  29. hug/lib/python3.12/site-packages/dateutil/zoneinfo/rebuild.py +42 -0
  30. hug/lib/python3.12/site-packages/distutils-precedence.pth +3 -0
  31. hug/lib/python3.12/site-packages/example.py +170 -0
  32. hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/INSTALLER +1 -0
  33. hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/METADATA +38 -0
  34. hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/RECORD +52 -0
  35. hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/WHEEL +4 -0
  36. hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/licenses/LICENSE +21 -0
  37. hug/lib/python3.12/site-packages/filelock/__init__.py +174 -0
  38. hug/lib/python3.12/site-packages/filelock/__pycache__/__init__.cpython-312.pyc +0 -0
  39. hug/lib/python3.12/site-packages/filelock/__pycache__/_api.cpython-312.pyc +3 -0
  40. hug/lib/python3.12/site-packages/filelock/__pycache__/_async.cpython-312.pyc +0 -0
  41. hug/lib/python3.12/site-packages/filelock/__pycache__/_async_read_write.cpython-312.pyc +0 -0
  42. hug/lib/python3.12/site-packages/filelock/__pycache__/_descriptor.cpython-312.pyc +0 -0
  43. hug/lib/python3.12/site-packages/filelock/__pycache__/_error.cpython-312.pyc +0 -0
  44. hug/lib/python3.12/site-packages/filelock/__pycache__/_identity.cpython-312.pyc +0 -0
  45. hug/lib/python3.12/site-packages/filelock/__pycache__/_lease.cpython-312.pyc +0 -0
  46. hug/lib/python3.12/site-packages/filelock/__pycache__/_marker.cpython-312.pyc +0 -0
  47. hug/lib/python3.12/site-packages/filelock/__pycache__/_read_write.cpython-312.pyc +0 -0
  48. hug/lib/python3.12/site-packages/filelock/__pycache__/_soft.cpython-312.pyc +0 -0
  49. hug/lib/python3.12/site-packages/filelock/__pycache__/_soft_protocol.cpython-312.pyc +0 -0
  50. hug/lib/python3.12/site-packages/filelock/__pycache__/_strict.cpython-312.pyc +0 -0
.gitattributes CHANGED
@@ -71,3 +71,14 @@ hug/lib/python3.12/site-packages/cuda/bindings/nvrtc.cpython-312-x86_64-linux-gn
71
  hug/lib/python3.12/site-packages/cuda/bindings/nvvm.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
72
  hug/lib/python3.12/site-packages/cuda/bindings/runtime.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
73
  hug/lib/python3.12/site-packages/cv2/cv2.abi3.so filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
71
  hug/lib/python3.12/site-packages/cuda/bindings/nvvm.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
72
  hug/lib/python3.12/site-packages/cuda/bindings/runtime.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
73
  hug/lib/python3.12/site-packages/cv2/cv2.abi3.so filter=lfs diff=lfs merge=lfs -text
74
+ hug/lib/python3.12/site-packages/filelock/__pycache__/_api.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
75
+ hug/lib/python3.12/site-packages/fontTools/__pycache__/agl.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
76
+ hug/lib/python3.12/site-packages/fontTools/cffLib/__pycache__/__init__.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
77
+ hug/lib/python3.12/site-packages/fontTools/cu2qu/cu2qu.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
78
+ hug/lib/python3.12/site-packages/fontTools/designspaceLib/__pycache__/__init__.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
79
+ hug/lib/python3.12/site-packages/fontTools/feaLib/__pycache__/ast.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
80
+ hug/lib/python3.12/site-packages/fontTools/feaLib/__pycache__/parser.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
81
+ hug/lib/python3.12/site-packages/fontTools/feaLib/lexer.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
82
+ hug/lib/python3.12/site-packages/fontTools/misc/bezierTools.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
83
+ hug/lib/python3.12/site-packages/fontTools/otlLib/__pycache__/builder.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
84
+ hug/lib/python3.12/site-packages/fontTools/pens/momentsPen.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
hug/lib/python3.12/site-packages/dateutil/parser/__init__.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ from ._parser import parse, parser, parserinfo, ParserError
3
+ from ._parser import DEFAULTPARSER, DEFAULTTZPARSER
4
+ from ._parser import UnknownTimezoneWarning
5
+
6
+ from ._parser import __doc__
7
+
8
+ from .isoparser import isoparser, isoparse
9
+
10
+ __all__ = ['parse', 'parser', 'parserinfo',
11
+ 'isoparse', 'isoparser',
12
+ 'ParserError',
13
+ 'UnknownTimezoneWarning']
14
+
15
+
16
+ ###
17
+ # Deprecate portions of the private interface so that downstream code that
18
+ # is improperly relying on it is given *some* notice.
19
+
20
+
21
+ def __deprecated_private_func(f):
22
+ from functools import wraps
23
+ import warnings
24
+
25
+ msg = ('{name} is a private function and may break without warning, '
26
+ 'it will be moved and or renamed in future versions.')
27
+ msg = msg.format(name=f.__name__)
28
+
29
+ @wraps(f)
30
+ def deprecated_func(*args, **kwargs):
31
+ warnings.warn(msg, DeprecationWarning)
32
+ return f(*args, **kwargs)
33
+
34
+ return deprecated_func
35
+
36
+ def __deprecate_private_class(c):
37
+ import warnings
38
+
39
+ msg = ('{name} is a private class and may break without warning, '
40
+ 'it will be moved and or renamed in future versions.')
41
+ msg = msg.format(name=c.__name__)
42
+
43
+ class private_class(c):
44
+ __doc__ = c.__doc__
45
+
46
+ def __init__(self, *args, **kwargs):
47
+ warnings.warn(msg, DeprecationWarning)
48
+ super(private_class, self).__init__(*args, **kwargs)
49
+
50
+ private_class.__name__ = c.__name__
51
+
52
+ return private_class
53
+
54
+
55
+ from ._parser import _timelex, _resultbase
56
+ from ._parser import _tzparser, _parsetz
57
+
58
+ _timelex = __deprecate_private_class(_timelex)
59
+ _tzparser = __deprecate_private_class(_tzparser)
60
+ _resultbase = __deprecate_private_class(_resultbase)
61
+ _parsetz = __deprecated_private_func(_parsetz)
hug/lib/python3.12/site-packages/dateutil/parser/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (2.65 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/parser/__pycache__/_parser.cpython-312.pyc ADDED
Binary file (61.8 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/parser/__pycache__/isoparser.cpython-312.pyc ADDED
Binary file (15.6 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/parser/_parser.py ADDED
@@ -0,0 +1,1613 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ This module offers a generic date/time string parser which is able to parse
4
+ most known formats to represent a date and/or time.
5
+
6
+ This module attempts to be forgiving with regards to unlikely input formats,
7
+ returning a datetime object even for dates which are ambiguous. If an element
8
+ of a date/time stamp is omitted, the following rules are applied:
9
+
10
+ - If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour
11
+ on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is
12
+ specified.
13
+ - If a time zone is omitted, a timezone-naive datetime is returned.
14
+
15
+ If any other elements are missing, they are taken from the
16
+ :class:`datetime.datetime` object passed to the parameter ``default``. If this
17
+ results in a day number exceeding the valid number of days per month, the
18
+ value falls back to the end of the month.
19
+
20
+ Additional resources about date/time string formats can be found below:
21
+
22
+ - `A summary of the international standard date and time notation
23
+ <https://www.cl.cam.ac.uk/~mgk25/iso-time.html>`_
24
+ - `W3C Date and Time Formats <https://www.w3.org/TR/NOTE-datetime>`_
25
+ - `Time Formats (Planetary Rings Node) <https://pds-rings.seti.org:443/tools/time_formats.html>`_
26
+ - `CPAN ParseDate module
27
+ <https://metacpan.org/pod/release/MUIR/Time-modules-2013.0912/lib/Time/ParseDate.pm>`_
28
+ - `Java SimpleDateFormat Class
29
+ <https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>`_
30
+ """
31
+ from __future__ import unicode_literals
32
+
33
+ import datetime
34
+ import re
35
+ import string
36
+ import time
37
+ import warnings
38
+
39
+ from calendar import monthrange
40
+ from io import StringIO
41
+
42
+ import six
43
+ from six import integer_types, text_type
44
+
45
+ from decimal import Decimal
46
+
47
+ from warnings import warn
48
+
49
+ from .. import relativedelta
50
+ from .. import tz
51
+
52
+ __all__ = ["parse", "parserinfo", "ParserError"]
53
+
54
+
55
+ # TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth
56
+ # making public and/or figuring out if there is something we can
57
+ # take off their plate.
58
+ class _timelex(object):
59
+ # Fractional seconds are sometimes split by a comma
60
+ _split_decimal = re.compile("([.,])")
61
+
62
+ def __init__(self, instream):
63
+ if isinstance(instream, (bytes, bytearray)):
64
+ instream = instream.decode()
65
+
66
+ if isinstance(instream, text_type):
67
+ instream = StringIO(instream)
68
+ elif getattr(instream, 'read', None) is None:
69
+ raise TypeError('Parser must be a string or character stream, not '
70
+ '{itype}'.format(itype=instream.__class__.__name__))
71
+
72
+ self.instream = instream
73
+ self.charstack = []
74
+ self.tokenstack = []
75
+ self.eof = False
76
+
77
+ def get_token(self):
78
+ """
79
+ This function breaks the time string into lexical units (tokens), which
80
+ can be parsed by the parser. Lexical units are demarcated by changes in
81
+ the character set, so any continuous string of letters is considered
82
+ one unit, any continuous string of numbers is considered one unit.
83
+
84
+ The main complication arises from the fact that dots ('.') can be used
85
+ both as separators (e.g. "Sep.20.2009") or decimal points (e.g.
86
+ "4:30:21.447"). As such, it is necessary to read the full context of
87
+ any dot-separated strings before breaking it into tokens; as such, this
88
+ function maintains a "token stack", for when the ambiguous context
89
+ demands that multiple tokens be parsed at once.
90
+ """
91
+ if self.tokenstack:
92
+ return self.tokenstack.pop(0)
93
+
94
+ seenletters = False
95
+ token = None
96
+ state = None
97
+
98
+ while not self.eof:
99
+ # We only realize that we've reached the end of a token when we
100
+ # find a character that's not part of the current token - since
101
+ # that character may be part of the next token, it's stored in the
102
+ # charstack.
103
+ if self.charstack:
104
+ nextchar = self.charstack.pop(0)
105
+ else:
106
+ nextchar = self.instream.read(1)
107
+ while nextchar == '\x00':
108
+ nextchar = self.instream.read(1)
109
+
110
+ if not nextchar:
111
+ self.eof = True
112
+ break
113
+ elif not state:
114
+ # First character of the token - determines if we're starting
115
+ # to parse a word, a number or something else.
116
+ token = nextchar
117
+ if self.isword(nextchar):
118
+ state = 'a'
119
+ elif self.isnum(nextchar):
120
+ state = '0'
121
+ elif self.isspace(nextchar):
122
+ token = ' '
123
+ break # emit token
124
+ else:
125
+ break # emit token
126
+ elif state == 'a':
127
+ # If we've already started reading a word, we keep reading
128
+ # letters until we find something that's not part of a word.
129
+ seenletters = True
130
+ if self.isword(nextchar):
131
+ token += nextchar
132
+ elif nextchar == '.':
133
+ token += nextchar
134
+ state = 'a.'
135
+ else:
136
+ self.charstack.append(nextchar)
137
+ break # emit token
138
+ elif state == '0':
139
+ # If we've already started reading a number, we keep reading
140
+ # numbers until we find something that doesn't fit.
141
+ if self.isnum(nextchar):
142
+ token += nextchar
143
+ elif nextchar == '.' or (nextchar == ',' and len(token) >= 2):
144
+ token += nextchar
145
+ state = '0.'
146
+ else:
147
+ self.charstack.append(nextchar)
148
+ break # emit token
149
+ elif state == 'a.':
150
+ # If we've seen some letters and a dot separator, continue
151
+ # parsing, and the tokens will be broken up later.
152
+ seenletters = True
153
+ if nextchar == '.' or self.isword(nextchar):
154
+ token += nextchar
155
+ elif self.isnum(nextchar) and token[-1] == '.':
156
+ token += nextchar
157
+ state = '0.'
158
+ else:
159
+ self.charstack.append(nextchar)
160
+ break # emit token
161
+ elif state == '0.':
162
+ # If we've seen at least one dot separator, keep going, we'll
163
+ # break up the tokens later.
164
+ if nextchar == '.' or self.isnum(nextchar):
165
+ token += nextchar
166
+ elif self.isword(nextchar) and token[-1] == '.':
167
+ token += nextchar
168
+ state = 'a.'
169
+ else:
170
+ self.charstack.append(nextchar)
171
+ break # emit token
172
+
173
+ if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or
174
+ token[-1] in '.,')):
175
+ l = self._split_decimal.split(token)
176
+ token = l[0]
177
+ for tok in l[1:]:
178
+ if tok:
179
+ self.tokenstack.append(tok)
180
+
181
+ if state == '0.' and token.count('.') == 0:
182
+ token = token.replace(',', '.')
183
+
184
+ return token
185
+
186
+ def __iter__(self):
187
+ return self
188
+
189
+ def __next__(self):
190
+ token = self.get_token()
191
+ if token is None:
192
+ raise StopIteration
193
+
194
+ return token
195
+
196
+ def next(self):
197
+ return self.__next__() # Python 2.x support
198
+
199
+ @classmethod
200
+ def split(cls, s):
201
+ return list(cls(s))
202
+
203
+ @classmethod
204
+ def isword(cls, nextchar):
205
+ """ Whether or not the next character is part of a word """
206
+ return nextchar.isalpha()
207
+
208
+ @classmethod
209
+ def isnum(cls, nextchar):
210
+ """ Whether the next character is part of a number """
211
+ return nextchar.isdigit()
212
+
213
+ @classmethod
214
+ def isspace(cls, nextchar):
215
+ """ Whether the next character is whitespace """
216
+ return nextchar.isspace()
217
+
218
+
219
+ class _resultbase(object):
220
+
221
+ def __init__(self):
222
+ for attr in self.__slots__:
223
+ setattr(self, attr, None)
224
+
225
+ def _repr(self, classname):
226
+ l = []
227
+ for attr in self.__slots__:
228
+ value = getattr(self, attr)
229
+ if value is not None:
230
+ l.append("%s=%s" % (attr, repr(value)))
231
+ return "%s(%s)" % (classname, ", ".join(l))
232
+
233
+ def __len__(self):
234
+ return (sum(getattr(self, attr) is not None
235
+ for attr in self.__slots__))
236
+
237
+ def __repr__(self):
238
+ return self._repr(self.__class__.__name__)
239
+
240
+
241
+ class parserinfo(object):
242
+ """
243
+ Class which handles what inputs are accepted. Subclass this to customize
244
+ the language and acceptable values for each parameter.
245
+
246
+ :param dayfirst:
247
+ Whether to interpret the first value in an ambiguous 3-integer date
248
+ (e.g. 01/05/09) as the day (``True``) or month (``False``). If
249
+ ``yearfirst`` is set to ``True``, this distinguishes between YDM
250
+ and YMD. Default is ``False``.
251
+
252
+ :param yearfirst:
253
+ Whether to interpret the first value in an ambiguous 3-integer date
254
+ (e.g. 01/05/09) as the year. If ``True``, the first number is taken
255
+ to be the year, otherwise the last number is taken to be the year.
256
+ Default is ``False``.
257
+ """
258
+
259
+ # m from a.m/p.m, t from ISO T separator
260
+ JUMP = [" ", ".", ",", ";", "-", "/", "'",
261
+ "at", "on", "and", "ad", "m", "t", "of",
262
+ "st", "nd", "rd", "th"]
263
+
264
+ WEEKDAYS = [("Mon", "Monday"),
265
+ ("Tue", "Tuesday"), # TODO: "Tues"
266
+ ("Wed", "Wednesday"),
267
+ ("Thu", "Thursday"), # TODO: "Thurs"
268
+ ("Fri", "Friday"),
269
+ ("Sat", "Saturday"),
270
+ ("Sun", "Sunday")]
271
+ MONTHS = [("Jan", "January"),
272
+ ("Feb", "February"), # TODO: "Febr"
273
+ ("Mar", "March"),
274
+ ("Apr", "April"),
275
+ ("May", "May"),
276
+ ("Jun", "June"),
277
+ ("Jul", "July"),
278
+ ("Aug", "August"),
279
+ ("Sep", "Sept", "September"),
280
+ ("Oct", "October"),
281
+ ("Nov", "November"),
282
+ ("Dec", "December")]
283
+ HMS = [("h", "hour", "hours"),
284
+ ("m", "minute", "minutes"),
285
+ ("s", "second", "seconds")]
286
+ AMPM = [("am", "a"),
287
+ ("pm", "p")]
288
+ UTCZONE = ["UTC", "GMT", "Z", "z"]
289
+ PERTAIN = ["of"]
290
+ TZOFFSET = {}
291
+ # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate",
292
+ # "Anno Domini", "Year of Our Lord"]
293
+
294
+ def __init__(self, dayfirst=False, yearfirst=False):
295
+ self._jump = self._convert(self.JUMP)
296
+ self._weekdays = self._convert(self.WEEKDAYS)
297
+ self._months = self._convert(self.MONTHS)
298
+ self._hms = self._convert(self.HMS)
299
+ self._ampm = self._convert(self.AMPM)
300
+ self._utczone = self._convert(self.UTCZONE)
301
+ self._pertain = self._convert(self.PERTAIN)
302
+
303
+ self.dayfirst = dayfirst
304
+ self.yearfirst = yearfirst
305
+
306
+ self._year = time.localtime().tm_year
307
+ self._century = self._year // 100 * 100
308
+
309
+ def _convert(self, lst):
310
+ dct = {}
311
+ for i, v in enumerate(lst):
312
+ if isinstance(v, tuple):
313
+ for v in v:
314
+ dct[v.lower()] = i
315
+ else:
316
+ dct[v.lower()] = i
317
+ return dct
318
+
319
+ def jump(self, name):
320
+ return name.lower() in self._jump
321
+
322
+ def weekday(self, name):
323
+ try:
324
+ return self._weekdays[name.lower()]
325
+ except KeyError:
326
+ pass
327
+ return None
328
+
329
+ def month(self, name):
330
+ try:
331
+ return self._months[name.lower()] + 1
332
+ except KeyError:
333
+ pass
334
+ return None
335
+
336
+ def hms(self, name):
337
+ try:
338
+ return self._hms[name.lower()]
339
+ except KeyError:
340
+ return None
341
+
342
+ def ampm(self, name):
343
+ try:
344
+ return self._ampm[name.lower()]
345
+ except KeyError:
346
+ return None
347
+
348
+ def pertain(self, name):
349
+ return name.lower() in self._pertain
350
+
351
+ def utczone(self, name):
352
+ return name.lower() in self._utczone
353
+
354
+ def tzoffset(self, name):
355
+ if name in self._utczone:
356
+ return 0
357
+
358
+ return self.TZOFFSET.get(name)
359
+
360
+ def convertyear(self, year, century_specified=False):
361
+ """
362
+ Converts two-digit years to year within [-50, 49]
363
+ range of self._year (current local time)
364
+ """
365
+
366
+ # Function contract is that the year is always positive
367
+ assert year >= 0
368
+
369
+ if year < 100 and not century_specified:
370
+ # assume current century to start
371
+ year += self._century
372
+
373
+ if year >= self._year + 50: # if too far in future
374
+ year -= 100
375
+ elif year < self._year - 50: # if too far in past
376
+ year += 100
377
+
378
+ return year
379
+
380
+ def validate(self, res):
381
+ # move to info
382
+ if res.year is not None:
383
+ res.year = self.convertyear(res.year, res.century_specified)
384
+
385
+ if ((res.tzoffset == 0 and not res.tzname) or
386
+ (res.tzname == 'Z' or res.tzname == 'z')):
387
+ res.tzname = "UTC"
388
+ res.tzoffset = 0
389
+ elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname):
390
+ res.tzoffset = 0
391
+ return True
392
+
393
+
394
+ class _ymd(list):
395
+ def __init__(self, *args, **kwargs):
396
+ super(self.__class__, self).__init__(*args, **kwargs)
397
+ self.century_specified = False
398
+ self.dstridx = None
399
+ self.mstridx = None
400
+ self.ystridx = None
401
+
402
+ @property
403
+ def has_year(self):
404
+ return self.ystridx is not None
405
+
406
+ @property
407
+ def has_month(self):
408
+ return self.mstridx is not None
409
+
410
+ @property
411
+ def has_day(self):
412
+ return self.dstridx is not None
413
+
414
+ def could_be_day(self, value):
415
+ if self.has_day:
416
+ return False
417
+ elif not self.has_month:
418
+ return 1 <= value <= 31
419
+ elif not self.has_year:
420
+ # Be permissive, assume leap year
421
+ month = self[self.mstridx]
422
+ return 1 <= value <= monthrange(2000, month)[1]
423
+ else:
424
+ month = self[self.mstridx]
425
+ year = self[self.ystridx]
426
+ return 1 <= value <= monthrange(year, month)[1]
427
+
428
+ def append(self, val, label=None):
429
+ if hasattr(val, '__len__'):
430
+ if val.isdigit() and len(val) > 2:
431
+ self.century_specified = True
432
+ if label not in [None, 'Y']: # pragma: no cover
433
+ raise ValueError(label)
434
+ label = 'Y'
435
+ elif val > 100:
436
+ self.century_specified = True
437
+ if label not in [None, 'Y']: # pragma: no cover
438
+ raise ValueError(label)
439
+ label = 'Y'
440
+
441
+ super(self.__class__, self).append(int(val))
442
+
443
+ if label == 'M':
444
+ if self.has_month:
445
+ raise ValueError('Month is already set')
446
+ self.mstridx = len(self) - 1
447
+ elif label == 'D':
448
+ if self.has_day:
449
+ raise ValueError('Day is already set')
450
+ self.dstridx = len(self) - 1
451
+ elif label == 'Y':
452
+ if self.has_year:
453
+ raise ValueError('Year is already set')
454
+ self.ystridx = len(self) - 1
455
+
456
+ def _resolve_from_stridxs(self, strids):
457
+ """
458
+ Try to resolve the identities of year/month/day elements using
459
+ ystridx, mstridx, and dstridx, if enough of these are specified.
460
+ """
461
+ if len(self) == 3 and len(strids) == 2:
462
+ # we can back out the remaining stridx value
463
+ missing = [x for x in range(3) if x not in strids.values()]
464
+ key = [x for x in ['y', 'm', 'd'] if x not in strids]
465
+ assert len(missing) == len(key) == 1
466
+ key = key[0]
467
+ val = missing[0]
468
+ strids[key] = val
469
+
470
+ assert len(self) == len(strids) # otherwise this should not be called
471
+ out = {key: self[strids[key]] for key in strids}
472
+ return (out.get('y'), out.get('m'), out.get('d'))
473
+
474
+ def resolve_ymd(self, yearfirst, dayfirst):
475
+ len_ymd = len(self)
476
+ year, month, day = (None, None, None)
477
+
478
+ strids = (('y', self.ystridx),
479
+ ('m', self.mstridx),
480
+ ('d', self.dstridx))
481
+
482
+ strids = {key: val for key, val in strids if val is not None}
483
+ if (len(self) == len(strids) > 0 or
484
+ (len(self) == 3 and len(strids) == 2)):
485
+ return self._resolve_from_stridxs(strids)
486
+
487
+ mstridx = self.mstridx
488
+
489
+ if len_ymd > 3:
490
+ raise ValueError("More than three YMD values")
491
+ elif len_ymd == 1 or (mstridx is not None and len_ymd == 2):
492
+ # One member, or two members with a month string
493
+ if mstridx is not None:
494
+ month = self[mstridx]
495
+ # since mstridx is 0 or 1, self[mstridx-1] always
496
+ # looks up the other element
497
+ other = self[mstridx - 1]
498
+ else:
499
+ other = self[0]
500
+
501
+ if len_ymd > 1 or mstridx is None:
502
+ if other > 31:
503
+ year = other
504
+ else:
505
+ day = other
506
+
507
+ elif len_ymd == 2:
508
+ # Two members with numbers
509
+ if self[0] > 31:
510
+ # 99-01
511
+ year, month = self
512
+ elif self[1] > 31:
513
+ # 01-99
514
+ month, year = self
515
+ elif dayfirst and self[1] <= 12:
516
+ # 13-01
517
+ day, month = self
518
+ else:
519
+ # 01-13
520
+ month, day = self
521
+
522
+ elif len_ymd == 3:
523
+ # Three members
524
+ if mstridx == 0:
525
+ if self[1] > 31:
526
+ # Apr-2003-25
527
+ month, year, day = self
528
+ else:
529
+ month, day, year = self
530
+ elif mstridx == 1:
531
+ if self[0] > 31 or (yearfirst and self[2] <= 31):
532
+ # 99-Jan-01
533
+ year, month, day = self
534
+ else:
535
+ # 01-Jan-01
536
+ # Give precedence to day-first, since
537
+ # two-digit years is usually hand-written.
538
+ day, month, year = self
539
+
540
+ elif mstridx == 2:
541
+ # WTF!?
542
+ if self[1] > 31:
543
+ # 01-99-Jan
544
+ day, year, month = self
545
+ else:
546
+ # 99-01-Jan
547
+ year, day, month = self
548
+
549
+ else:
550
+ if (self[0] > 31 or
551
+ self.ystridx == 0 or
552
+ (yearfirst and self[1] <= 12 and self[2] <= 31)):
553
+ # 99-01-01
554
+ if dayfirst and self[2] <= 12:
555
+ year, day, month = self
556
+ else:
557
+ year, month, day = self
558
+ elif self[0] > 12 or (dayfirst and self[1] <= 12):
559
+ # 13-01-01
560
+ day, month, year = self
561
+ else:
562
+ # 01-13-01
563
+ month, day, year = self
564
+
565
+ return year, month, day
566
+
567
+
568
+ class parser(object):
569
+ def __init__(self, info=None):
570
+ self.info = info or parserinfo()
571
+
572
+ def parse(self, timestr, default=None,
573
+ ignoretz=False, tzinfos=None, **kwargs):
574
+ """
575
+ Parse the date/time string into a :class:`datetime.datetime` object.
576
+
577
+ :param timestr:
578
+ Any date/time string using the supported formats.
579
+
580
+ :param default:
581
+ The default datetime object, if this is a datetime object and not
582
+ ``None``, elements specified in ``timestr`` replace elements in the
583
+ default object.
584
+
585
+ :param ignoretz:
586
+ If set ``True``, time zones in parsed strings are ignored and a
587
+ naive :class:`datetime.datetime` object is returned.
588
+
589
+ :param tzinfos:
590
+ Additional time zone names / aliases which may be present in the
591
+ string. This argument maps time zone names (and optionally offsets
592
+ from those time zones) to time zones. This parameter can be a
593
+ dictionary with timezone aliases mapping time zone names to time
594
+ zones or a function taking two parameters (``tzname`` and
595
+ ``tzoffset``) and returning a time zone.
596
+
597
+ The timezones to which the names are mapped can be an integer
598
+ offset from UTC in seconds or a :class:`tzinfo` object.
599
+
600
+ .. doctest::
601
+ :options: +NORMALIZE_WHITESPACE
602
+
603
+ >>> from dateutil.parser import parse
604
+ >>> from dateutil.tz import gettz
605
+ >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")}
606
+ >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
607
+ datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200))
608
+ >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
609
+ datetime.datetime(2012, 1, 19, 17, 21,
610
+ tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
611
+
612
+ This parameter is ignored if ``ignoretz`` is set.
613
+
614
+ :param \\*\\*kwargs:
615
+ Keyword arguments as passed to ``_parse()``.
616
+
617
+ :return:
618
+ Returns a :class:`datetime.datetime` object or, if the
619
+ ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
620
+ first element being a :class:`datetime.datetime` object, the second
621
+ a tuple containing the fuzzy tokens.
622
+
623
+ :raises ParserError:
624
+ Raised for invalid or unknown string format, if the provided
625
+ :class:`tzinfo` is not in a valid format, or if an invalid date
626
+ would be created.
627
+
628
+ :raises TypeError:
629
+ Raised for non-string or character stream input.
630
+
631
+ :raises OverflowError:
632
+ Raised if the parsed date exceeds the largest valid C integer on
633
+ your system.
634
+ """
635
+
636
+ if default is None:
637
+ default = datetime.datetime.now().replace(hour=0, minute=0,
638
+ second=0, microsecond=0)
639
+
640
+ res, skipped_tokens = self._parse(timestr, **kwargs)
641
+
642
+ if res is None:
643
+ raise ParserError("Unknown string format: %s", timestr)
644
+
645
+ if len(res) == 0:
646
+ raise ParserError("String does not contain a date: %s", timestr)
647
+
648
+ try:
649
+ ret = self._build_naive(res, default)
650
+ except ValueError as e:
651
+ six.raise_from(ParserError(str(e) + ": %s", timestr), e)
652
+
653
+ if not ignoretz:
654
+ ret = self._build_tzaware(ret, res, tzinfos)
655
+
656
+ if kwargs.get('fuzzy_with_tokens', False):
657
+ return ret, skipped_tokens
658
+ else:
659
+ return ret
660
+
661
+ class _result(_resultbase):
662
+ __slots__ = ["year", "month", "day", "weekday",
663
+ "hour", "minute", "second", "microsecond",
664
+ "tzname", "tzoffset", "ampm","any_unused_tokens"]
665
+
666
+ def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False,
667
+ fuzzy_with_tokens=False):
668
+ """
669
+ Private method which performs the heavy lifting of parsing, called from
670
+ ``parse()``, which passes on its ``kwargs`` to this function.
671
+
672
+ :param timestr:
673
+ The string to parse.
674
+
675
+ :param dayfirst:
676
+ Whether to interpret the first value in an ambiguous 3-integer date
677
+ (e.g. 01/05/09) as the day (``True``) or month (``False``). If
678
+ ``yearfirst`` is set to ``True``, this distinguishes between YDM
679
+ and YMD. If set to ``None``, this value is retrieved from the
680
+ current :class:`parserinfo` object (which itself defaults to
681
+ ``False``).
682
+
683
+ :param yearfirst:
684
+ Whether to interpret the first value in an ambiguous 3-integer date
685
+ (e.g. 01/05/09) as the year. If ``True``, the first number is taken
686
+ to be the year, otherwise the last number is taken to be the year.
687
+ If this is set to ``None``, the value is retrieved from the current
688
+ :class:`parserinfo` object (which itself defaults to ``False``).
689
+
690
+ :param fuzzy:
691
+ Whether to allow fuzzy parsing, allowing for string like "Today is
692
+ January 1, 2047 at 8:21:00AM".
693
+
694
+ :param fuzzy_with_tokens:
695
+ If ``True``, ``fuzzy`` is automatically set to True, and the parser
696
+ will return a tuple where the first element is the parsed
697
+ :class:`datetime.datetime` datetimestamp and the second element is
698
+ a tuple containing the portions of the string which were ignored:
699
+
700
+ .. doctest::
701
+
702
+ >>> from dateutil.parser import parse
703
+ >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
704
+ (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
705
+
706
+ """
707
+ if fuzzy_with_tokens:
708
+ fuzzy = True
709
+
710
+ info = self.info
711
+
712
+ if dayfirst is None:
713
+ dayfirst = info.dayfirst
714
+
715
+ if yearfirst is None:
716
+ yearfirst = info.yearfirst
717
+
718
+ res = self._result()
719
+ l = _timelex.split(timestr) # Splits the timestr into tokens
720
+
721
+ skipped_idxs = []
722
+
723
+ # year/month/day list
724
+ ymd = _ymd()
725
+
726
+ len_l = len(l)
727
+ i = 0
728
+ try:
729
+ while i < len_l:
730
+
731
+ # Check if it's a number
732
+ value_repr = l[i]
733
+ try:
734
+ value = float(value_repr)
735
+ except ValueError:
736
+ value = None
737
+
738
+ if value is not None:
739
+ # Numeric token
740
+ i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy)
741
+
742
+ # Check weekday
743
+ elif info.weekday(l[i]) is not None:
744
+ value = info.weekday(l[i])
745
+ res.weekday = value
746
+
747
+ # Check month name
748
+ elif info.month(l[i]) is not None:
749
+ value = info.month(l[i])
750
+ ymd.append(value, 'M')
751
+
752
+ if i + 1 < len_l:
753
+ if l[i + 1] in ('-', '/'):
754
+ # Jan-01[-99]
755
+ sep = l[i + 1]
756
+ ymd.append(l[i + 2])
757
+
758
+ if i + 3 < len_l and l[i + 3] == sep:
759
+ # Jan-01-99
760
+ ymd.append(l[i + 4])
761
+ i += 2
762
+
763
+ i += 2
764
+
765
+ elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and
766
+ info.pertain(l[i + 2])):
767
+ # Jan of 01
768
+ # In this case, 01 is clearly year
769
+ if l[i + 4].isdigit():
770
+ # Convert it here to become unambiguous
771
+ value = int(l[i + 4])
772
+ year = str(info.convertyear(value))
773
+ ymd.append(year, 'Y')
774
+ else:
775
+ # Wrong guess
776
+ pass
777
+ # TODO: not hit in tests
778
+ i += 4
779
+
780
+ # Check am/pm
781
+ elif info.ampm(l[i]) is not None:
782
+ value = info.ampm(l[i])
783
+ val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy)
784
+
785
+ if val_is_ampm:
786
+ res.hour = self._adjust_ampm(res.hour, value)
787
+ res.ampm = value
788
+
789
+ elif fuzzy:
790
+ skipped_idxs.append(i)
791
+
792
+ # Check for a timezone name
793
+ elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]):
794
+ res.tzname = l[i]
795
+ res.tzoffset = info.tzoffset(res.tzname)
796
+
797
+ # Check for something like GMT+3, or BRST+3. Notice
798
+ # that it doesn't mean "I am 3 hours after GMT", but
799
+ # "my time +3 is GMT". If found, we reverse the
800
+ # logic so that timezone parsing code will get it
801
+ # right.
802
+ if i + 1 < len_l and l[i + 1] in ('+', '-'):
803
+ l[i + 1] = ('+', '-')[l[i + 1] == '+']
804
+ res.tzoffset = None
805
+ if info.utczone(res.tzname):
806
+ # With something like GMT+3, the timezone
807
+ # is *not* GMT.
808
+ res.tzname = None
809
+
810
+ # Check for a numbered timezone
811
+ elif res.hour is not None and l[i] in ('+', '-'):
812
+ signal = (-1, 1)[l[i] == '+']
813
+ len_li = len(l[i + 1])
814
+
815
+ # TODO: check that l[i + 1] is integer?
816
+ if len_li == 4:
817
+ # -0300
818
+ hour_offset = int(l[i + 1][:2])
819
+ min_offset = int(l[i + 1][2:])
820
+ elif i + 2 < len_l and l[i + 2] == ':':
821
+ # -03:00
822
+ hour_offset = int(l[i + 1])
823
+ min_offset = int(l[i + 3]) # TODO: Check that l[i+3] is minute-like?
824
+ i += 2
825
+ elif len_li <= 2:
826
+ # -[0]3
827
+ hour_offset = int(l[i + 1][:2])
828
+ min_offset = 0
829
+ else:
830
+ raise ValueError(timestr)
831
+
832
+ res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60)
833
+
834
+ # Look for a timezone name between parenthesis
835
+ if (i + 5 < len_l and
836
+ info.jump(l[i + 2]) and l[i + 3] == '(' and
837
+ l[i + 5] == ')' and
838
+ 3 <= len(l[i + 4]) and
839
+ self._could_be_tzname(res.hour, res.tzname,
840
+ None, l[i + 4])):
841
+ # -0300 (BRST)
842
+ res.tzname = l[i + 4]
843
+ i += 4
844
+
845
+ i += 1
846
+
847
+ # Check jumps
848
+ elif not (info.jump(l[i]) or fuzzy):
849
+ raise ValueError(timestr)
850
+
851
+ else:
852
+ skipped_idxs.append(i)
853
+ i += 1
854
+
855
+ # Process year/month/day
856
+ year, month, day = ymd.resolve_ymd(yearfirst, dayfirst)
857
+
858
+ res.century_specified = ymd.century_specified
859
+ res.year = year
860
+ res.month = month
861
+ res.day = day
862
+
863
+ except (IndexError, ValueError):
864
+ return None, None
865
+
866
+ if not info.validate(res):
867
+ return None, None
868
+
869
+ if fuzzy_with_tokens:
870
+ skipped_tokens = self._recombine_skipped(l, skipped_idxs)
871
+ return res, tuple(skipped_tokens)
872
+ else:
873
+ return res, None
874
+
875
+ def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy):
876
+ # Token is a number
877
+ value_repr = tokens[idx]
878
+ try:
879
+ value = self._to_decimal(value_repr)
880
+ except Exception as e:
881
+ six.raise_from(ValueError('Unknown numeric token'), e)
882
+
883
+ len_li = len(value_repr)
884
+
885
+ len_l = len(tokens)
886
+
887
+ if (len(ymd) == 3 and len_li in (2, 4) and
888
+ res.hour is None and
889
+ (idx + 1 >= len_l or
890
+ (tokens[idx + 1] != ':' and
891
+ info.hms(tokens[idx + 1]) is None))):
892
+ # 19990101T23[59]
893
+ s = tokens[idx]
894
+ res.hour = int(s[:2])
895
+
896
+ if len_li == 4:
897
+ res.minute = int(s[2:])
898
+
899
+ elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6):
900
+ # YYMMDD or HHMMSS[.ss]
901
+ s = tokens[idx]
902
+
903
+ if not ymd and '.' not in tokens[idx]:
904
+ ymd.append(s[:2])
905
+ ymd.append(s[2:4])
906
+ ymd.append(s[4:])
907
+ else:
908
+ # 19990101T235959[.59]
909
+
910
+ # TODO: Check if res attributes already set.
911
+ res.hour = int(s[:2])
912
+ res.minute = int(s[2:4])
913
+ res.second, res.microsecond = self._parsems(s[4:])
914
+
915
+ elif len_li in (8, 12, 14):
916
+ # YYYYMMDD
917
+ s = tokens[idx]
918
+ ymd.append(s[:4], 'Y')
919
+ ymd.append(s[4:6])
920
+ ymd.append(s[6:8])
921
+
922
+ if len_li > 8:
923
+ res.hour = int(s[8:10])
924
+ res.minute = int(s[10:12])
925
+
926
+ if len_li > 12:
927
+ res.second = int(s[12:])
928
+
929
+ elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None:
930
+ # HH[ ]h or MM[ ]m or SS[.ss][ ]s
931
+ hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True)
932
+ (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx)
933
+ if hms is not None:
934
+ # TODO: checking that hour/minute/second are not
935
+ # already set?
936
+ self._assign_hms(res, value_repr, hms)
937
+
938
+ elif idx + 2 < len_l and tokens[idx + 1] == ':':
939
+ # HH:MM[:SS[.ss]]
940
+ res.hour = int(value)
941
+ value = self._to_decimal(tokens[idx + 2]) # TODO: try/except for this?
942
+ (res.minute, res.second) = self._parse_min_sec(value)
943
+
944
+ if idx + 4 < len_l and tokens[idx + 3] == ':':
945
+ res.second, res.microsecond = self._parsems(tokens[idx + 4])
946
+
947
+ idx += 2
948
+
949
+ idx += 2
950
+
951
+ elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'):
952
+ sep = tokens[idx + 1]
953
+ ymd.append(value_repr)
954
+
955
+ if idx + 2 < len_l and not info.jump(tokens[idx + 2]):
956
+ if tokens[idx + 2].isdigit():
957
+ # 01-01[-01]
958
+ ymd.append(tokens[idx + 2])
959
+ else:
960
+ # 01-Jan[-01]
961
+ value = info.month(tokens[idx + 2])
962
+
963
+ if value is not None:
964
+ ymd.append(value, 'M')
965
+ else:
966
+ raise ValueError()
967
+
968
+ if idx + 3 < len_l and tokens[idx + 3] == sep:
969
+ # We have three members
970
+ value = info.month(tokens[idx + 4])
971
+
972
+ if value is not None:
973
+ ymd.append(value, 'M')
974
+ else:
975
+ ymd.append(tokens[idx + 4])
976
+ idx += 2
977
+
978
+ idx += 1
979
+ idx += 1
980
+
981
+ elif idx + 1 >= len_l or info.jump(tokens[idx + 1]):
982
+ if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None:
983
+ # 12 am
984
+ hour = int(value)
985
+ res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2]))
986
+ idx += 1
987
+ else:
988
+ # Year, month or day
989
+ ymd.append(value)
990
+ idx += 1
991
+
992
+ elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24):
993
+ # 12am
994
+ hour = int(value)
995
+ res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1]))
996
+ idx += 1
997
+
998
+ elif ymd.could_be_day(value):
999
+ ymd.append(value)
1000
+
1001
+ elif not fuzzy:
1002
+ raise ValueError()
1003
+
1004
+ return idx
1005
+
1006
+ def _find_hms_idx(self, idx, tokens, info, allow_jump):
1007
+ len_l = len(tokens)
1008
+
1009
+ if idx+1 < len_l and info.hms(tokens[idx+1]) is not None:
1010
+ # There is an "h", "m", or "s" label following this token. We take
1011
+ # assign the upcoming label to the current token.
1012
+ # e.g. the "12" in 12h"
1013
+ hms_idx = idx + 1
1014
+
1015
+ elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and
1016
+ info.hms(tokens[idx+2]) is not None):
1017
+ # There is a space and then an "h", "m", or "s" label.
1018
+ # e.g. the "12" in "12 h"
1019
+ hms_idx = idx + 2
1020
+
1021
+ elif idx > 0 and info.hms(tokens[idx-1]) is not None:
1022
+ # There is a "h", "m", or "s" preceding this token. Since neither
1023
+ # of the previous cases was hit, there is no label following this
1024
+ # token, so we use the previous label.
1025
+ # e.g. the "04" in "12h04"
1026
+ hms_idx = idx-1
1027
+
1028
+ elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and
1029
+ info.hms(tokens[idx-2]) is not None):
1030
+ # If we are looking at the final token, we allow for a
1031
+ # backward-looking check to skip over a space.
1032
+ # TODO: Are we sure this is the right condition here?
1033
+ hms_idx = idx - 2
1034
+
1035
+ else:
1036
+ hms_idx = None
1037
+
1038
+ return hms_idx
1039
+
1040
+ def _assign_hms(self, res, value_repr, hms):
1041
+ # See GH issue #427, fixing float rounding
1042
+ value = self._to_decimal(value_repr)
1043
+
1044
+ if hms == 0:
1045
+ # Hour
1046
+ res.hour = int(value)
1047
+ if value % 1:
1048
+ res.minute = int(60*(value % 1))
1049
+
1050
+ elif hms == 1:
1051
+ (res.minute, res.second) = self._parse_min_sec(value)
1052
+
1053
+ elif hms == 2:
1054
+ (res.second, res.microsecond) = self._parsems(value_repr)
1055
+
1056
+ def _could_be_tzname(self, hour, tzname, tzoffset, token):
1057
+ return (hour is not None and
1058
+ tzname is None and
1059
+ tzoffset is None and
1060
+ len(token) <= 5 and
1061
+ (all(x in string.ascii_uppercase for x in token)
1062
+ or token in self.info.UTCZONE))
1063
+
1064
+ def _ampm_valid(self, hour, ampm, fuzzy):
1065
+ """
1066
+ For fuzzy parsing, 'a' or 'am' (both valid English words)
1067
+ may erroneously trigger the AM/PM flag. Deal with that
1068
+ here.
1069
+ """
1070
+ val_is_ampm = True
1071
+
1072
+ # If there's already an AM/PM flag, this one isn't one.
1073
+ if fuzzy and ampm is not None:
1074
+ val_is_ampm = False
1075
+
1076
+ # If AM/PM is found and hour is not, raise a ValueError
1077
+ if hour is None:
1078
+ if fuzzy:
1079
+ val_is_ampm = False
1080
+ else:
1081
+ raise ValueError('No hour specified with AM or PM flag.')
1082
+ elif not 0 <= hour <= 12:
1083
+ # If AM/PM is found, it's a 12 hour clock, so raise
1084
+ # an error for invalid range
1085
+ if fuzzy:
1086
+ val_is_ampm = False
1087
+ else:
1088
+ raise ValueError('Invalid hour specified for 12-hour clock.')
1089
+
1090
+ return val_is_ampm
1091
+
1092
+ def _adjust_ampm(self, hour, ampm):
1093
+ if hour < 12 and ampm == 1:
1094
+ hour += 12
1095
+ elif hour == 12 and ampm == 0:
1096
+ hour = 0
1097
+ return hour
1098
+
1099
+ def _parse_min_sec(self, value):
1100
+ # TODO: Every usage of this function sets res.second to the return
1101
+ # value. Are there any cases where second will be returned as None and
1102
+ # we *don't* want to set res.second = None?
1103
+ minute = int(value)
1104
+ second = None
1105
+
1106
+ sec_remainder = value % 1
1107
+ if sec_remainder:
1108
+ second = int(60 * sec_remainder)
1109
+ return (minute, second)
1110
+
1111
+ def _parse_hms(self, idx, tokens, info, hms_idx):
1112
+ # TODO: Is this going to admit a lot of false-positives for when we
1113
+ # just happen to have digits and "h", "m" or "s" characters in non-date
1114
+ # text? I guess hex hashes won't have that problem, but there's plenty
1115
+ # of random junk out there.
1116
+ if hms_idx is None:
1117
+ hms = None
1118
+ new_idx = idx
1119
+ elif hms_idx > idx:
1120
+ hms = info.hms(tokens[hms_idx])
1121
+ new_idx = hms_idx
1122
+ else:
1123
+ # Looking backwards, increment one.
1124
+ hms = info.hms(tokens[hms_idx]) + 1
1125
+ new_idx = idx
1126
+
1127
+ return (new_idx, hms)
1128
+
1129
+ # ------------------------------------------------------------------
1130
+ # Handling for individual tokens. These are kept as methods instead
1131
+ # of functions for the sake of customizability via subclassing.
1132
+
1133
+ def _parsems(self, value):
1134
+ """Parse a I[.F] seconds value into (seconds, microseconds)."""
1135
+ if "." not in value:
1136
+ return int(value), 0
1137
+ else:
1138
+ i, f = value.split(".")
1139
+ return int(i), int(f.ljust(6, "0")[:6])
1140
+
1141
+ def _to_decimal(self, val):
1142
+ try:
1143
+ decimal_value = Decimal(val)
1144
+ # See GH 662, edge case, infinite value should not be converted
1145
+ # via `_to_decimal`
1146
+ if not decimal_value.is_finite():
1147
+ raise ValueError("Converted decimal value is infinite or NaN")
1148
+ except Exception as e:
1149
+ msg = "Could not convert %s to decimal" % val
1150
+ six.raise_from(ValueError(msg), e)
1151
+ else:
1152
+ return decimal_value
1153
+
1154
+ # ------------------------------------------------------------------
1155
+ # Post-Parsing construction of datetime output. These are kept as
1156
+ # methods instead of functions for the sake of customizability via
1157
+ # subclassing.
1158
+
1159
+ def _build_tzinfo(self, tzinfos, tzname, tzoffset):
1160
+ if callable(tzinfos):
1161
+ tzdata = tzinfos(tzname, tzoffset)
1162
+ else:
1163
+ tzdata = tzinfos.get(tzname)
1164
+ # handle case where tzinfo is paased an options that returns None
1165
+ # eg tzinfos = {'BRST' : None}
1166
+ if isinstance(tzdata, datetime.tzinfo) or tzdata is None:
1167
+ tzinfo = tzdata
1168
+ elif isinstance(tzdata, text_type):
1169
+ tzinfo = tz.tzstr(tzdata)
1170
+ elif isinstance(tzdata, integer_types):
1171
+ tzinfo = tz.tzoffset(tzname, tzdata)
1172
+ else:
1173
+ raise TypeError("Offset must be tzinfo subclass, tz string, "
1174
+ "or int offset.")
1175
+ return tzinfo
1176
+
1177
+ def _build_tzaware(self, naive, res, tzinfos):
1178
+ if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)):
1179
+ tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset)
1180
+ aware = naive.replace(tzinfo=tzinfo)
1181
+ aware = self._assign_tzname(aware, res.tzname)
1182
+
1183
+ elif res.tzname and res.tzname in time.tzname:
1184
+ aware = naive.replace(tzinfo=tz.tzlocal())
1185
+
1186
+ # Handle ambiguous local datetime
1187
+ aware = self._assign_tzname(aware, res.tzname)
1188
+
1189
+ # This is mostly relevant for winter GMT zones parsed in the UK
1190
+ if (aware.tzname() != res.tzname and
1191
+ res.tzname in self.info.UTCZONE):
1192
+ aware = aware.replace(tzinfo=tz.UTC)
1193
+
1194
+ elif res.tzoffset == 0:
1195
+ aware = naive.replace(tzinfo=tz.UTC)
1196
+
1197
+ elif res.tzoffset:
1198
+ aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset))
1199
+
1200
+ elif not res.tzname and not res.tzoffset:
1201
+ # i.e. no timezone information was found.
1202
+ aware = naive
1203
+
1204
+ elif res.tzname:
1205
+ # tz-like string was parsed but we don't know what to do
1206
+ # with it
1207
+ warnings.warn("tzname {tzname} identified but not understood. "
1208
+ "Pass `tzinfos` argument in order to correctly "
1209
+ "return a timezone-aware datetime. In a future "
1210
+ "version, this will raise an "
1211
+ "exception.".format(tzname=res.tzname),
1212
+ category=UnknownTimezoneWarning)
1213
+ aware = naive
1214
+
1215
+ return aware
1216
+
1217
+ def _build_naive(self, res, default):
1218
+ repl = {}
1219
+ for attr in ("year", "month", "day", "hour",
1220
+ "minute", "second", "microsecond"):
1221
+ value = getattr(res, attr)
1222
+ if value is not None:
1223
+ repl[attr] = value
1224
+
1225
+ if 'day' not in repl:
1226
+ # If the default day exceeds the last day of the month, fall back
1227
+ # to the end of the month.
1228
+ cyear = default.year if res.year is None else res.year
1229
+ cmonth = default.month if res.month is None else res.month
1230
+ cday = default.day if res.day is None else res.day
1231
+
1232
+ if cday > monthrange(cyear, cmonth)[1]:
1233
+ repl['day'] = monthrange(cyear, cmonth)[1]
1234
+
1235
+ naive = default.replace(**repl)
1236
+
1237
+ if res.weekday is not None and not res.day:
1238
+ naive = naive + relativedelta.relativedelta(weekday=res.weekday)
1239
+
1240
+ return naive
1241
+
1242
+ def _assign_tzname(self, dt, tzname):
1243
+ if dt.tzname() != tzname:
1244
+ new_dt = tz.enfold(dt, fold=1)
1245
+ if new_dt.tzname() == tzname:
1246
+ return new_dt
1247
+
1248
+ return dt
1249
+
1250
+ def _recombine_skipped(self, tokens, skipped_idxs):
1251
+ """
1252
+ >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"]
1253
+ >>> skipped_idxs = [0, 1, 2, 5]
1254
+ >>> _recombine_skipped(tokens, skipped_idxs)
1255
+ ["foo bar", "baz"]
1256
+ """
1257
+ skipped_tokens = []
1258
+ for i, idx in enumerate(sorted(skipped_idxs)):
1259
+ if i > 0 and idx - 1 == skipped_idxs[i - 1]:
1260
+ skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx]
1261
+ else:
1262
+ skipped_tokens.append(tokens[idx])
1263
+
1264
+ return skipped_tokens
1265
+
1266
+
1267
+ DEFAULTPARSER = parser()
1268
+
1269
+
1270
+ def parse(timestr, parserinfo=None, **kwargs):
1271
+ """
1272
+
1273
+ Parse a string in one of the supported formats, using the
1274
+ ``parserinfo`` parameters.
1275
+
1276
+ :param timestr:
1277
+ A string containing a date/time stamp.
1278
+
1279
+ :param parserinfo:
1280
+ A :class:`parserinfo` object containing parameters for the parser.
1281
+ If ``None``, the default arguments to the :class:`parserinfo`
1282
+ constructor are used.
1283
+
1284
+ The ``**kwargs`` parameter takes the following keyword arguments:
1285
+
1286
+ :param default:
1287
+ The default datetime object, if this is a datetime object and not
1288
+ ``None``, elements specified in ``timestr`` replace elements in the
1289
+ default object.
1290
+
1291
+ :param ignoretz:
1292
+ If set ``True``, time zones in parsed strings are ignored and a naive
1293
+ :class:`datetime` object is returned.
1294
+
1295
+ :param tzinfos:
1296
+ Additional time zone names / aliases which may be present in the
1297
+ string. This argument maps time zone names (and optionally offsets
1298
+ from those time zones) to time zones. This parameter can be a
1299
+ dictionary with timezone aliases mapping time zone names to time
1300
+ zones or a function taking two parameters (``tzname`` and
1301
+ ``tzoffset``) and returning a time zone.
1302
+
1303
+ The timezones to which the names are mapped can be an integer
1304
+ offset from UTC in seconds or a :class:`tzinfo` object.
1305
+
1306
+ .. doctest::
1307
+ :options: +NORMALIZE_WHITESPACE
1308
+
1309
+ >>> from dateutil.parser import parse
1310
+ >>> from dateutil.tz import gettz
1311
+ >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")}
1312
+ >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos)
1313
+ datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200))
1314
+ >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos)
1315
+ datetime.datetime(2012, 1, 19, 17, 21,
1316
+ tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago'))
1317
+
1318
+ This parameter is ignored if ``ignoretz`` is set.
1319
+
1320
+ :param dayfirst:
1321
+ Whether to interpret the first value in an ambiguous 3-integer date
1322
+ (e.g. 01/05/09) as the day (``True``) or month (``False``). If
1323
+ ``yearfirst`` is set to ``True``, this distinguishes between YDM and
1324
+ YMD. If set to ``None``, this value is retrieved from the current
1325
+ :class:`parserinfo` object (which itself defaults to ``False``).
1326
+
1327
+ :param yearfirst:
1328
+ Whether to interpret the first value in an ambiguous 3-integer date
1329
+ (e.g. 01/05/09) as the year. If ``True``, the first number is taken to
1330
+ be the year, otherwise the last number is taken to be the year. If
1331
+ this is set to ``None``, the value is retrieved from the current
1332
+ :class:`parserinfo` object (which itself defaults to ``False``).
1333
+
1334
+ :param fuzzy:
1335
+ Whether to allow fuzzy parsing, allowing for string like "Today is
1336
+ January 1, 2047 at 8:21:00AM".
1337
+
1338
+ :param fuzzy_with_tokens:
1339
+ If ``True``, ``fuzzy`` is automatically set to True, and the parser
1340
+ will return a tuple where the first element is the parsed
1341
+ :class:`datetime.datetime` datetimestamp and the second element is
1342
+ a tuple containing the portions of the string which were ignored:
1343
+
1344
+ .. doctest::
1345
+
1346
+ >>> from dateutil.parser import parse
1347
+ >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True)
1348
+ (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at '))
1349
+
1350
+ :return:
1351
+ Returns a :class:`datetime.datetime` object or, if the
1352
+ ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the
1353
+ first element being a :class:`datetime.datetime` object, the second
1354
+ a tuple containing the fuzzy tokens.
1355
+
1356
+ :raises ParserError:
1357
+ Raised for invalid or unknown string formats, if the provided
1358
+ :class:`tzinfo` is not in a valid format, or if an invalid date would
1359
+ be created.
1360
+
1361
+ :raises OverflowError:
1362
+ Raised if the parsed date exceeds the largest valid C integer on
1363
+ your system.
1364
+ """
1365
+ if parserinfo:
1366
+ return parser(parserinfo).parse(timestr, **kwargs)
1367
+ else:
1368
+ return DEFAULTPARSER.parse(timestr, **kwargs)
1369
+
1370
+
1371
+ class _tzparser(object):
1372
+
1373
+ class _result(_resultbase):
1374
+
1375
+ __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset",
1376
+ "start", "end"]
1377
+
1378
+ class _attr(_resultbase):
1379
+ __slots__ = ["month", "week", "weekday",
1380
+ "yday", "jyday", "day", "time"]
1381
+
1382
+ def __repr__(self):
1383
+ return self._repr("")
1384
+
1385
+ def __init__(self):
1386
+ _resultbase.__init__(self)
1387
+ self.start = self._attr()
1388
+ self.end = self._attr()
1389
+
1390
+ def parse(self, tzstr):
1391
+ res = self._result()
1392
+ l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x]
1393
+ used_idxs = list()
1394
+ try:
1395
+
1396
+ len_l = len(l)
1397
+
1398
+ i = 0
1399
+ while i < len_l:
1400
+ # BRST+3[BRDT[+2]]
1401
+ j = i
1402
+ while j < len_l and not [x for x in l[j]
1403
+ if x in "0123456789:,-+"]:
1404
+ j += 1
1405
+ if j != i:
1406
+ if not res.stdabbr:
1407
+ offattr = "stdoffset"
1408
+ res.stdabbr = "".join(l[i:j])
1409
+ else:
1410
+ offattr = "dstoffset"
1411
+ res.dstabbr = "".join(l[i:j])
1412
+
1413
+ for ii in range(j):
1414
+ used_idxs.append(ii)
1415
+ i = j
1416
+ if (i < len_l and (l[i] in ('+', '-') or l[i][0] in
1417
+ "0123456789")):
1418
+ if l[i] in ('+', '-'):
1419
+ # Yes, that's right. See the TZ variable
1420
+ # documentation.
1421
+ signal = (1, -1)[l[i] == '+']
1422
+ used_idxs.append(i)
1423
+ i += 1
1424
+ else:
1425
+ signal = -1
1426
+ len_li = len(l[i])
1427
+ if len_li == 4:
1428
+ # -0300
1429
+ setattr(res, offattr, (int(l[i][:2]) * 3600 +
1430
+ int(l[i][2:]) * 60) * signal)
1431
+ elif i + 1 < len_l and l[i + 1] == ':':
1432
+ # -03:00
1433
+ setattr(res, offattr,
1434
+ (int(l[i]) * 3600 +
1435
+ int(l[i + 2]) * 60) * signal)
1436
+ used_idxs.append(i)
1437
+ i += 2
1438
+ elif len_li <= 2:
1439
+ # -[0]3
1440
+ setattr(res, offattr,
1441
+ int(l[i][:2]) * 3600 * signal)
1442
+ else:
1443
+ return None
1444
+ used_idxs.append(i)
1445
+ i += 1
1446
+ if res.dstabbr:
1447
+ break
1448
+ else:
1449
+ break
1450
+
1451
+
1452
+ if i < len_l:
1453
+ for j in range(i, len_l):
1454
+ if l[j] == ';':
1455
+ l[j] = ','
1456
+
1457
+ assert l[i] == ','
1458
+
1459
+ i += 1
1460
+
1461
+ if i >= len_l:
1462
+ pass
1463
+ elif (8 <= l.count(',') <= 9 and
1464
+ not [y for x in l[i:] if x != ','
1465
+ for y in x if y not in "0123456789+-"]):
1466
+ # GMT0BST,3,0,30,3600,10,0,26,7200[,3600]
1467
+ for x in (res.start, res.end):
1468
+ x.month = int(l[i])
1469
+ used_idxs.append(i)
1470
+ i += 2
1471
+ if l[i] == '-':
1472
+ value = int(l[i + 1]) * -1
1473
+ used_idxs.append(i)
1474
+ i += 1
1475
+ else:
1476
+ value = int(l[i])
1477
+ used_idxs.append(i)
1478
+ i += 2
1479
+ if value:
1480
+ x.week = value
1481
+ x.weekday = (int(l[i]) - 1) % 7
1482
+ else:
1483
+ x.day = int(l[i])
1484
+ used_idxs.append(i)
1485
+ i += 2
1486
+ x.time = int(l[i])
1487
+ used_idxs.append(i)
1488
+ i += 2
1489
+ if i < len_l:
1490
+ if l[i] in ('-', '+'):
1491
+ signal = (-1, 1)[l[i] == "+"]
1492
+ used_idxs.append(i)
1493
+ i += 1
1494
+ else:
1495
+ signal = 1
1496
+ used_idxs.append(i)
1497
+ res.dstoffset = (res.stdoffset + int(l[i]) * signal)
1498
+
1499
+ # This was a made-up format that is not in normal use
1500
+ warn(('Parsed time zone "%s"' % tzstr) +
1501
+ 'is in a non-standard dateutil-specific format, which ' +
1502
+ 'is now deprecated; support for parsing this format ' +
1503
+ 'will be removed in future versions. It is recommended ' +
1504
+ 'that you switch to a standard format like the GNU ' +
1505
+ 'TZ variable format.', tz.DeprecatedTzFormatWarning)
1506
+ elif (l.count(',') == 2 and l[i:].count('/') <= 2 and
1507
+ not [y for x in l[i:] if x not in (',', '/', 'J', 'M',
1508
+ '.', '-', ':')
1509
+ for y in x if y not in "0123456789"]):
1510
+ for x in (res.start, res.end):
1511
+ if l[i] == 'J':
1512
+ # non-leap year day (1 based)
1513
+ used_idxs.append(i)
1514
+ i += 1
1515
+ x.jyday = int(l[i])
1516
+ elif l[i] == 'M':
1517
+ # month[-.]week[-.]weekday
1518
+ used_idxs.append(i)
1519
+ i += 1
1520
+ x.month = int(l[i])
1521
+ used_idxs.append(i)
1522
+ i += 1
1523
+ assert l[i] in ('-', '.')
1524
+ used_idxs.append(i)
1525
+ i += 1
1526
+ x.week = int(l[i])
1527
+ if x.week == 5:
1528
+ x.week = -1
1529
+ used_idxs.append(i)
1530
+ i += 1
1531
+ assert l[i] in ('-', '.')
1532
+ used_idxs.append(i)
1533
+ i += 1
1534
+ x.weekday = (int(l[i]) - 1) % 7
1535
+ else:
1536
+ # year day (zero based)
1537
+ x.yday = int(l[i]) + 1
1538
+
1539
+ used_idxs.append(i)
1540
+ i += 1
1541
+
1542
+ if i < len_l and l[i] == '/':
1543
+ used_idxs.append(i)
1544
+ i += 1
1545
+ # start time
1546
+ len_li = len(l[i])
1547
+ if len_li == 4:
1548
+ # -0300
1549
+ x.time = (int(l[i][:2]) * 3600 +
1550
+ int(l[i][2:]) * 60)
1551
+ elif i + 1 < len_l and l[i + 1] == ':':
1552
+ # -03:00
1553
+ x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60
1554
+ used_idxs.append(i)
1555
+ i += 2
1556
+ if i + 1 < len_l and l[i + 1] == ':':
1557
+ used_idxs.append(i)
1558
+ i += 2
1559
+ x.time += int(l[i])
1560
+ elif len_li <= 2:
1561
+ # -[0]3
1562
+ x.time = (int(l[i][:2]) * 3600)
1563
+ else:
1564
+ return None
1565
+ used_idxs.append(i)
1566
+ i += 1
1567
+
1568
+ assert i == len_l or l[i] == ','
1569
+
1570
+ i += 1
1571
+
1572
+ assert i >= len_l
1573
+
1574
+ except (IndexError, ValueError, AssertionError):
1575
+ return None
1576
+
1577
+ unused_idxs = set(range(len_l)).difference(used_idxs)
1578
+ res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"})
1579
+ return res
1580
+
1581
+
1582
+ DEFAULTTZPARSER = _tzparser()
1583
+
1584
+
1585
+ def _parsetz(tzstr):
1586
+ return DEFAULTTZPARSER.parse(tzstr)
1587
+
1588
+
1589
+ class ParserError(ValueError):
1590
+ """Exception subclass used for any failure to parse a datetime string.
1591
+
1592
+ This is a subclass of :py:exc:`ValueError`, and should be raised any time
1593
+ earlier versions of ``dateutil`` would have raised ``ValueError``.
1594
+
1595
+ .. versionadded:: 2.8.1
1596
+ """
1597
+ def __str__(self):
1598
+ try:
1599
+ return self.args[0] % self.args[1:]
1600
+ except (TypeError, IndexError):
1601
+ return super(ParserError, self).__str__()
1602
+
1603
+ def __repr__(self):
1604
+ args = ", ".join("'%s'" % arg for arg in self.args)
1605
+ return "%s(%s)" % (self.__class__.__name__, args)
1606
+
1607
+
1608
+ class UnknownTimezoneWarning(RuntimeWarning):
1609
+ """Raised when the parser finds a timezone it cannot parse into a tzinfo.
1610
+
1611
+ .. versionadded:: 2.7.0
1612
+ """
1613
+ # vim:ts=4:sw=4:et
hug/lib/python3.12/site-packages/dateutil/parser/isoparser.py ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ This module offers a parser for ISO-8601 strings
4
+
5
+ It is intended to support all valid date, time and datetime formats per the
6
+ ISO-8601 specification.
7
+
8
+ ..versionadded:: 2.7.0
9
+ """
10
+ from datetime import datetime, timedelta, time, date
11
+ import calendar
12
+ from dateutil import tz
13
+
14
+ from functools import wraps
15
+
16
+ import re
17
+ import six
18
+
19
+ __all__ = ["isoparse", "isoparser"]
20
+
21
+
22
+ def _takes_ascii(f):
23
+ @wraps(f)
24
+ def func(self, str_in, *args, **kwargs):
25
+ # If it's a stream, read the whole thing
26
+ str_in = getattr(str_in, 'read', lambda: str_in)()
27
+
28
+ # If it's unicode, turn it into bytes, since ISO-8601 only covers ASCII
29
+ if isinstance(str_in, six.text_type):
30
+ # ASCII is the same in UTF-8
31
+ try:
32
+ str_in = str_in.encode('ascii')
33
+ except UnicodeEncodeError as e:
34
+ msg = 'ISO-8601 strings should contain only ASCII characters'
35
+ six.raise_from(ValueError(msg), e)
36
+
37
+ return f(self, str_in, *args, **kwargs)
38
+
39
+ return func
40
+
41
+
42
+ class isoparser(object):
43
+ def __init__(self, sep=None):
44
+ """
45
+ :param sep:
46
+ A single character that separates date and time portions. If
47
+ ``None``, the parser will accept any single character.
48
+ For strict ISO-8601 adherence, pass ``'T'``.
49
+ """
50
+ if sep is not None:
51
+ if (len(sep) != 1 or ord(sep) >= 128 or sep in '0123456789'):
52
+ raise ValueError('Separator must be a single, non-numeric ' +
53
+ 'ASCII character')
54
+
55
+ sep = sep.encode('ascii')
56
+
57
+ self._sep = sep
58
+
59
+ @_takes_ascii
60
+ def isoparse(self, dt_str):
61
+ """
62
+ Parse an ISO-8601 datetime string into a :class:`datetime.datetime`.
63
+
64
+ An ISO-8601 datetime string consists of a date portion, followed
65
+ optionally by a time portion - the date and time portions are separated
66
+ by a single character separator, which is ``T`` in the official
67
+ standard. Incomplete date formats (such as ``YYYY-MM``) may *not* be
68
+ combined with a time portion.
69
+
70
+ Supported date formats are:
71
+
72
+ Common:
73
+
74
+ - ``YYYY``
75
+ - ``YYYY-MM``
76
+ - ``YYYY-MM-DD`` or ``YYYYMMDD``
77
+
78
+ Uncommon:
79
+
80
+ - ``YYYY-Www`` or ``YYYYWww`` - ISO week (day defaults to 0)
81
+ - ``YYYY-Www-D`` or ``YYYYWwwD`` - ISO week and day
82
+
83
+ The ISO week and day numbering follows the same logic as
84
+ :func:`datetime.date.isocalendar`.
85
+
86
+ Supported time formats are:
87
+
88
+ - ``hh``
89
+ - ``hh:mm`` or ``hhmm``
90
+ - ``hh:mm:ss`` or ``hhmmss``
91
+ - ``hh:mm:ss.ssssss`` (Up to 6 sub-second digits)
92
+
93
+ Midnight is a special case for `hh`, as the standard supports both
94
+ 00:00 and 24:00 as a representation. The decimal separator can be
95
+ either a dot or a comma.
96
+
97
+
98
+ .. caution::
99
+
100
+ Support for fractional components other than seconds is part of the
101
+ ISO-8601 standard, but is not currently implemented in this parser.
102
+
103
+ Supported time zone offset formats are:
104
+
105
+ - `Z` (UTC)
106
+ - `±HH:MM`
107
+ - `±HHMM`
108
+ - `±HH`
109
+
110
+ Offsets will be represented as :class:`dateutil.tz.tzoffset` objects,
111
+ with the exception of UTC, which will be represented as
112
+ :class:`dateutil.tz.tzutc`. Time zone offsets equivalent to UTC (such
113
+ as `+00:00`) will also be represented as :class:`dateutil.tz.tzutc`.
114
+
115
+ :param dt_str:
116
+ A string or stream containing only an ISO-8601 datetime string
117
+
118
+ :return:
119
+ Returns a :class:`datetime.datetime` representing the string.
120
+ Unspecified components default to their lowest value.
121
+
122
+ .. warning::
123
+
124
+ As of version 2.7.0, the strictness of the parser should not be
125
+ considered a stable part of the contract. Any valid ISO-8601 string
126
+ that parses correctly with the default settings will continue to
127
+ parse correctly in future versions, but invalid strings that
128
+ currently fail (e.g. ``2017-01-01T00:00+00:00:00``) are not
129
+ guaranteed to continue failing in future versions if they encode
130
+ a valid date.
131
+
132
+ .. versionadded:: 2.7.0
133
+ """
134
+ components, pos = self._parse_isodate(dt_str)
135
+
136
+ if len(dt_str) > pos:
137
+ if self._sep is None or dt_str[pos:pos + 1] == self._sep:
138
+ components += self._parse_isotime(dt_str[pos + 1:])
139
+ else:
140
+ raise ValueError('String contains unknown ISO components')
141
+
142
+ if len(components) > 3 and components[3] == 24:
143
+ components[3] = 0
144
+ return datetime(*components) + timedelta(days=1)
145
+
146
+ return datetime(*components)
147
+
148
+ @_takes_ascii
149
+ def parse_isodate(self, datestr):
150
+ """
151
+ Parse the date portion of an ISO string.
152
+
153
+ :param datestr:
154
+ The string portion of an ISO string, without a separator
155
+
156
+ :return:
157
+ Returns a :class:`datetime.date` object
158
+ """
159
+ components, pos = self._parse_isodate(datestr)
160
+ if pos < len(datestr):
161
+ raise ValueError('String contains unknown ISO ' +
162
+ 'components: {!r}'.format(datestr.decode('ascii')))
163
+ return date(*components)
164
+
165
+ @_takes_ascii
166
+ def parse_isotime(self, timestr):
167
+ """
168
+ Parse the time portion of an ISO string.
169
+
170
+ :param timestr:
171
+ The time portion of an ISO string, without a separator
172
+
173
+ :return:
174
+ Returns a :class:`datetime.time` object
175
+ """
176
+ components = self._parse_isotime(timestr)
177
+ if components[0] == 24:
178
+ components[0] = 0
179
+ return time(*components)
180
+
181
+ @_takes_ascii
182
+ def parse_tzstr(self, tzstr, zero_as_utc=True):
183
+ """
184
+ Parse a valid ISO time zone string.
185
+
186
+ See :func:`isoparser.isoparse` for details on supported formats.
187
+
188
+ :param tzstr:
189
+ A string representing an ISO time zone offset
190
+
191
+ :param zero_as_utc:
192
+ Whether to return :class:`dateutil.tz.tzutc` for zero-offset zones
193
+
194
+ :return:
195
+ Returns :class:`dateutil.tz.tzoffset` for offsets and
196
+ :class:`dateutil.tz.tzutc` for ``Z`` and (if ``zero_as_utc`` is
197
+ specified) offsets equivalent to UTC.
198
+ """
199
+ return self._parse_tzstr(tzstr, zero_as_utc=zero_as_utc)
200
+
201
+ # Constants
202
+ _DATE_SEP = b'-'
203
+ _TIME_SEP = b':'
204
+ _FRACTION_REGEX = re.compile(b'[\\.,]([0-9]+)')
205
+
206
+ def _parse_isodate(self, dt_str):
207
+ try:
208
+ return self._parse_isodate_common(dt_str)
209
+ except ValueError:
210
+ return self._parse_isodate_uncommon(dt_str)
211
+
212
+ def _parse_isodate_common(self, dt_str):
213
+ len_str = len(dt_str)
214
+ components = [1, 1, 1]
215
+
216
+ if len_str < 4:
217
+ raise ValueError('ISO string too short')
218
+
219
+ # Year
220
+ components[0] = int(dt_str[0:4])
221
+ pos = 4
222
+ if pos >= len_str:
223
+ return components, pos
224
+
225
+ has_sep = dt_str[pos:pos + 1] == self._DATE_SEP
226
+ if has_sep:
227
+ pos += 1
228
+
229
+ # Month
230
+ if len_str - pos < 2:
231
+ raise ValueError('Invalid common month')
232
+
233
+ components[1] = int(dt_str[pos:pos + 2])
234
+ pos += 2
235
+
236
+ if pos >= len_str:
237
+ if has_sep:
238
+ return components, pos
239
+ else:
240
+ raise ValueError('Invalid ISO format')
241
+
242
+ if has_sep:
243
+ if dt_str[pos:pos + 1] != self._DATE_SEP:
244
+ raise ValueError('Invalid separator in ISO string')
245
+ pos += 1
246
+
247
+ # Day
248
+ if len_str - pos < 2:
249
+ raise ValueError('Invalid common day')
250
+ components[2] = int(dt_str[pos:pos + 2])
251
+ return components, pos + 2
252
+
253
+ def _parse_isodate_uncommon(self, dt_str):
254
+ if len(dt_str) < 4:
255
+ raise ValueError('ISO string too short')
256
+
257
+ # All ISO formats start with the year
258
+ year = int(dt_str[0:4])
259
+
260
+ has_sep = dt_str[4:5] == self._DATE_SEP
261
+
262
+ pos = 4 + has_sep # Skip '-' if it's there
263
+ if dt_str[pos:pos + 1] == b'W':
264
+ # YYYY-?Www-?D?
265
+ pos += 1
266
+ weekno = int(dt_str[pos:pos + 2])
267
+ pos += 2
268
+
269
+ dayno = 1
270
+ if len(dt_str) > pos:
271
+ if (dt_str[pos:pos + 1] == self._DATE_SEP) != has_sep:
272
+ raise ValueError('Inconsistent use of dash separator')
273
+
274
+ pos += has_sep
275
+
276
+ dayno = int(dt_str[pos:pos + 1])
277
+ pos += 1
278
+
279
+ base_date = self._calculate_weekdate(year, weekno, dayno)
280
+ else:
281
+ # YYYYDDD or YYYY-DDD
282
+ if len(dt_str) - pos < 3:
283
+ raise ValueError('Invalid ordinal day')
284
+
285
+ ordinal_day = int(dt_str[pos:pos + 3])
286
+ pos += 3
287
+
288
+ if ordinal_day < 1 or ordinal_day > (365 + calendar.isleap(year)):
289
+ raise ValueError('Invalid ordinal day' +
290
+ ' {} for year {}'.format(ordinal_day, year))
291
+
292
+ base_date = date(year, 1, 1) + timedelta(days=ordinal_day - 1)
293
+
294
+ components = [base_date.year, base_date.month, base_date.day]
295
+ return components, pos
296
+
297
+ def _calculate_weekdate(self, year, week, day):
298
+ """
299
+ Calculate the day of corresponding to the ISO year-week-day calendar.
300
+
301
+ This function is effectively the inverse of
302
+ :func:`datetime.date.isocalendar`.
303
+
304
+ :param year:
305
+ The year in the ISO calendar
306
+
307
+ :param week:
308
+ The week in the ISO calendar - range is [1, 53]
309
+
310
+ :param day:
311
+ The day in the ISO calendar - range is [1 (MON), 7 (SUN)]
312
+
313
+ :return:
314
+ Returns a :class:`datetime.date`
315
+ """
316
+ if not 0 < week < 54:
317
+ raise ValueError('Invalid week: {}'.format(week))
318
+
319
+ if not 0 < day < 8: # Range is 1-7
320
+ raise ValueError('Invalid weekday: {}'.format(day))
321
+
322
+ # Get week 1 for the specific year:
323
+ jan_4 = date(year, 1, 4) # Week 1 always has January 4th in it
324
+ week_1 = jan_4 - timedelta(days=jan_4.isocalendar()[2] - 1)
325
+
326
+ # Now add the specific number of weeks and days to get what we want
327
+ week_offset = (week - 1) * 7 + (day - 1)
328
+ return week_1 + timedelta(days=week_offset)
329
+
330
+ def _parse_isotime(self, timestr):
331
+ len_str = len(timestr)
332
+ components = [0, 0, 0, 0, None]
333
+ pos = 0
334
+ comp = -1
335
+
336
+ if len_str < 2:
337
+ raise ValueError('ISO time too short')
338
+
339
+ has_sep = False
340
+
341
+ while pos < len_str and comp < 5:
342
+ comp += 1
343
+
344
+ if timestr[pos:pos + 1] in b'-+Zz':
345
+ # Detect time zone boundary
346
+ components[-1] = self._parse_tzstr(timestr[pos:])
347
+ pos = len_str
348
+ break
349
+
350
+ if comp == 1 and timestr[pos:pos+1] == self._TIME_SEP:
351
+ has_sep = True
352
+ pos += 1
353
+ elif comp == 2 and has_sep:
354
+ if timestr[pos:pos+1] != self._TIME_SEP:
355
+ raise ValueError('Inconsistent use of colon separator')
356
+ pos += 1
357
+
358
+ if comp < 3:
359
+ # Hour, minute, second
360
+ components[comp] = int(timestr[pos:pos + 2])
361
+ pos += 2
362
+
363
+ if comp == 3:
364
+ # Fraction of a second
365
+ frac = self._FRACTION_REGEX.match(timestr[pos:])
366
+ if not frac:
367
+ continue
368
+
369
+ us_str = frac.group(1)[:6] # Truncate to microseconds
370
+ components[comp] = int(us_str) * 10**(6 - len(us_str))
371
+ pos += len(frac.group())
372
+
373
+ if pos < len_str:
374
+ raise ValueError('Unused components in ISO string')
375
+
376
+ if components[0] == 24:
377
+ # Standard supports 00:00 and 24:00 as representations of midnight
378
+ if any(component != 0 for component in components[1:4]):
379
+ raise ValueError('Hour may only be 24 at 24:00:00.000')
380
+
381
+ return components
382
+
383
+ def _parse_tzstr(self, tzstr, zero_as_utc=True):
384
+ if tzstr == b'Z' or tzstr == b'z':
385
+ return tz.UTC
386
+
387
+ if len(tzstr) not in {3, 5, 6}:
388
+ raise ValueError('Time zone offset must be 1, 3, 5 or 6 characters')
389
+
390
+ if tzstr[0:1] == b'-':
391
+ mult = -1
392
+ elif tzstr[0:1] == b'+':
393
+ mult = 1
394
+ else:
395
+ raise ValueError('Time zone offset requires sign')
396
+
397
+ hours = int(tzstr[1:3])
398
+ if len(tzstr) == 3:
399
+ minutes = 0
400
+ else:
401
+ minutes = int(tzstr[(4 if tzstr[3:4] == self._TIME_SEP else 3):])
402
+
403
+ if zero_as_utc and hours == 0 and minutes == 0:
404
+ return tz.UTC
405
+ else:
406
+ if minutes > 59:
407
+ raise ValueError('Invalid minutes in time zone offset')
408
+
409
+ if hours > 23:
410
+ raise ValueError('Invalid hours in time zone offset')
411
+
412
+ return tz.tzoffset(None, mult * (hours * 60 + minutes) * 60)
413
+
414
+
415
+ DEFAULT_ISOPARSER = isoparser()
416
+ isoparse = DEFAULT_ISOPARSER.isoparse
hug/lib/python3.12/site-packages/dateutil/relativedelta.py ADDED
@@ -0,0 +1,596 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import datetime
3
+ import calendar
4
+
5
+ import operator
6
+ from math import copysign
7
+
8
+ from six import integer_types
9
+ from warnings import warn
10
+
11
+ from ._common import weekday
12
+
13
+ MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
14
+
15
+ __all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
16
+
17
+
18
+ class relativedelta(object):
19
+ """
20
+ The relativedelta type is designed to be applied to an existing datetime and
21
+ can replace specific components of that datetime, or represents an interval
22
+ of time.
23
+
24
+ It is based on the specification of the excellent work done by M.-A. Lemburg
25
+ in his
26
+ `mx.DateTime <https://www.egenix.com/products/python/mxBase/mxDateTime/>`_ extension.
27
+ However, notice that this type does *NOT* implement the same algorithm as
28
+ his work. Do *NOT* expect it to behave like mx.DateTime's counterpart.
29
+
30
+ There are two different ways to build a relativedelta instance. The
31
+ first one is passing it two date/datetime classes::
32
+
33
+ relativedelta(datetime1, datetime2)
34
+
35
+ The second one is passing it any number of the following keyword arguments::
36
+
37
+ relativedelta(arg1=x,arg2=y,arg3=z...)
38
+
39
+ year, month, day, hour, minute, second, microsecond:
40
+ Absolute information (argument is singular); adding or subtracting a
41
+ relativedelta with absolute information does not perform an arithmetic
42
+ operation, but rather REPLACES the corresponding value in the
43
+ original datetime with the value(s) in relativedelta.
44
+
45
+ years, months, weeks, days, hours, minutes, seconds, microseconds:
46
+ Relative information, may be negative (argument is plural); adding
47
+ or subtracting a relativedelta with relative information performs
48
+ the corresponding arithmetic operation on the original datetime value
49
+ with the information in the relativedelta.
50
+
51
+ weekday:
52
+ One of the weekday instances (MO, TU, etc) available in the
53
+ relativedelta module. These instances may receive a parameter N,
54
+ specifying the Nth weekday, which could be positive or negative
55
+ (like MO(+1) or MO(-2)). Not specifying it is the same as specifying
56
+ +1. You can also use an integer, where 0=MO. This argument is always
57
+ relative e.g. if the calculated date is already Monday, using MO(1)
58
+ or MO(-1) won't change the day. To effectively make it absolute, use
59
+ it in combination with the day argument (e.g. day=1, MO(1) for first
60
+ Monday of the month).
61
+
62
+ leapdays:
63
+ Will add given days to the date found, if year is a leap
64
+ year, and the date found is post 28 of february.
65
+
66
+ yearday, nlyearday:
67
+ Set the yearday or the non-leap year day (jump leap days).
68
+ These are converted to day/month/leapdays information.
69
+
70
+ There are relative and absolute forms of the keyword
71
+ arguments. The plural is relative, and the singular is
72
+ absolute. For each argument in the order below, the absolute form
73
+ is applied first (by setting each attribute to that value) and
74
+ then the relative form (by adding the value to the attribute).
75
+
76
+ The order of attributes considered when this relativedelta is
77
+ added to a datetime is:
78
+
79
+ 1. Year
80
+ 2. Month
81
+ 3. Day
82
+ 4. Hours
83
+ 5. Minutes
84
+ 6. Seconds
85
+ 7. Microseconds
86
+
87
+ Finally, weekday is applied, using the rule described above.
88
+
89
+ For example
90
+
91
+ >>> from datetime import datetime
92
+ >>> from dateutil.relativedelta import relativedelta, MO
93
+ >>> dt = datetime(2018, 4, 9, 13, 37, 0)
94
+ >>> delta = relativedelta(hours=25, day=1, weekday=MO(1))
95
+ >>> dt + delta
96
+ datetime.datetime(2018, 4, 2, 14, 37)
97
+
98
+ First, the day is set to 1 (the first of the month), then 25 hours
99
+ are added, to get to the 2nd day and 14th hour, finally the
100
+ weekday is applied, but since the 2nd is already a Monday there is
101
+ no effect.
102
+
103
+ """
104
+
105
+ def __init__(self, dt1=None, dt2=None,
106
+ years=0, months=0, days=0, leapdays=0, weeks=0,
107
+ hours=0, minutes=0, seconds=0, microseconds=0,
108
+ year=None, month=None, day=None, weekday=None,
109
+ yearday=None, nlyearday=None,
110
+ hour=None, minute=None, second=None, microsecond=None):
111
+
112
+ if dt1 and dt2:
113
+ # datetime is a subclass of date. So both must be date
114
+ if not (isinstance(dt1, datetime.date) and
115
+ isinstance(dt2, datetime.date)):
116
+ raise TypeError("relativedelta only diffs datetime/date")
117
+
118
+ # We allow two dates, or two datetimes, so we coerce them to be
119
+ # of the same type
120
+ if (isinstance(dt1, datetime.datetime) !=
121
+ isinstance(dt2, datetime.datetime)):
122
+ if not isinstance(dt1, datetime.datetime):
123
+ dt1 = datetime.datetime.fromordinal(dt1.toordinal())
124
+ elif not isinstance(dt2, datetime.datetime):
125
+ dt2 = datetime.datetime.fromordinal(dt2.toordinal())
126
+
127
+ self.years = 0
128
+ self.months = 0
129
+ self.days = 0
130
+ self.leapdays = 0
131
+ self.hours = 0
132
+ self.minutes = 0
133
+ self.seconds = 0
134
+ self.microseconds = 0
135
+ self.year = None
136
+ self.month = None
137
+ self.day = None
138
+ self.weekday = None
139
+ self.hour = None
140
+ self.minute = None
141
+ self.second = None
142
+ self.microsecond = None
143
+ self._has_time = 0
144
+
145
+ # Get year / month delta between the two
146
+ months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month)
147
+ self._set_months(months)
148
+
149
+ # Remove the year/month delta so the timedelta is just well-defined
150
+ # time units (seconds, days and microseconds)
151
+ dtm = self.__radd__(dt2)
152
+
153
+ # If we've overshot our target, make an adjustment
154
+ if dt1 < dt2:
155
+ compare = operator.gt
156
+ increment = 1
157
+ else:
158
+ compare = operator.lt
159
+ increment = -1
160
+
161
+ while compare(dt1, dtm):
162
+ months += increment
163
+ self._set_months(months)
164
+ dtm = self.__radd__(dt2)
165
+
166
+ # Get the timedelta between the "months-adjusted" date and dt1
167
+ delta = dt1 - dtm
168
+ self.seconds = delta.seconds + delta.days * 86400
169
+ self.microseconds = delta.microseconds
170
+ else:
171
+ # Check for non-integer values in integer-only quantities
172
+ if any(x is not None and x != int(x) for x in (years, months)):
173
+ raise ValueError("Non-integer years and months are "
174
+ "ambiguous and not currently supported.")
175
+
176
+ # Relative information
177
+ self.years = int(years)
178
+ self.months = int(months)
179
+ self.days = days + weeks * 7
180
+ self.leapdays = leapdays
181
+ self.hours = hours
182
+ self.minutes = minutes
183
+ self.seconds = seconds
184
+ self.microseconds = microseconds
185
+
186
+ # Absolute information
187
+ self.year = year
188
+ self.month = month
189
+ self.day = day
190
+ self.hour = hour
191
+ self.minute = minute
192
+ self.second = second
193
+ self.microsecond = microsecond
194
+
195
+ if any(x is not None and int(x) != x
196
+ for x in (year, month, day, hour,
197
+ minute, second, microsecond)):
198
+ # For now we'll deprecate floats - later it'll be an error.
199
+ warn("Non-integer value passed as absolute information. " +
200
+ "This is not a well-defined condition and will raise " +
201
+ "errors in future versions.", DeprecationWarning)
202
+
203
+ if isinstance(weekday, integer_types):
204
+ self.weekday = weekdays[weekday]
205
+ else:
206
+ self.weekday = weekday
207
+
208
+ yday = 0
209
+ if nlyearday:
210
+ yday = nlyearday
211
+ elif yearday:
212
+ yday = yearday
213
+ if yearday > 59:
214
+ self.leapdays = -1
215
+ if yday:
216
+ ydayidx = [31, 59, 90, 120, 151, 181, 212,
217
+ 243, 273, 304, 334, 366]
218
+ for idx, ydays in enumerate(ydayidx):
219
+ if yday <= ydays:
220
+ self.month = idx+1
221
+ if idx == 0:
222
+ self.day = yday
223
+ else:
224
+ self.day = yday-ydayidx[idx-1]
225
+ break
226
+ else:
227
+ raise ValueError("invalid year day (%d)" % yday)
228
+
229
+ self._fix()
230
+
231
+ def _fix(self):
232
+ if abs(self.microseconds) > 999999:
233
+ s = _sign(self.microseconds)
234
+ div, mod = divmod(self.microseconds * s, 1000000)
235
+ self.microseconds = mod * s
236
+ self.seconds += div * s
237
+ if abs(self.seconds) > 59:
238
+ s = _sign(self.seconds)
239
+ div, mod = divmod(self.seconds * s, 60)
240
+ self.seconds = mod * s
241
+ self.minutes += div * s
242
+ if abs(self.minutes) > 59:
243
+ s = _sign(self.minutes)
244
+ div, mod = divmod(self.minutes * s, 60)
245
+ self.minutes = mod * s
246
+ self.hours += div * s
247
+ if abs(self.hours) > 23:
248
+ s = _sign(self.hours)
249
+ div, mod = divmod(self.hours * s, 24)
250
+ self.hours = mod * s
251
+ self.days += div * s
252
+ if abs(self.months) > 11:
253
+ s = _sign(self.months)
254
+ div, mod = divmod(self.months * s, 12)
255
+ self.months = mod * s
256
+ self.years += div * s
257
+ if (self.hours or self.minutes or self.seconds or self.microseconds
258
+ or self.hour is not None or self.minute is not None or
259
+ self.second is not None or self.microsecond is not None):
260
+ self._has_time = 1
261
+ else:
262
+ self._has_time = 0
263
+
264
+ @property
265
+ def weeks(self):
266
+ return int(self.days / 7.0)
267
+
268
+ @weeks.setter
269
+ def weeks(self, value):
270
+ self.days = self.days - (self.weeks * 7) + value * 7
271
+
272
+ def _set_months(self, months):
273
+ self.months = months
274
+ if abs(self.months) > 11:
275
+ s = _sign(self.months)
276
+ div, mod = divmod(self.months * s, 12)
277
+ self.months = mod * s
278
+ self.years = div * s
279
+ else:
280
+ self.years = 0
281
+
282
+ def normalized(self):
283
+ """
284
+ Return a version of this object represented entirely using integer
285
+ values for the relative attributes.
286
+
287
+ >>> relativedelta(days=1.5, hours=2).normalized()
288
+ relativedelta(days=+1, hours=+14)
289
+
290
+ :return:
291
+ Returns a :class:`dateutil.relativedelta.relativedelta` object.
292
+ """
293
+ # Cascade remainders down (rounding each to roughly nearest microsecond)
294
+ days = int(self.days)
295
+
296
+ hours_f = round(self.hours + 24 * (self.days - days), 11)
297
+ hours = int(hours_f)
298
+
299
+ minutes_f = round(self.minutes + 60 * (hours_f - hours), 10)
300
+ minutes = int(minutes_f)
301
+
302
+ seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8)
303
+ seconds = int(seconds_f)
304
+
305
+ microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds))
306
+
307
+ # Constructor carries overflow back up with call to _fix()
308
+ return self.__class__(years=self.years, months=self.months,
309
+ days=days, hours=hours, minutes=minutes,
310
+ seconds=seconds, microseconds=microseconds,
311
+ leapdays=self.leapdays, year=self.year,
312
+ month=self.month, day=self.day,
313
+ weekday=self.weekday, hour=self.hour,
314
+ minute=self.minute, second=self.second,
315
+ microsecond=self.microsecond)
316
+
317
+ def __add__(self, other):
318
+ if isinstance(other, relativedelta):
319
+ return self.__class__(years=other.years + self.years,
320
+ months=other.months + self.months,
321
+ days=other.days + self.days,
322
+ hours=other.hours + self.hours,
323
+ minutes=other.minutes + self.minutes,
324
+ seconds=other.seconds + self.seconds,
325
+ microseconds=(other.microseconds +
326
+ self.microseconds),
327
+ leapdays=other.leapdays or self.leapdays,
328
+ year=(other.year if other.year is not None
329
+ else self.year),
330
+ month=(other.month if other.month is not None
331
+ else self.month),
332
+ day=(other.day if other.day is not None
333
+ else self.day),
334
+ weekday=(other.weekday if other.weekday is not None
335
+ else self.weekday),
336
+ hour=(other.hour if other.hour is not None
337
+ else self.hour),
338
+ minute=(other.minute if other.minute is not None
339
+ else self.minute),
340
+ second=(other.second if other.second is not None
341
+ else self.second),
342
+ microsecond=(other.microsecond if other.microsecond
343
+ is not None else
344
+ self.microsecond))
345
+ if isinstance(other, datetime.timedelta):
346
+ return self.__class__(years=self.years,
347
+ months=self.months,
348
+ days=self.days + other.days,
349
+ hours=self.hours,
350
+ minutes=self.minutes,
351
+ seconds=self.seconds + other.seconds,
352
+ microseconds=self.microseconds + other.microseconds,
353
+ leapdays=self.leapdays,
354
+ year=self.year,
355
+ month=self.month,
356
+ day=self.day,
357
+ weekday=self.weekday,
358
+ hour=self.hour,
359
+ minute=self.minute,
360
+ second=self.second,
361
+ microsecond=self.microsecond)
362
+ if not isinstance(other, datetime.date):
363
+ return NotImplemented
364
+ elif self._has_time and not isinstance(other, datetime.datetime):
365
+ other = datetime.datetime.fromordinal(other.toordinal())
366
+ year = (self.year or other.year)+self.years
367
+ month = self.month or other.month
368
+ if self.months:
369
+ assert 1 <= abs(self.months) <= 12
370
+ month += self.months
371
+ if month > 12:
372
+ year += 1
373
+ month -= 12
374
+ elif month < 1:
375
+ year -= 1
376
+ month += 12
377
+ day = min(calendar.monthrange(year, month)[1],
378
+ self.day or other.day)
379
+ repl = {"year": year, "month": month, "day": day}
380
+ for attr in ["hour", "minute", "second", "microsecond"]:
381
+ value = getattr(self, attr)
382
+ if value is not None:
383
+ repl[attr] = value
384
+ days = self.days
385
+ if self.leapdays and month > 2 and calendar.isleap(year):
386
+ days += self.leapdays
387
+ ret = (other.replace(**repl)
388
+ + datetime.timedelta(days=days,
389
+ hours=self.hours,
390
+ minutes=self.minutes,
391
+ seconds=self.seconds,
392
+ microseconds=self.microseconds))
393
+ if self.weekday:
394
+ weekday, nth = self.weekday.weekday, self.weekday.n or 1
395
+ jumpdays = (abs(nth) - 1) * 7
396
+ if nth > 0:
397
+ jumpdays += (7 - ret.weekday() + weekday) % 7
398
+ else:
399
+ jumpdays += (ret.weekday() - weekday) % 7
400
+ jumpdays *= -1
401
+ ret += datetime.timedelta(days=jumpdays)
402
+ return ret
403
+
404
+ def __radd__(self, other):
405
+ return self.__add__(other)
406
+
407
+ def __rsub__(self, other):
408
+ return self.__neg__().__radd__(other)
409
+
410
+ def __sub__(self, other):
411
+ if not isinstance(other, relativedelta):
412
+ return NotImplemented # In case the other object defines __rsub__
413
+ return self.__class__(years=self.years - other.years,
414
+ months=self.months - other.months,
415
+ days=self.days - other.days,
416
+ hours=self.hours - other.hours,
417
+ minutes=self.minutes - other.minutes,
418
+ seconds=self.seconds - other.seconds,
419
+ microseconds=self.microseconds - other.microseconds,
420
+ leapdays=self.leapdays or other.leapdays,
421
+ year=(self.year if self.year is not None
422
+ else other.year),
423
+ month=(self.month if self.month is not None else
424
+ other.month),
425
+ day=(self.day if self.day is not None else
426
+ other.day),
427
+ weekday=(self.weekday if self.weekday is not None else
428
+ other.weekday),
429
+ hour=(self.hour if self.hour is not None else
430
+ other.hour),
431
+ minute=(self.minute if self.minute is not None else
432
+ other.minute),
433
+ second=(self.second if self.second is not None else
434
+ other.second),
435
+ microsecond=(self.microsecond if self.microsecond
436
+ is not None else
437
+ other.microsecond))
438
+
439
+ def __abs__(self):
440
+ return self.__class__(years=abs(self.years),
441
+ months=abs(self.months),
442
+ days=abs(self.days),
443
+ hours=abs(self.hours),
444
+ minutes=abs(self.minutes),
445
+ seconds=abs(self.seconds),
446
+ microseconds=abs(self.microseconds),
447
+ leapdays=self.leapdays,
448
+ year=self.year,
449
+ month=self.month,
450
+ day=self.day,
451
+ weekday=self.weekday,
452
+ hour=self.hour,
453
+ minute=self.minute,
454
+ second=self.second,
455
+ microsecond=self.microsecond)
456
+
457
+ def __neg__(self):
458
+ return self.__class__(years=-self.years,
459
+ months=-self.months,
460
+ days=-self.days,
461
+ hours=-self.hours,
462
+ minutes=-self.minutes,
463
+ seconds=-self.seconds,
464
+ microseconds=-self.microseconds,
465
+ leapdays=self.leapdays,
466
+ year=self.year,
467
+ month=self.month,
468
+ day=self.day,
469
+ weekday=self.weekday,
470
+ hour=self.hour,
471
+ minute=self.minute,
472
+ second=self.second,
473
+ microsecond=self.microsecond)
474
+
475
+ def __bool__(self):
476
+ return not (not self.years and
477
+ not self.months and
478
+ not self.days and
479
+ not self.hours and
480
+ not self.minutes and
481
+ not self.seconds and
482
+ not self.microseconds and
483
+ not self.leapdays and
484
+ self.year is None and
485
+ self.month is None and
486
+ self.day is None and
487
+ self.weekday is None and
488
+ self.hour is None and
489
+ self.minute is None and
490
+ self.second is None and
491
+ self.microsecond is None)
492
+ # Compatibility with Python 2.x
493
+ __nonzero__ = __bool__
494
+
495
+ def __mul__(self, other):
496
+ try:
497
+ f = float(other)
498
+ except TypeError:
499
+ return NotImplemented
500
+
501
+ return self.__class__(years=int(self.years * f),
502
+ months=int(self.months * f),
503
+ days=int(self.days * f),
504
+ hours=int(self.hours * f),
505
+ minutes=int(self.minutes * f),
506
+ seconds=int(self.seconds * f),
507
+ microseconds=int(self.microseconds * f),
508
+ leapdays=self.leapdays,
509
+ year=self.year,
510
+ month=self.month,
511
+ day=self.day,
512
+ weekday=self.weekday,
513
+ hour=self.hour,
514
+ minute=self.minute,
515
+ second=self.second,
516
+ microsecond=self.microsecond)
517
+
518
+ __rmul__ = __mul__
519
+
520
+ def __eq__(self, other):
521
+ if not isinstance(other, relativedelta):
522
+ return NotImplemented
523
+ if self.weekday or other.weekday:
524
+ if not self.weekday or not other.weekday:
525
+ return False
526
+ if self.weekday.weekday != other.weekday.weekday:
527
+ return False
528
+ n1, n2 = self.weekday.n, other.weekday.n
529
+ if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)):
530
+ return False
531
+ return (self.years == other.years and
532
+ self.months == other.months and
533
+ self.days == other.days and
534
+ self.hours == other.hours and
535
+ self.minutes == other.minutes and
536
+ self.seconds == other.seconds and
537
+ self.microseconds == other.microseconds and
538
+ self.leapdays == other.leapdays and
539
+ self.year == other.year and
540
+ self.month == other.month and
541
+ self.day == other.day and
542
+ self.hour == other.hour and
543
+ self.minute == other.minute and
544
+ self.second == other.second and
545
+ self.microsecond == other.microsecond)
546
+
547
+ def __hash__(self):
548
+ return hash((
549
+ self.weekday,
550
+ self.years,
551
+ self.months,
552
+ self.days,
553
+ self.hours,
554
+ self.minutes,
555
+ self.seconds,
556
+ self.microseconds,
557
+ self.leapdays,
558
+ self.year,
559
+ self.month,
560
+ self.day,
561
+ self.hour,
562
+ self.minute,
563
+ self.second,
564
+ self.microsecond,
565
+ ))
566
+
567
+ def __div__(self, other):
568
+ try:
569
+ reciprocal = 1 / float(other)
570
+ except TypeError:
571
+ return NotImplemented
572
+
573
+ return self.__mul__(reciprocal)
574
+
575
+ __truediv__ = __div__
576
+
577
+ def __repr__(self):
578
+ l = []
579
+ for attr in ["years", "months", "days", "leapdays",
580
+ "hours", "minutes", "seconds", "microseconds"]:
581
+ value = getattr(self, attr)
582
+ if value:
583
+ l.append("{attr}={value:+g}".format(attr=attr, value=value))
584
+ for attr in ["year", "month", "day", "weekday",
585
+ "hour", "minute", "second", "microsecond"]:
586
+ value = getattr(self, attr)
587
+ if value is not None:
588
+ l.append("{attr}={value}".format(attr=attr, value=repr(value)))
589
+ return "{classname}({attrs})".format(classname=self.__class__.__name__,
590
+ attrs=", ".join(l))
591
+
592
+
593
+ def _sign(x):
594
+ return int(copysign(1, x))
595
+
596
+ # vim:ts=4:sw=4:et
hug/lib/python3.12/site-packages/dateutil/rrule.py ADDED
@@ -0,0 +1,1764 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ The rrule module offers a small, complete, and very fast, implementation of
4
+ the recurrence rules documented in the
5
+ `iCalendar RFC <https://tools.ietf.org/html/rfc5545>`_,
6
+ including support for caching of results.
7
+ """
8
+ import calendar
9
+ import datetime
10
+ import heapq
11
+ import itertools
12
+ import re
13
+ import sys
14
+ from functools import wraps
15
+ # For warning about deprecation of until and count
16
+ from warnings import warn
17
+
18
+ from six import advance_iterator, integer_types
19
+
20
+ from six.moves import _thread, range
21
+
22
+ from ._common import weekday as weekdaybase
23
+
24
+ try:
25
+ from math import gcd
26
+ except ImportError:
27
+ from fractions import gcd
28
+
29
+ __all__ = ["rrule", "rruleset", "rrulestr",
30
+ "YEARLY", "MONTHLY", "WEEKLY", "DAILY",
31
+ "HOURLY", "MINUTELY", "SECONDLY",
32
+ "MO", "TU", "WE", "TH", "FR", "SA", "SU"]
33
+
34
+ # Every mask is 7 days longer to handle cross-year weekly periods.
35
+ M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 +
36
+ [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7)
37
+ M365MASK = list(M366MASK)
38
+ M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32))
39
+ MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
40
+ MDAY365MASK = list(MDAY366MASK)
41
+ M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0))
42
+ NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7])
43
+ NMDAY365MASK = list(NMDAY366MASK)
44
+ M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366)
45
+ M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
46
+ WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55
47
+ del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31]
48
+ MDAY365MASK = tuple(MDAY365MASK)
49
+ M365MASK = tuple(M365MASK)
50
+
51
+ FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY']
52
+
53
+ (YEARLY,
54
+ MONTHLY,
55
+ WEEKLY,
56
+ DAILY,
57
+ HOURLY,
58
+ MINUTELY,
59
+ SECONDLY) = list(range(7))
60
+
61
+ # Imported on demand.
62
+ easter = None
63
+ parser = None
64
+
65
+
66
+ class weekday(weekdaybase):
67
+ """
68
+ This version of weekday does not allow n = 0.
69
+ """
70
+ def __init__(self, wkday, n=None):
71
+ if n == 0:
72
+ raise ValueError("Can't create weekday with n==0")
73
+
74
+ super(weekday, self).__init__(wkday, n)
75
+
76
+
77
+ MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7))
78
+
79
+
80
+ def _invalidates_cache(f):
81
+ """
82
+ Decorator for rruleset methods which may invalidate the
83
+ cached length.
84
+ """
85
+ @wraps(f)
86
+ def inner_func(self, *args, **kwargs):
87
+ rv = f(self, *args, **kwargs)
88
+ self._invalidate_cache()
89
+ return rv
90
+
91
+ return inner_func
92
+
93
+
94
+ class rrulebase(object):
95
+ def __init__(self, cache=False):
96
+ if cache:
97
+ self._cache = []
98
+ self._cache_lock = _thread.allocate_lock()
99
+ self._invalidate_cache()
100
+ else:
101
+ self._cache = None
102
+ self._cache_complete = False
103
+ self._len = None
104
+
105
+ def __reduce_ex__(self, protocol):
106
+ rv = super(rrulebase, self).__reduce_ex__(protocol)
107
+ state, slots = rv[2], None
108
+ if isinstance(state, tuple):
109
+ state, slots = state
110
+ if self._cache is not None:
111
+ state = dict(state)
112
+ state.pop("_cache_lock", None)
113
+ state.pop("_cache_gen", None)
114
+ state["_cache"] = []
115
+ if slots is not None:
116
+ state = (state, slots)
117
+ return rv[:2] + (state,) + rv[3:]
118
+
119
+ def __setstate__(self, state):
120
+ if isinstance(state, tuple):
121
+ state, slots = state
122
+ for name, value in slots.items():
123
+ setattr(self, name, value)
124
+ self.__dict__.update(state)
125
+ if self._cache is not None:
126
+ self._cache_lock = _thread.allocate_lock()
127
+ self._invalidate_cache()
128
+
129
+ def __iter__(self):
130
+ if self._cache_complete:
131
+ return iter(self._cache)
132
+ elif self._cache is None:
133
+ return self._iter()
134
+ else:
135
+ return self._iter_cached()
136
+
137
+ def _invalidate_cache(self):
138
+ if self._cache is not None:
139
+ self._cache = []
140
+ self._cache_complete = False
141
+ self._cache_gen = self._iter()
142
+
143
+ if self._cache_lock.locked():
144
+ self._cache_lock.release()
145
+
146
+ self._len = None
147
+
148
+ def _iter_cached(self):
149
+ i = 0
150
+ gen = self._cache_gen
151
+ cache = self._cache
152
+ acquire = self._cache_lock.acquire
153
+ release = self._cache_lock.release
154
+ while gen:
155
+ if i == len(cache):
156
+ acquire()
157
+ if self._cache_complete:
158
+ break
159
+ try:
160
+ for j in range(10):
161
+ cache.append(advance_iterator(gen))
162
+ except StopIteration:
163
+ self._cache_gen = gen = None
164
+ self._cache_complete = True
165
+ break
166
+ release()
167
+ yield cache[i]
168
+ i += 1
169
+ while i < self._len:
170
+ yield cache[i]
171
+ i += 1
172
+
173
+ def __getitem__(self, item):
174
+ if self._cache_complete:
175
+ return self._cache[item]
176
+ elif isinstance(item, slice):
177
+ if item.step and item.step < 0:
178
+ return list(iter(self))[item]
179
+ else:
180
+ return list(itertools.islice(self,
181
+ item.start or 0,
182
+ item.stop or sys.maxsize,
183
+ item.step or 1))
184
+ elif item >= 0:
185
+ gen = iter(self)
186
+ try:
187
+ for i in range(item+1):
188
+ res = advance_iterator(gen)
189
+ except StopIteration:
190
+ raise IndexError
191
+ return res
192
+ else:
193
+ return list(iter(self))[item]
194
+
195
+ def __contains__(self, item):
196
+ if self._cache_complete:
197
+ return item in self._cache
198
+ else:
199
+ for i in self:
200
+ if i == item:
201
+ return True
202
+ elif i > item:
203
+ return False
204
+ return False
205
+
206
+ # __len__() introduces a large performance penalty.
207
+ def count(self):
208
+ """ Returns the number of recurrences in this set. It will have go
209
+ through the whole recurrence, if this hasn't been done before. """
210
+ if self._len is None:
211
+ for x in self:
212
+ pass
213
+ return self._len
214
+
215
+ def before(self, dt, inc=False):
216
+ """ Returns the last recurrence before the given datetime instance. The
217
+ inc keyword defines what happens if dt is an occurrence. With
218
+ inc=True, if dt itself is an occurrence, it will be returned. """
219
+ if self._cache_complete:
220
+ gen = self._cache
221
+ else:
222
+ gen = self
223
+ last = None
224
+ if inc:
225
+ for i in gen:
226
+ if i > dt:
227
+ break
228
+ last = i
229
+ else:
230
+ for i in gen:
231
+ if i >= dt:
232
+ break
233
+ last = i
234
+ return last
235
+
236
+ def after(self, dt, inc=False):
237
+ """ Returns the first recurrence after the given datetime instance. The
238
+ inc keyword defines what happens if dt is an occurrence. With
239
+ inc=True, if dt itself is an occurrence, it will be returned. """
240
+ if self._cache_complete:
241
+ gen = self._cache
242
+ else:
243
+ gen = self
244
+ if inc:
245
+ for i in gen:
246
+ if i >= dt:
247
+ return i
248
+ else:
249
+ for i in gen:
250
+ if i > dt:
251
+ return i
252
+ return None
253
+
254
+ def xafter(self, dt, count=None, inc=False):
255
+ """
256
+ Generator which yields up to `count` recurrences after the given
257
+ datetime instance, equivalent to `after`.
258
+
259
+ :param dt:
260
+ The datetime at which to start generating recurrences.
261
+
262
+ :param count:
263
+ The maximum number of recurrences to generate. If `None` (default),
264
+ dates are generated until the recurrence rule is exhausted.
265
+
266
+ :param inc:
267
+ If `dt` is an instance of the rule and `inc` is `True`, it is
268
+ included in the output.
269
+
270
+ :yields: Yields a sequence of `datetime` objects.
271
+ """
272
+
273
+ if self._cache_complete:
274
+ gen = self._cache
275
+ else:
276
+ gen = self
277
+
278
+ # Select the comparison function
279
+ if inc:
280
+ comp = lambda dc, dtc: dc >= dtc
281
+ else:
282
+ comp = lambda dc, dtc: dc > dtc
283
+
284
+ # Generate dates
285
+ n = 0
286
+ for d in gen:
287
+ if comp(d, dt):
288
+ if count is not None:
289
+ n += 1
290
+ if n > count:
291
+ break
292
+
293
+ yield d
294
+
295
+ def between(self, after, before, inc=False, count=1):
296
+ """ Returns all the occurrences of the rrule between after and before.
297
+ The inc keyword defines what happens if after and/or before are
298
+ themselves occurrences. With inc=True, they will be included in the
299
+ list, if they are found in the recurrence set. """
300
+ if self._cache_complete:
301
+ gen = self._cache
302
+ else:
303
+ gen = self
304
+ started = False
305
+ l = []
306
+ if inc:
307
+ for i in gen:
308
+ if i > before:
309
+ break
310
+ elif not started:
311
+ if i >= after:
312
+ started = True
313
+ l.append(i)
314
+ else:
315
+ l.append(i)
316
+ else:
317
+ for i in gen:
318
+ if i >= before:
319
+ break
320
+ elif not started:
321
+ if i > after:
322
+ started = True
323
+ l.append(i)
324
+ else:
325
+ l.append(i)
326
+ return l
327
+
328
+
329
+ class rrule(rrulebase):
330
+ """
331
+ That's the base of the rrule operation. It accepts all the keywords
332
+ defined in the RFC as its constructor parameters (except byday,
333
+ which was renamed to byweekday) and more. The constructor prototype is::
334
+
335
+ rrule(freq)
336
+
337
+ Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY,
338
+ or SECONDLY.
339
+
340
+ .. note::
341
+ Per RFC section 3.3.10, recurrence instances falling on invalid dates
342
+ and times are ignored rather than coerced:
343
+
344
+ Recurrence rules may generate recurrence instances with an invalid
345
+ date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM
346
+ on a day where the local time is moved forward by an hour at 1:00
347
+ AM). Such recurrence instances MUST be ignored and MUST NOT be
348
+ counted as part of the recurrence set.
349
+
350
+ This can lead to possibly surprising behavior when, for example, the
351
+ start date occurs at the end of the month:
352
+
353
+ >>> from dateutil.rrule import rrule, MONTHLY
354
+ >>> from datetime import datetime
355
+ >>> start_date = datetime(2014, 12, 31)
356
+ >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date))
357
+ ... # doctest: +NORMALIZE_WHITESPACE
358
+ [datetime.datetime(2014, 12, 31, 0, 0),
359
+ datetime.datetime(2015, 1, 31, 0, 0),
360
+ datetime.datetime(2015, 3, 31, 0, 0),
361
+ datetime.datetime(2015, 5, 31, 0, 0)]
362
+
363
+ Additionally, it supports the following keyword arguments:
364
+
365
+ :param dtstart:
366
+ The recurrence start. Besides being the base for the recurrence,
367
+ missing parameters in the final recurrence instances will also be
368
+ extracted from this date. If not given, datetime.now() will be used
369
+ instead.
370
+ :param interval:
371
+ The interval between each freq iteration. For example, when using
372
+ YEARLY, an interval of 2 means once every two years, but with HOURLY,
373
+ it means once every two hours. The default interval is 1.
374
+ :param wkst:
375
+ The week start day. Must be one of the MO, TU, WE constants, or an
376
+ integer, specifying the first day of the week. This will affect
377
+ recurrences based on weekly periods. The default week start is got
378
+ from calendar.firstweekday(), and may be modified by
379
+ calendar.setfirstweekday().
380
+ :param count:
381
+ If given, this determines how many occurrences will be generated.
382
+
383
+ .. note::
384
+ As of version 2.5.0, the use of the keyword ``until`` in conjunction
385
+ with ``count`` is deprecated, to make sure ``dateutil`` is fully
386
+ compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
387
+ html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
388
+ **must not** occur in the same call to ``rrule``.
389
+ :param until:
390
+ If given, this must be a datetime instance specifying the upper-bound
391
+ limit of the recurrence. The last recurrence in the rule is the greatest
392
+ datetime that is less than or equal to the value specified in the
393
+ ``until`` parameter.
394
+
395
+ .. note::
396
+ As of version 2.5.0, the use of the keyword ``until`` in conjunction
397
+ with ``count`` is deprecated, to make sure ``dateutil`` is fully
398
+ compliant with `RFC-5545 Sec. 3.3.10 <https://tools.ietf.org/
399
+ html/rfc5545#section-3.3.10>`_. Therefore, ``until`` and ``count``
400
+ **must not** occur in the same call to ``rrule``.
401
+ :param bysetpos:
402
+ If given, it must be either an integer, or a sequence of integers,
403
+ positive or negative. Each given integer will specify an occurrence
404
+ number, corresponding to the nth occurrence of the rule inside the
405
+ frequency period. For example, a bysetpos of -1 if combined with a
406
+ MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will
407
+ result in the last work day of every month.
408
+ :param bymonth:
409
+ If given, it must be either an integer, or a sequence of integers,
410
+ meaning the months to apply the recurrence to.
411
+ :param bymonthday:
412
+ If given, it must be either an integer, or a sequence of integers,
413
+ meaning the month days to apply the recurrence to.
414
+ :param byyearday:
415
+ If given, it must be either an integer, or a sequence of integers,
416
+ meaning the year days to apply the recurrence to.
417
+ :param byeaster:
418
+ If given, it must be either an integer, or a sequence of integers,
419
+ positive or negative. Each integer will define an offset from the
420
+ Easter Sunday. Passing the offset 0 to byeaster will yield the Easter
421
+ Sunday itself. This is an extension to the RFC specification.
422
+ :param byweekno:
423
+ If given, it must be either an integer, or a sequence of integers,
424
+ meaning the week numbers to apply the recurrence to. Week numbers
425
+ have the meaning described in ISO8601, that is, the first week of
426
+ the year is that containing at least four days of the new year.
427
+ :param byweekday:
428
+ If given, it must be either an integer (0 == MO), a sequence of
429
+ integers, one of the weekday constants (MO, TU, etc), or a sequence
430
+ of these constants. When given, these variables will define the
431
+ weekdays where the recurrence will be applied. It's also possible to
432
+ use an argument n for the weekday instances, which will mean the nth
433
+ occurrence of this weekday in the period. For example, with MONTHLY,
434
+ or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the
435
+ first friday of the month where the recurrence happens. Notice that in
436
+ the RFC documentation, this is specified as BYDAY, but was renamed to
437
+ avoid the ambiguity of that keyword.
438
+ :param byhour:
439
+ If given, it must be either an integer, or a sequence of integers,
440
+ meaning the hours to apply the recurrence to.
441
+ :param byminute:
442
+ If given, it must be either an integer, or a sequence of integers,
443
+ meaning the minutes to apply the recurrence to.
444
+ :param bysecond:
445
+ If given, it must be either an integer, or a sequence of integers,
446
+ meaning the seconds to apply the recurrence to.
447
+ :param cache:
448
+ If given, it must be a boolean value specifying to enable or disable
449
+ caching of results. If you will use the same rrule instance multiple
450
+ times, enabling caching will improve the performance considerably.
451
+ """
452
+ def __init__(self, freq, dtstart=None,
453
+ interval=1, wkst=None, count=None, until=None, bysetpos=None,
454
+ bymonth=None, bymonthday=None, byyearday=None, byeaster=None,
455
+ byweekno=None, byweekday=None,
456
+ byhour=None, byminute=None, bysecond=None,
457
+ cache=False):
458
+ super(rrule, self).__init__(cache)
459
+ global easter
460
+ if not dtstart:
461
+ if until and until.tzinfo:
462
+ dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0)
463
+ else:
464
+ dtstart = datetime.datetime.now().replace(microsecond=0)
465
+ elif not isinstance(dtstart, datetime.datetime):
466
+ dtstart = datetime.datetime.fromordinal(dtstart.toordinal())
467
+ else:
468
+ dtstart = dtstart.replace(microsecond=0)
469
+ self._dtstart = dtstart
470
+ self._tzinfo = dtstart.tzinfo
471
+ self._freq = freq
472
+ self._interval = interval
473
+ self._count = count
474
+
475
+ # Cache the original byxxx rules, if they are provided, as the _byxxx
476
+ # attributes do not necessarily map to the inputs, and this can be
477
+ # a problem in generating the strings. Only store things if they've
478
+ # been supplied (the string retrieval will just use .get())
479
+ self._original_rule = {}
480
+
481
+ if until and not isinstance(until, datetime.datetime):
482
+ until = datetime.datetime.fromordinal(until.toordinal())
483
+ self._until = until
484
+
485
+ if self._dtstart and self._until:
486
+ if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None):
487
+ # According to RFC5545 Section 3.3.10:
488
+ # https://tools.ietf.org/html/rfc5545#section-3.3.10
489
+ #
490
+ # > If the "DTSTART" property is specified as a date with UTC
491
+ # > time or a date with local time and time zone reference,
492
+ # > then the UNTIL rule part MUST be specified as a date with
493
+ # > UTC time.
494
+ raise ValueError(
495
+ 'RRULE UNTIL values must be specified in UTC when DTSTART '
496
+ 'is timezone-aware'
497
+ )
498
+
499
+ if count is not None and until:
500
+ warn("Using both 'count' and 'until' is inconsistent with RFC 5545"
501
+ " and has been deprecated in dateutil. Future versions will "
502
+ "raise an error.", DeprecationWarning)
503
+
504
+ if wkst is None:
505
+ self._wkst = calendar.firstweekday()
506
+ elif isinstance(wkst, integer_types):
507
+ self._wkst = wkst
508
+ else:
509
+ self._wkst = wkst.weekday
510
+
511
+ if bysetpos is None:
512
+ self._bysetpos = None
513
+ elif isinstance(bysetpos, integer_types):
514
+ if bysetpos == 0 or not (-366 <= bysetpos <= 366):
515
+ raise ValueError("bysetpos must be between 1 and 366, "
516
+ "or between -366 and -1")
517
+ self._bysetpos = (bysetpos,)
518
+ else:
519
+ self._bysetpos = tuple(bysetpos)
520
+ for pos in self._bysetpos:
521
+ if pos == 0 or not (-366 <= pos <= 366):
522
+ raise ValueError("bysetpos must be between 1 and 366, "
523
+ "or between -366 and -1")
524
+
525
+ if self._bysetpos:
526
+ self._original_rule['bysetpos'] = self._bysetpos
527
+
528
+ if (byweekno is None and byyearday is None and bymonthday is None and
529
+ byweekday is None and byeaster is None):
530
+ if freq == YEARLY:
531
+ if bymonth is None:
532
+ bymonth = dtstart.month
533
+ self._original_rule['bymonth'] = None
534
+ bymonthday = dtstart.day
535
+ self._original_rule['bymonthday'] = None
536
+ elif freq == MONTHLY:
537
+ bymonthday = dtstart.day
538
+ self._original_rule['bymonthday'] = None
539
+ elif freq == WEEKLY:
540
+ byweekday = dtstart.weekday()
541
+ self._original_rule['byweekday'] = None
542
+
543
+ # bymonth
544
+ if bymonth is None:
545
+ self._bymonth = None
546
+ else:
547
+ if isinstance(bymonth, integer_types):
548
+ bymonth = (bymonth,)
549
+
550
+ self._bymonth = tuple(sorted(set(bymonth)))
551
+
552
+ if 'bymonth' not in self._original_rule:
553
+ self._original_rule['bymonth'] = self._bymonth
554
+
555
+ # byyearday
556
+ if byyearday is None:
557
+ self._byyearday = None
558
+ else:
559
+ if isinstance(byyearday, integer_types):
560
+ byyearday = (byyearday,)
561
+
562
+ self._byyearday = tuple(sorted(set(byyearday)))
563
+ self._original_rule['byyearday'] = self._byyearday
564
+
565
+ # byeaster
566
+ if byeaster is not None:
567
+ if not easter:
568
+ from dateutil import easter
569
+ if isinstance(byeaster, integer_types):
570
+ self._byeaster = (byeaster,)
571
+ else:
572
+ self._byeaster = tuple(sorted(byeaster))
573
+
574
+ self._original_rule['byeaster'] = self._byeaster
575
+ else:
576
+ self._byeaster = None
577
+
578
+ # bymonthday
579
+ if bymonthday is None:
580
+ self._bymonthday = ()
581
+ self._bynmonthday = ()
582
+ else:
583
+ if isinstance(bymonthday, integer_types):
584
+ bymonthday = (bymonthday,)
585
+
586
+ bymonthday = set(bymonthday) # Ensure it's unique
587
+
588
+ self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0))
589
+ self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0))
590
+
591
+ # Storing positive numbers first, then negative numbers
592
+ if 'bymonthday' not in self._original_rule:
593
+ self._original_rule['bymonthday'] = tuple(
594
+ itertools.chain(self._bymonthday, self._bynmonthday))
595
+
596
+ # byweekno
597
+ if byweekno is None:
598
+ self._byweekno = None
599
+ else:
600
+ if isinstance(byweekno, integer_types):
601
+ byweekno = (byweekno,)
602
+
603
+ self._byweekno = tuple(sorted(set(byweekno)))
604
+
605
+ self._original_rule['byweekno'] = self._byweekno
606
+
607
+ # byweekday / bynweekday
608
+ if byweekday is None:
609
+ self._byweekday = None
610
+ self._bynweekday = None
611
+ else:
612
+ # If it's one of the valid non-sequence types, convert to a
613
+ # single-element sequence before the iterator that builds the
614
+ # byweekday set.
615
+ if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"):
616
+ byweekday = (byweekday,)
617
+
618
+ self._byweekday = set()
619
+ self._bynweekday = set()
620
+ for wday in byweekday:
621
+ if isinstance(wday, integer_types):
622
+ self._byweekday.add(wday)
623
+ elif not wday.n or freq > MONTHLY:
624
+ self._byweekday.add(wday.weekday)
625
+ else:
626
+ self._bynweekday.add((wday.weekday, wday.n))
627
+
628
+ if not self._byweekday:
629
+ self._byweekday = None
630
+ elif not self._bynweekday:
631
+ self._bynweekday = None
632
+
633
+ if self._byweekday is not None:
634
+ self._byweekday = tuple(sorted(self._byweekday))
635
+ orig_byweekday = [weekday(x) for x in self._byweekday]
636
+ else:
637
+ orig_byweekday = ()
638
+
639
+ if self._bynweekday is not None:
640
+ self._bynweekday = tuple(sorted(self._bynweekday))
641
+ orig_bynweekday = [weekday(*x) for x in self._bynweekday]
642
+ else:
643
+ orig_bynweekday = ()
644
+
645
+ if 'byweekday' not in self._original_rule:
646
+ self._original_rule['byweekday'] = tuple(itertools.chain(
647
+ orig_byweekday, orig_bynweekday))
648
+
649
+ # byhour
650
+ if byhour is None:
651
+ if freq < HOURLY:
652
+ self._byhour = {dtstart.hour}
653
+ else:
654
+ self._byhour = None
655
+ else:
656
+ if isinstance(byhour, integer_types):
657
+ byhour = (byhour,)
658
+
659
+ if freq == HOURLY:
660
+ self._byhour = self.__construct_byset(start=dtstart.hour,
661
+ byxxx=byhour,
662
+ base=24)
663
+ else:
664
+ self._byhour = set(byhour)
665
+
666
+ self._byhour = tuple(sorted(self._byhour))
667
+ self._original_rule['byhour'] = self._byhour
668
+
669
+ # byminute
670
+ if byminute is None:
671
+ if freq < MINUTELY:
672
+ self._byminute = {dtstart.minute}
673
+ else:
674
+ self._byminute = None
675
+ else:
676
+ if isinstance(byminute, integer_types):
677
+ byminute = (byminute,)
678
+
679
+ if freq == MINUTELY:
680
+ self._byminute = self.__construct_byset(start=dtstart.minute,
681
+ byxxx=byminute,
682
+ base=60)
683
+ else:
684
+ self._byminute = set(byminute)
685
+
686
+ self._byminute = tuple(sorted(self._byminute))
687
+ self._original_rule['byminute'] = self._byminute
688
+
689
+ # bysecond
690
+ if bysecond is None:
691
+ if freq < SECONDLY:
692
+ self._bysecond = ((dtstart.second,))
693
+ else:
694
+ self._bysecond = None
695
+ else:
696
+ if isinstance(bysecond, integer_types):
697
+ bysecond = (bysecond,)
698
+
699
+ self._bysecond = set(bysecond)
700
+
701
+ if freq == SECONDLY:
702
+ self._bysecond = self.__construct_byset(start=dtstart.second,
703
+ byxxx=bysecond,
704
+ base=60)
705
+ else:
706
+ self._bysecond = set(bysecond)
707
+
708
+ self._bysecond = tuple(sorted(self._bysecond))
709
+ self._original_rule['bysecond'] = self._bysecond
710
+
711
+ if self._freq >= HOURLY:
712
+ self._timeset = None
713
+ else:
714
+ self._timeset = []
715
+ for hour in self._byhour:
716
+ for minute in self._byminute:
717
+ for second in self._bysecond:
718
+ self._timeset.append(
719
+ datetime.time(hour, minute, second,
720
+ tzinfo=self._tzinfo))
721
+ self._timeset.sort()
722
+ self._timeset = tuple(self._timeset)
723
+
724
+ def __str__(self):
725
+ """
726
+ Output a string that would generate this RRULE if passed to rrulestr.
727
+ This is mostly compatible with RFC5545, except for the
728
+ dateutil-specific extension BYEASTER.
729
+ """
730
+
731
+ output = []
732
+ h, m, s = [None] * 3
733
+ if self._dtstart:
734
+ output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S'))
735
+ h, m, s = self._dtstart.timetuple()[3:6]
736
+
737
+ parts = ['FREQ=' + FREQNAMES[self._freq]]
738
+ if self._interval != 1:
739
+ parts.append('INTERVAL=' + str(self._interval))
740
+
741
+ if self._wkst:
742
+ parts.append('WKST=' + repr(weekday(self._wkst))[0:2])
743
+
744
+ if self._count is not None:
745
+ parts.append('COUNT=' + str(self._count))
746
+
747
+ if self._until:
748
+ parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S'))
749
+
750
+ if self._original_rule.get('byweekday') is not None:
751
+ # The str() method on weekday objects doesn't generate
752
+ # RFC5545-compliant strings, so we should modify that.
753
+ original_rule = dict(self._original_rule)
754
+ wday_strings = []
755
+ for wday in original_rule['byweekday']:
756
+ if wday.n:
757
+ wday_strings.append('{n:+d}{wday}'.format(
758
+ n=wday.n,
759
+ wday=repr(wday)[0:2]))
760
+ else:
761
+ wday_strings.append(repr(wday))
762
+
763
+ original_rule['byweekday'] = wday_strings
764
+ else:
765
+ original_rule = self._original_rule
766
+
767
+ partfmt = '{name}={vals}'
768
+ for name, key in [('BYSETPOS', 'bysetpos'),
769
+ ('BYMONTH', 'bymonth'),
770
+ ('BYMONTHDAY', 'bymonthday'),
771
+ ('BYYEARDAY', 'byyearday'),
772
+ ('BYWEEKNO', 'byweekno'),
773
+ ('BYDAY', 'byweekday'),
774
+ ('BYHOUR', 'byhour'),
775
+ ('BYMINUTE', 'byminute'),
776
+ ('BYSECOND', 'bysecond'),
777
+ ('BYEASTER', 'byeaster')]:
778
+ value = original_rule.get(key)
779
+ if value:
780
+ parts.append(partfmt.format(name=name, vals=(','.join(str(v)
781
+ for v in value))))
782
+
783
+ output.append('RRULE:' + ';'.join(parts))
784
+ return '\n'.join(output)
785
+
786
+ def replace(self, **kwargs):
787
+ """Return new rrule with same attributes except for those attributes given new
788
+ values by whichever keyword arguments are specified."""
789
+ new_kwargs = {"interval": self._interval,
790
+ "count": self._count,
791
+ "dtstart": self._dtstart,
792
+ "freq": self._freq,
793
+ "until": self._until,
794
+ "wkst": self._wkst,
795
+ "cache": False if self._cache is None else True }
796
+ new_kwargs.update(self._original_rule)
797
+ new_kwargs.update(kwargs)
798
+ return rrule(**new_kwargs)
799
+
800
+ def _iter(self):
801
+ year, month, day, hour, minute, second, weekday, yearday, _ = \
802
+ self._dtstart.timetuple()
803
+
804
+ # Some local variables to speed things up a bit
805
+ freq = self._freq
806
+ interval = self._interval
807
+ wkst = self._wkst
808
+ until = self._until
809
+ bymonth = self._bymonth
810
+ byweekno = self._byweekno
811
+ byyearday = self._byyearday
812
+ byweekday = self._byweekday
813
+ byeaster = self._byeaster
814
+ bymonthday = self._bymonthday
815
+ bynmonthday = self._bynmonthday
816
+ bysetpos = self._bysetpos
817
+ byhour = self._byhour
818
+ byminute = self._byminute
819
+ bysecond = self._bysecond
820
+
821
+ ii = _iterinfo(self)
822
+ ii.rebuild(year, month)
823
+
824
+ getdayset = {YEARLY: ii.ydayset,
825
+ MONTHLY: ii.mdayset,
826
+ WEEKLY: ii.wdayset,
827
+ DAILY: ii.ddayset,
828
+ HOURLY: ii.ddayset,
829
+ MINUTELY: ii.ddayset,
830
+ SECONDLY: ii.ddayset}[freq]
831
+
832
+ if freq < HOURLY:
833
+ timeset = self._timeset
834
+ else:
835
+ gettimeset = {HOURLY: ii.htimeset,
836
+ MINUTELY: ii.mtimeset,
837
+ SECONDLY: ii.stimeset}[freq]
838
+ if ((freq >= HOURLY and
839
+ self._byhour and hour not in self._byhour) or
840
+ (freq >= MINUTELY and
841
+ self._byminute and minute not in self._byminute) or
842
+ (freq >= SECONDLY and
843
+ self._bysecond and second not in self._bysecond)):
844
+ timeset = ()
845
+ else:
846
+ timeset = gettimeset(hour, minute, second)
847
+
848
+ total = 0
849
+ count = self._count
850
+ while True:
851
+ # Get dayset with the right frequency
852
+ dayset, start, end = getdayset(year, month, day)
853
+
854
+ # Do the "hard" work ;-)
855
+ filtered = False
856
+ for i in dayset[start:end]:
857
+ if ((bymonth and ii.mmask[i] not in bymonth) or
858
+ (byweekno and not ii.wnomask[i]) or
859
+ (byweekday and ii.wdaymask[i] not in byweekday) or
860
+ (ii.nwdaymask and not ii.nwdaymask[i]) or
861
+ (byeaster and not ii.eastermask[i]) or
862
+ ((bymonthday or bynmonthday) and
863
+ ii.mdaymask[i] not in bymonthday and
864
+ ii.nmdaymask[i] not in bynmonthday) or
865
+ (byyearday and
866
+ ((i < ii.yearlen and i+1 not in byyearday and
867
+ -ii.yearlen+i not in byyearday) or
868
+ (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and
869
+ -ii.nextyearlen+i-ii.yearlen not in byyearday)))):
870
+ dayset[i] = None
871
+ filtered = True
872
+
873
+ # Output results
874
+ if bysetpos and timeset:
875
+ poslist = []
876
+ for pos in bysetpos:
877
+ if pos < 0:
878
+ daypos, timepos = divmod(pos, len(timeset))
879
+ else:
880
+ daypos, timepos = divmod(pos-1, len(timeset))
881
+ try:
882
+ i = [x for x in dayset[start:end]
883
+ if x is not None][daypos]
884
+ time = timeset[timepos]
885
+ except IndexError:
886
+ pass
887
+ else:
888
+ date = datetime.date.fromordinal(ii.yearordinal+i)
889
+ res = datetime.datetime.combine(date, time)
890
+ if res not in poslist:
891
+ poslist.append(res)
892
+ poslist.sort()
893
+ for res in poslist:
894
+ if until and res > until:
895
+ self._len = total
896
+ return
897
+ elif res >= self._dtstart:
898
+ if count is not None:
899
+ count -= 1
900
+ if count < 0:
901
+ self._len = total
902
+ return
903
+ total += 1
904
+ yield res
905
+ else:
906
+ for i in dayset[start:end]:
907
+ if i is not None:
908
+ date = datetime.date.fromordinal(ii.yearordinal + i)
909
+ for time in timeset:
910
+ res = datetime.datetime.combine(date, time)
911
+ if until and res > until:
912
+ self._len = total
913
+ return
914
+ elif res >= self._dtstart:
915
+ if count is not None:
916
+ count -= 1
917
+ if count < 0:
918
+ self._len = total
919
+ return
920
+
921
+ total += 1
922
+ yield res
923
+
924
+ # Handle frequency and interval
925
+ fixday = False
926
+ if freq == YEARLY:
927
+ year += interval
928
+ if year > datetime.MAXYEAR:
929
+ self._len = total
930
+ return
931
+ ii.rebuild(year, month)
932
+ elif freq == MONTHLY:
933
+ month += interval
934
+ if month > 12:
935
+ div, mod = divmod(month, 12)
936
+ month = mod
937
+ year += div
938
+ if month == 0:
939
+ month = 12
940
+ year -= 1
941
+ if year > datetime.MAXYEAR:
942
+ self._len = total
943
+ return
944
+ ii.rebuild(year, month)
945
+ elif freq == WEEKLY:
946
+ if wkst > weekday:
947
+ day += -(weekday+1+(6-wkst))+self._interval*7
948
+ else:
949
+ day += -(weekday-wkst)+self._interval*7
950
+ weekday = wkst
951
+ fixday = True
952
+ elif freq == DAILY:
953
+ day += interval
954
+ fixday = True
955
+ elif freq == HOURLY:
956
+ if filtered:
957
+ # Jump to one iteration before next day
958
+ hour += ((23-hour)//interval)*interval
959
+
960
+ if byhour:
961
+ ndays, hour = self.__mod_distance(value=hour,
962
+ byxxx=self._byhour,
963
+ base=24)
964
+ else:
965
+ ndays, hour = divmod(hour+interval, 24)
966
+
967
+ if ndays:
968
+ day += ndays
969
+ fixday = True
970
+
971
+ timeset = gettimeset(hour, minute, second)
972
+ elif freq == MINUTELY:
973
+ if filtered:
974
+ # Jump to one iteration before next day
975
+ minute += ((1439-(hour*60+minute))//interval)*interval
976
+
977
+ valid = False
978
+ rep_rate = (24*60)
979
+ for j in range(rep_rate // gcd(interval, rep_rate)):
980
+ if byminute:
981
+ nhours, minute = \
982
+ self.__mod_distance(value=minute,
983
+ byxxx=self._byminute,
984
+ base=60)
985
+ else:
986
+ nhours, minute = divmod(minute+interval, 60)
987
+
988
+ div, hour = divmod(hour+nhours, 24)
989
+ if div:
990
+ day += div
991
+ fixday = True
992
+ filtered = False
993
+
994
+ if not byhour or hour in byhour:
995
+ valid = True
996
+ break
997
+
998
+ if not valid:
999
+ raise ValueError('Invalid combination of interval and ' +
1000
+ 'byhour resulting in empty rule.')
1001
+
1002
+ timeset = gettimeset(hour, minute, second)
1003
+ elif freq == SECONDLY:
1004
+ if filtered:
1005
+ # Jump to one iteration before next day
1006
+ second += (((86399 - (hour * 3600 + minute * 60 + second))
1007
+ // interval) * interval)
1008
+
1009
+ rep_rate = (24 * 3600)
1010
+ valid = False
1011
+ for j in range(0, rep_rate // gcd(interval, rep_rate)):
1012
+ if bysecond:
1013
+ nminutes, second = \
1014
+ self.__mod_distance(value=second,
1015
+ byxxx=self._bysecond,
1016
+ base=60)
1017
+ else:
1018
+ nminutes, second = divmod(second+interval, 60)
1019
+
1020
+ div, minute = divmod(minute+nminutes, 60)
1021
+ if div:
1022
+ hour += div
1023
+ div, hour = divmod(hour, 24)
1024
+ if div:
1025
+ day += div
1026
+ fixday = True
1027
+
1028
+ if ((not byhour or hour in byhour) and
1029
+ (not byminute or minute in byminute) and
1030
+ (not bysecond or second in bysecond)):
1031
+ valid = True
1032
+ break
1033
+
1034
+ if not valid:
1035
+ raise ValueError('Invalid combination of interval, ' +
1036
+ 'byhour and byminute resulting in empty' +
1037
+ ' rule.')
1038
+
1039
+ timeset = gettimeset(hour, minute, second)
1040
+
1041
+ if fixday and day > 28:
1042
+ daysinmonth = calendar.monthrange(year, month)[1]
1043
+ if day > daysinmonth:
1044
+ while day > daysinmonth:
1045
+ day -= daysinmonth
1046
+ month += 1
1047
+ if month == 13:
1048
+ month = 1
1049
+ year += 1
1050
+ if year > datetime.MAXYEAR:
1051
+ self._len = total
1052
+ return
1053
+ daysinmonth = calendar.monthrange(year, month)[1]
1054
+ ii.rebuild(year, month)
1055
+
1056
+ def __construct_byset(self, start, byxxx, base):
1057
+ """
1058
+ If a `BYXXX` sequence is passed to the constructor at the same level as
1059
+ `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some
1060
+ specifications which cannot be reached given some starting conditions.
1061
+
1062
+ This occurs whenever the interval is not coprime with the base of a
1063
+ given unit and the difference between the starting position and the
1064
+ ending position is not coprime with the greatest common denominator
1065
+ between the interval and the base. For example, with a FREQ of hourly
1066
+ starting at 17:00 and an interval of 4, the only valid values for
1067
+ BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not
1068
+ coprime.
1069
+
1070
+ :param start:
1071
+ Specifies the starting position.
1072
+ :param byxxx:
1073
+ An iterable containing the list of allowed values.
1074
+ :param base:
1075
+ The largest allowable value for the specified frequency (e.g.
1076
+ 24 hours, 60 minutes).
1077
+
1078
+ This does not preserve the type of the iterable, returning a set, since
1079
+ the values should be unique and the order is irrelevant, this will
1080
+ speed up later lookups.
1081
+
1082
+ In the event of an empty set, raises a :exception:`ValueError`, as this
1083
+ results in an empty rrule.
1084
+ """
1085
+
1086
+ cset = set()
1087
+
1088
+ # Support a single byxxx value.
1089
+ if isinstance(byxxx, integer_types):
1090
+ byxxx = (byxxx, )
1091
+
1092
+ for num in byxxx:
1093
+ i_gcd = gcd(self._interval, base)
1094
+ # Use divmod rather than % because we need to wrap negative nums.
1095
+ if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0:
1096
+ cset.add(num)
1097
+
1098
+ if len(cset) == 0:
1099
+ raise ValueError("Invalid rrule byxxx generates an empty set.")
1100
+
1101
+ return cset
1102
+
1103
+ def __mod_distance(self, value, byxxx, base):
1104
+ """
1105
+ Calculates the next value in a sequence where the `FREQ` parameter is
1106
+ specified along with a `BYXXX` parameter at the same "level"
1107
+ (e.g. `HOURLY` specified with `BYHOUR`).
1108
+
1109
+ :param value:
1110
+ The old value of the component.
1111
+ :param byxxx:
1112
+ The `BYXXX` set, which should have been generated by
1113
+ `rrule._construct_byset`, or something else which checks that a
1114
+ valid rule is present.
1115
+ :param base:
1116
+ The largest allowable value for the specified frequency (e.g.
1117
+ 24 hours, 60 minutes).
1118
+
1119
+ If a valid value is not found after `base` iterations (the maximum
1120
+ number before the sequence would start to repeat), this raises a
1121
+ :exception:`ValueError`, as no valid values were found.
1122
+
1123
+ This returns a tuple of `divmod(n*interval, base)`, where `n` is the
1124
+ smallest number of `interval` repetitions until the next specified
1125
+ value in `byxxx` is found.
1126
+ """
1127
+ accumulator = 0
1128
+ for ii in range(1, base + 1):
1129
+ # Using divmod() over % to account for negative intervals
1130
+ div, value = divmod(value + self._interval, base)
1131
+ accumulator += div
1132
+ if value in byxxx:
1133
+ return (accumulator, value)
1134
+
1135
+
1136
+ class _iterinfo(object):
1137
+ __slots__ = ["rrule", "lastyear", "lastmonth",
1138
+ "yearlen", "nextyearlen", "yearordinal", "yearweekday",
1139
+ "mmask", "mrange", "mdaymask", "nmdaymask",
1140
+ "wdaymask", "wnomask", "nwdaymask", "eastermask"]
1141
+
1142
+ def __init__(self, rrule):
1143
+ for attr in self.__slots__:
1144
+ setattr(self, attr, None)
1145
+ self.rrule = rrule
1146
+
1147
+ def rebuild(self, year, month):
1148
+ # Every mask is 7 days longer to handle cross-year weekly periods.
1149
+ rr = self.rrule
1150
+ if year != self.lastyear:
1151
+ self.yearlen = 365 + calendar.isleap(year)
1152
+ self.nextyearlen = 365 + calendar.isleap(year + 1)
1153
+ firstyday = datetime.date(year, 1, 1)
1154
+ self.yearordinal = firstyday.toordinal()
1155
+ self.yearweekday = firstyday.weekday()
1156
+
1157
+ wday = datetime.date(year, 1, 1).weekday()
1158
+ if self.yearlen == 365:
1159
+ self.mmask = M365MASK
1160
+ self.mdaymask = MDAY365MASK
1161
+ self.nmdaymask = NMDAY365MASK
1162
+ self.wdaymask = WDAYMASK[wday:]
1163
+ self.mrange = M365RANGE
1164
+ else:
1165
+ self.mmask = M366MASK
1166
+ self.mdaymask = MDAY366MASK
1167
+ self.nmdaymask = NMDAY366MASK
1168
+ self.wdaymask = WDAYMASK[wday:]
1169
+ self.mrange = M366RANGE
1170
+
1171
+ if not rr._byweekno:
1172
+ self.wnomask = None
1173
+ else:
1174
+ self.wnomask = [0]*(self.yearlen+7)
1175
+ # no1wkst = firstwkst = self.wdaymask.index(rr._wkst)
1176
+ no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7
1177
+ if no1wkst >= 4:
1178
+ no1wkst = 0
1179
+ # Number of days in the year, plus the days we got
1180
+ # from last year.
1181
+ wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7
1182
+ else:
1183
+ # Number of days in the year, minus the days we
1184
+ # left in last year.
1185
+ wyearlen = self.yearlen-no1wkst
1186
+ div, mod = divmod(wyearlen, 7)
1187
+ numweeks = div+mod//4
1188
+ for n in rr._byweekno:
1189
+ if n < 0:
1190
+ n += numweeks+1
1191
+ if not (0 < n <= numweeks):
1192
+ continue
1193
+ if n > 1:
1194
+ i = no1wkst+(n-1)*7
1195
+ if no1wkst != firstwkst:
1196
+ i -= 7-firstwkst
1197
+ else:
1198
+ i = no1wkst
1199
+ for j in range(7):
1200
+ self.wnomask[i] = 1
1201
+ i += 1
1202
+ if self.wdaymask[i] == rr._wkst:
1203
+ break
1204
+ if 1 in rr._byweekno:
1205
+ # Check week number 1 of next year as well
1206
+ # TODO: Check -numweeks for next year.
1207
+ i = no1wkst+numweeks*7
1208
+ if no1wkst != firstwkst:
1209
+ i -= 7-firstwkst
1210
+ if i < self.yearlen:
1211
+ # If week starts in next year, we
1212
+ # don't care about it.
1213
+ for j in range(7):
1214
+ self.wnomask[i] = 1
1215
+ i += 1
1216
+ if self.wdaymask[i] == rr._wkst:
1217
+ break
1218
+ if no1wkst:
1219
+ # Check last week number of last year as
1220
+ # well. If no1wkst is 0, either the year
1221
+ # started on week start, or week number 1
1222
+ # got days from last year, so there are no
1223
+ # days from last year's last week number in
1224
+ # this year.
1225
+ if -1 not in rr._byweekno:
1226
+ lyearweekday = datetime.date(year-1, 1, 1).weekday()
1227
+ lno1wkst = (7-lyearweekday+rr._wkst) % 7
1228
+ lyearlen = 365+calendar.isleap(year-1)
1229
+ if lno1wkst >= 4:
1230
+ lno1wkst = 0
1231
+ lnumweeks = 52+(lyearlen +
1232
+ (lyearweekday-rr._wkst) % 7) % 7//4
1233
+ else:
1234
+ lnumweeks = 52+(self.yearlen-no1wkst) % 7//4
1235
+ else:
1236
+ lnumweeks = -1
1237
+ if lnumweeks in rr._byweekno:
1238
+ for i in range(no1wkst):
1239
+ self.wnomask[i] = 1
1240
+
1241
+ if (rr._bynweekday and (month != self.lastmonth or
1242
+ year != self.lastyear)):
1243
+ ranges = []
1244
+ if rr._freq == YEARLY:
1245
+ if rr._bymonth:
1246
+ for month in rr._bymonth:
1247
+ ranges.append(self.mrange[month-1:month+1])
1248
+ else:
1249
+ ranges = [(0, self.yearlen)]
1250
+ elif rr._freq == MONTHLY:
1251
+ ranges = [self.mrange[month-1:month+1]]
1252
+ if ranges:
1253
+ # Weekly frequency won't get here, so we may not
1254
+ # care about cross-year weekly periods.
1255
+ self.nwdaymask = [0]*self.yearlen
1256
+ for first, last in ranges:
1257
+ last -= 1
1258
+ for wday, n in rr._bynweekday:
1259
+ if n < 0:
1260
+ i = last+(n+1)*7
1261
+ i -= (self.wdaymask[i]-wday) % 7
1262
+ else:
1263
+ i = first+(n-1)*7
1264
+ i += (7-self.wdaymask[i]+wday) % 7
1265
+ if first <= i <= last:
1266
+ self.nwdaymask[i] = 1
1267
+
1268
+ if rr._byeaster:
1269
+ self.eastermask = [0]*(self.yearlen+7)
1270
+ eyday = easter.easter(year).toordinal()-self.yearordinal
1271
+ for offset in rr._byeaster:
1272
+ self.eastermask[eyday+offset] = 1
1273
+
1274
+ self.lastyear = year
1275
+ self.lastmonth = month
1276
+
1277
+ def ydayset(self, year, month, day):
1278
+ return list(range(self.yearlen)), 0, self.yearlen
1279
+
1280
+ def mdayset(self, year, month, day):
1281
+ dset = [None]*self.yearlen
1282
+ start, end = self.mrange[month-1:month+1]
1283
+ for i in range(start, end):
1284
+ dset[i] = i
1285
+ return dset, start, end
1286
+
1287
+ def wdayset(self, year, month, day):
1288
+ # We need to handle cross-year weeks here.
1289
+ dset = [None]*(self.yearlen+7)
1290
+ i = datetime.date(year, month, day).toordinal()-self.yearordinal
1291
+ start = i
1292
+ for j in range(7):
1293
+ dset[i] = i
1294
+ i += 1
1295
+ # if (not (0 <= i < self.yearlen) or
1296
+ # self.wdaymask[i] == self.rrule._wkst):
1297
+ # This will cross the year boundary, if necessary.
1298
+ if self.wdaymask[i] == self.rrule._wkst:
1299
+ break
1300
+ return dset, start, i
1301
+
1302
+ def ddayset(self, year, month, day):
1303
+ dset = [None] * self.yearlen
1304
+ i = datetime.date(year, month, day).toordinal() - self.yearordinal
1305
+ dset[i] = i
1306
+ return dset, i, i + 1
1307
+
1308
+ def htimeset(self, hour, minute, second):
1309
+ tset = []
1310
+ rr = self.rrule
1311
+ for minute in rr._byminute:
1312
+ for second in rr._bysecond:
1313
+ tset.append(datetime.time(hour, minute, second,
1314
+ tzinfo=rr._tzinfo))
1315
+ tset.sort()
1316
+ return tset
1317
+
1318
+ def mtimeset(self, hour, minute, second):
1319
+ tset = []
1320
+ rr = self.rrule
1321
+ for second in rr._bysecond:
1322
+ tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo))
1323
+ tset.sort()
1324
+ return tset
1325
+
1326
+ def stimeset(self, hour, minute, second):
1327
+ return (datetime.time(hour, minute, second,
1328
+ tzinfo=self.rrule._tzinfo),)
1329
+
1330
+
1331
+ class rruleset(rrulebase):
1332
+ """ The rruleset type allows more complex recurrence setups, mixing
1333
+ multiple rules, dates, exclusion rules, and exclusion dates. The type
1334
+ constructor takes the following keyword arguments:
1335
+
1336
+ :param cache: If True, caching of results will be enabled, improving
1337
+ performance of multiple queries considerably. """
1338
+
1339
+ class _genitem(object):
1340
+ def __init__(self, genlist, gen):
1341
+ try:
1342
+ self.dt = advance_iterator(gen)
1343
+ genlist.append(self)
1344
+ except StopIteration:
1345
+ pass
1346
+ self.genlist = genlist
1347
+ self.gen = gen
1348
+
1349
+ def __next__(self):
1350
+ try:
1351
+ self.dt = advance_iterator(self.gen)
1352
+ except StopIteration:
1353
+ if self.genlist[0] is self:
1354
+ heapq.heappop(self.genlist)
1355
+ else:
1356
+ self.genlist.remove(self)
1357
+ heapq.heapify(self.genlist)
1358
+
1359
+ next = __next__
1360
+
1361
+ def __lt__(self, other):
1362
+ return self.dt < other.dt
1363
+
1364
+ def __gt__(self, other):
1365
+ return self.dt > other.dt
1366
+
1367
+ def __eq__(self, other):
1368
+ return self.dt == other.dt
1369
+
1370
+ def __ne__(self, other):
1371
+ return self.dt != other.dt
1372
+
1373
+ def __init__(self, cache=False):
1374
+ super(rruleset, self).__init__(cache)
1375
+ self._rrule = []
1376
+ self._rdate = []
1377
+ self._exrule = []
1378
+ self._exdate = []
1379
+
1380
+ @_invalidates_cache
1381
+ def rrule(self, rrule):
1382
+ """ Include the given :py:class:`rrule` instance in the recurrence set
1383
+ generation. """
1384
+ self._rrule.append(rrule)
1385
+
1386
+ @_invalidates_cache
1387
+ def rdate(self, rdate):
1388
+ """ Include the given :py:class:`datetime` instance in the recurrence
1389
+ set generation. """
1390
+ self._rdate.append(rdate)
1391
+
1392
+ @_invalidates_cache
1393
+ def exrule(self, exrule):
1394
+ """ Include the given rrule instance in the recurrence set exclusion
1395
+ list. Dates which are part of the given recurrence rules will not
1396
+ be generated, even if some inclusive rrule or rdate matches them.
1397
+ """
1398
+ self._exrule.append(exrule)
1399
+
1400
+ @_invalidates_cache
1401
+ def exdate(self, exdate):
1402
+ """ Include the given datetime instance in the recurrence set
1403
+ exclusion list. Dates included that way will not be generated,
1404
+ even if some inclusive rrule or rdate matches them. """
1405
+ self._exdate.append(exdate)
1406
+
1407
+ def _iter(self):
1408
+ rlist = []
1409
+ self._rdate.sort()
1410
+ self._genitem(rlist, iter(self._rdate))
1411
+ for gen in [iter(x) for x in self._rrule]:
1412
+ self._genitem(rlist, gen)
1413
+ exlist = []
1414
+ self._exdate.sort()
1415
+ self._genitem(exlist, iter(self._exdate))
1416
+ for gen in [iter(x) for x in self._exrule]:
1417
+ self._genitem(exlist, gen)
1418
+ lastdt = None
1419
+ total = 0
1420
+ heapq.heapify(rlist)
1421
+ heapq.heapify(exlist)
1422
+ while rlist:
1423
+ ritem = rlist[0]
1424
+ if not lastdt or lastdt != ritem.dt:
1425
+ while exlist and exlist[0] < ritem:
1426
+ exitem = exlist[0]
1427
+ advance_iterator(exitem)
1428
+ if exlist and exlist[0] is exitem:
1429
+ heapq.heapreplace(exlist, exitem)
1430
+ if not exlist or ritem != exlist[0]:
1431
+ total += 1
1432
+ yield ritem.dt
1433
+ lastdt = ritem.dt
1434
+ advance_iterator(ritem)
1435
+ if rlist and rlist[0] is ritem:
1436
+ heapq.heapreplace(rlist, ritem)
1437
+ self._len = total
1438
+
1439
+
1440
+
1441
+
1442
+ class _rrulestr(object):
1443
+ """ Parses a string representation of a recurrence rule or set of
1444
+ recurrence rules.
1445
+
1446
+ :param s:
1447
+ Required, a string defining one or more recurrence rules.
1448
+
1449
+ :param dtstart:
1450
+ If given, used as the default recurrence start if not specified in the
1451
+ rule string.
1452
+
1453
+ :param cache:
1454
+ If set ``True`` caching of results will be enabled, improving
1455
+ performance of multiple queries considerably.
1456
+
1457
+ :param unfold:
1458
+ If set ``True`` indicates that a rule string is split over more
1459
+ than one line and should be joined before processing.
1460
+
1461
+ :param forceset:
1462
+ If set ``True`` forces a :class:`dateutil.rrule.rruleset` to
1463
+ be returned.
1464
+
1465
+ :param compatible:
1466
+ If set ``True`` forces ``unfold`` and ``forceset`` to be ``True``.
1467
+
1468
+ :param ignoretz:
1469
+ If set ``True``, time zones in parsed strings are ignored and a naive
1470
+ :class:`datetime.datetime` object is returned.
1471
+
1472
+ :param tzids:
1473
+ If given, a callable or mapping used to retrieve a
1474
+ :class:`datetime.tzinfo` from a string representation.
1475
+ Defaults to :func:`dateutil.tz.gettz`.
1476
+
1477
+ :param tzinfos:
1478
+ Additional time zone names / aliases which may be present in a string
1479
+ representation. See :func:`dateutil.parser.parse` for more
1480
+ information.
1481
+
1482
+ :return:
1483
+ Returns a :class:`dateutil.rrule.rruleset` or
1484
+ :class:`dateutil.rrule.rrule`
1485
+ """
1486
+
1487
+ _freq_map = {"YEARLY": YEARLY,
1488
+ "MONTHLY": MONTHLY,
1489
+ "WEEKLY": WEEKLY,
1490
+ "DAILY": DAILY,
1491
+ "HOURLY": HOURLY,
1492
+ "MINUTELY": MINUTELY,
1493
+ "SECONDLY": SECONDLY}
1494
+
1495
+ _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3,
1496
+ "FR": 4, "SA": 5, "SU": 6}
1497
+
1498
+ def _handle_int(self, rrkwargs, name, value, **kwargs):
1499
+ rrkwargs[name.lower()] = int(value)
1500
+
1501
+ def _handle_int_list(self, rrkwargs, name, value, **kwargs):
1502
+ rrkwargs[name.lower()] = [int(x) for x in value.split(',')]
1503
+
1504
+ _handle_INTERVAL = _handle_int
1505
+ _handle_COUNT = _handle_int
1506
+ _handle_BYSETPOS = _handle_int_list
1507
+ _handle_BYMONTH = _handle_int_list
1508
+ _handle_BYMONTHDAY = _handle_int_list
1509
+ _handle_BYYEARDAY = _handle_int_list
1510
+ _handle_BYEASTER = _handle_int_list
1511
+ _handle_BYWEEKNO = _handle_int_list
1512
+ _handle_BYHOUR = _handle_int_list
1513
+ _handle_BYMINUTE = _handle_int_list
1514
+ _handle_BYSECOND = _handle_int_list
1515
+
1516
+ def _handle_FREQ(self, rrkwargs, name, value, **kwargs):
1517
+ rrkwargs["freq"] = self._freq_map[value]
1518
+
1519
+ def _handle_UNTIL(self, rrkwargs, name, value, **kwargs):
1520
+ global parser
1521
+ if not parser:
1522
+ from dateutil import parser
1523
+ try:
1524
+ rrkwargs["until"] = parser.parse(value,
1525
+ ignoretz=kwargs.get("ignoretz"),
1526
+ tzinfos=kwargs.get("tzinfos"))
1527
+ except ValueError:
1528
+ raise ValueError("invalid until date")
1529
+
1530
+ def _handle_WKST(self, rrkwargs, name, value, **kwargs):
1531
+ rrkwargs["wkst"] = self._weekday_map[value]
1532
+
1533
+ def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs):
1534
+ """
1535
+ Two ways to specify this: +1MO or MO(+1)
1536
+ """
1537
+ l = []
1538
+ for wday in value.split(','):
1539
+ if '(' in wday:
1540
+ # If it's of the form TH(+1), etc.
1541
+ splt = wday.split('(')
1542
+ w = splt[0]
1543
+ n = int(splt[1][:-1])
1544
+ elif len(wday):
1545
+ # If it's of the form +1MO
1546
+ for i in range(len(wday)):
1547
+ if wday[i] not in '+-0123456789':
1548
+ break
1549
+ n = wday[:i] or None
1550
+ w = wday[i:]
1551
+ if n:
1552
+ n = int(n)
1553
+ else:
1554
+ raise ValueError("Invalid (empty) BYDAY specification.")
1555
+
1556
+ l.append(weekdays[self._weekday_map[w]](n))
1557
+ rrkwargs["byweekday"] = l
1558
+
1559
+ _handle_BYDAY = _handle_BYWEEKDAY
1560
+
1561
+ def _parse_rfc_rrule(self, line,
1562
+ dtstart=None,
1563
+ cache=False,
1564
+ ignoretz=False,
1565
+ tzinfos=None):
1566
+ if line.find(':') != -1:
1567
+ name, value = line.split(':')
1568
+ if name != "RRULE":
1569
+ raise ValueError("unknown parameter name")
1570
+ else:
1571
+ value = line
1572
+ rrkwargs = {}
1573
+ for pair in value.split(';'):
1574
+ name, value = pair.split('=')
1575
+ name = name.upper()
1576
+ value = value.upper()
1577
+ try:
1578
+ getattr(self, "_handle_"+name)(rrkwargs, name, value,
1579
+ ignoretz=ignoretz,
1580
+ tzinfos=tzinfos)
1581
+ except AttributeError:
1582
+ raise ValueError("unknown parameter '%s'" % name)
1583
+ except (KeyError, ValueError):
1584
+ raise ValueError("invalid '%s': %s" % (name, value))
1585
+ return rrule(dtstart=dtstart, cache=cache, **rrkwargs)
1586
+
1587
+ def _parse_date_value(self, date_value, parms, rule_tzids,
1588
+ ignoretz, tzids, tzinfos):
1589
+ global parser
1590
+ if not parser:
1591
+ from dateutil import parser
1592
+
1593
+ datevals = []
1594
+ value_found = False
1595
+ TZID = None
1596
+
1597
+ for parm in parms:
1598
+ if parm.startswith("TZID="):
1599
+ try:
1600
+ tzkey = rule_tzids[parm.split('TZID=')[-1]]
1601
+ except KeyError:
1602
+ continue
1603
+ if tzids is None:
1604
+ from . import tz
1605
+ tzlookup = tz.gettz
1606
+ elif callable(tzids):
1607
+ tzlookup = tzids
1608
+ else:
1609
+ tzlookup = getattr(tzids, 'get', None)
1610
+ if tzlookup is None:
1611
+ msg = ('tzids must be a callable, mapping, or None, '
1612
+ 'not %s' % tzids)
1613
+ raise ValueError(msg)
1614
+
1615
+ TZID = tzlookup(tzkey)
1616
+ continue
1617
+
1618
+ # RFC 5445 3.8.2.4: The VALUE parameter is optional, but may be found
1619
+ # only once.
1620
+ if parm not in {"VALUE=DATE-TIME", "VALUE=DATE"}:
1621
+ raise ValueError("unsupported parm: " + parm)
1622
+ else:
1623
+ if value_found:
1624
+ msg = ("Duplicate value parameter found in: " + parm)
1625
+ raise ValueError(msg)
1626
+ value_found = True
1627
+
1628
+ for datestr in date_value.split(','):
1629
+ date = parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos)
1630
+ if TZID is not None:
1631
+ if date.tzinfo is None:
1632
+ date = date.replace(tzinfo=TZID)
1633
+ else:
1634
+ raise ValueError('DTSTART/EXDATE specifies multiple timezone')
1635
+ datevals.append(date)
1636
+
1637
+ return datevals
1638
+
1639
+ def _parse_rfc(self, s,
1640
+ dtstart=None,
1641
+ cache=False,
1642
+ unfold=False,
1643
+ forceset=False,
1644
+ compatible=False,
1645
+ ignoretz=False,
1646
+ tzids=None,
1647
+ tzinfos=None):
1648
+ global parser
1649
+ if compatible:
1650
+ forceset = True
1651
+ unfold = True
1652
+
1653
+ TZID_NAMES = dict(map(
1654
+ lambda x: (x.upper(), x),
1655
+ re.findall('TZID=(?P<name>[^:]+):', s)
1656
+ ))
1657
+ s = s.upper()
1658
+ if not s.strip():
1659
+ raise ValueError("empty string")
1660
+ if unfold:
1661
+ lines = s.splitlines()
1662
+ i = 0
1663
+ while i < len(lines):
1664
+ line = lines[i].rstrip()
1665
+ if not line:
1666
+ del lines[i]
1667
+ elif i > 0 and line[0] == " ":
1668
+ lines[i-1] += line[1:]
1669
+ del lines[i]
1670
+ else:
1671
+ i += 1
1672
+ else:
1673
+ lines = s.split()
1674
+ if (not forceset and len(lines) == 1 and (s.find(':') == -1 or
1675
+ s.startswith('RRULE:'))):
1676
+ return self._parse_rfc_rrule(lines[0], cache=cache,
1677
+ dtstart=dtstart, ignoretz=ignoretz,
1678
+ tzinfos=tzinfos)
1679
+ else:
1680
+ rrulevals = []
1681
+ rdatevals = []
1682
+ exrulevals = []
1683
+ exdatevals = []
1684
+ for line in lines:
1685
+ if not line:
1686
+ continue
1687
+ if line.find(':') == -1:
1688
+ name = "RRULE"
1689
+ value = line
1690
+ else:
1691
+ name, value = line.split(':', 1)
1692
+ parms = name.split(';')
1693
+ if not parms:
1694
+ raise ValueError("empty property name")
1695
+ name = parms[0]
1696
+ parms = parms[1:]
1697
+ if name == "RRULE":
1698
+ for parm in parms:
1699
+ if parm.startswith("X-"):
1700
+ # Experimental random vendor features; safe to ignore
1701
+ continue
1702
+ raise ValueError("unsupported RRULE parm: " + parm)
1703
+ rrulevals.append(value)
1704
+ elif name == "RDATE":
1705
+ for parm in parms:
1706
+ if parm != "VALUE=DATE-TIME":
1707
+ raise ValueError("unsupported RDATE parm: "+parm)
1708
+ rdatevals.append(value)
1709
+ elif name == "EXRULE":
1710
+ for parm in parms:
1711
+ raise ValueError("unsupported EXRULE parm: "+parm)
1712
+ exrulevals.append(value)
1713
+ elif name == "EXDATE":
1714
+ exdatevals.extend(
1715
+ self._parse_date_value(value, parms,
1716
+ TZID_NAMES, ignoretz,
1717
+ tzids, tzinfos)
1718
+ )
1719
+ elif name == "DTSTART":
1720
+ dtvals = self._parse_date_value(value, parms, TZID_NAMES,
1721
+ ignoretz, tzids, tzinfos)
1722
+ if len(dtvals) != 1:
1723
+ raise ValueError("Multiple DTSTART values specified:" +
1724
+ value)
1725
+ dtstart = dtvals[0]
1726
+ else:
1727
+ raise ValueError("unsupported property: "+name)
1728
+ if (forceset or len(rrulevals) > 1 or rdatevals
1729
+ or exrulevals or exdatevals):
1730
+ if not parser and (rdatevals or exdatevals):
1731
+ from dateutil import parser
1732
+ rset = rruleset(cache=cache)
1733
+ for value in rrulevals:
1734
+ rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart,
1735
+ ignoretz=ignoretz,
1736
+ tzinfos=tzinfos))
1737
+ for value in rdatevals:
1738
+ for datestr in value.split(','):
1739
+ rset.rdate(parser.parse(datestr,
1740
+ ignoretz=ignoretz,
1741
+ tzinfos=tzinfos))
1742
+ for value in exrulevals:
1743
+ rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart,
1744
+ ignoretz=ignoretz,
1745
+ tzinfos=tzinfos))
1746
+ for value in exdatevals:
1747
+ rset.exdate(value)
1748
+ if compatible and dtstart:
1749
+ rset.rdate(dtstart)
1750
+ return rset
1751
+ else:
1752
+ return self._parse_rfc_rrule(rrulevals[0],
1753
+ dtstart=dtstart,
1754
+ cache=cache,
1755
+ ignoretz=ignoretz,
1756
+ tzinfos=tzinfos)
1757
+
1758
+ def __call__(self, s, **kwargs):
1759
+ return self._parse_rfc(s, **kwargs)
1760
+
1761
+
1762
+ rrulestr = _rrulestr()
1763
+
1764
+ # vim:ts=4:sw=4:et
hug/lib/python3.12/site-packages/dateutil/tz/__init__.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import sys
3
+
4
+ from . import _tzpath
5
+ from ._tzpath import reset_tzpath
6
+ from .tz import *
7
+ from .tz import __doc__
8
+
9
+ __all__ = [
10
+ "tzutc",
11
+ "tzoffset",
12
+ "tzlocal",
13
+ "tzfile",
14
+ "tzrange",
15
+ "tzstr",
16
+ "tzical",
17
+ "tzwin",
18
+ "tzwinlocal",
19
+ "gettz",
20
+ "enfold",
21
+ "datetime_ambiguous",
22
+ "datetime_exists",
23
+ "resolve_imaginary",
24
+ "available_iana_timezones",
25
+ "UTC",
26
+ "TZPATH",
27
+ "reset_tzpath",
28
+ "DeprecatedTzFormatWarning",
29
+ "DeprecatedTzKeyWarning",
30
+ ]
31
+
32
+ if sys.version_info < (3, 7):
33
+ # Module-level __getattr__ was added in Python 3.7, so instead of lazily
34
+ # populating TZPATH on every access, we will register a callback with
35
+ # reset_tzpath to update the top-level tuple.
36
+ TZPATH = _tzpath.TZPATH
37
+
38
+ def _tzpath_callback(new_tzpath):
39
+ global TZPATH
40
+ TZPATH = new_tzpath
41
+
42
+ _tzpath.TZPATH_CALLBACKS.append(_tzpath_callback)
43
+
44
+ else:
45
+
46
+ def __getattr__(name):
47
+ if name == "TZPATH":
48
+ return _tzpath.TZPATH
49
+ else:
50
+ raise AttributeError(
51
+ "Module %s has no attribute %s" % (repr(__name__), repr(name))
52
+ )
53
+
54
+
55
+ def __dir__():
56
+ return sorted(list(globals()) + ["TZPATH"])
57
+
58
+
59
+ class DeprecatedTzFormatWarning(Warning):
60
+ """Warning raised when time zones are parsed from deprecated formats."""
hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (1.75 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/_common.cpython-312.pyc ADDED
Binary file (14.2 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/_factories.cpython-312.pyc ADDED
Binary file (4.59 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/_tzfile.cpython-312.pyc ADDED
Binary file (47.6 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/_tzpath.cpython-312.pyc ADDED
Binary file (3.97 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/tz.cpython-312.pyc ADDED
Binary file (54.3 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/tz/__pycache__/win.cpython-312.pyc ADDED
Binary file (17.3 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/tz/_common.py ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from six import PY2
2
+
3
+ from functools import wraps
4
+
5
+ from datetime import datetime, timedelta, tzinfo
6
+
7
+
8
+ ZERO = timedelta(0)
9
+
10
+ __all__ = ['tzname_in_python2', 'enfold']
11
+
12
+
13
+ def tzname_in_python2(namefunc):
14
+ """Change unicode output into bytestrings in Python 2
15
+
16
+ tzname() API changed in Python 3. It used to return bytes, but was changed
17
+ to unicode strings
18
+ """
19
+ if PY2:
20
+ @wraps(namefunc)
21
+ def adjust_encoding(*args, **kwargs):
22
+ name = namefunc(*args, **kwargs)
23
+ if name is not None:
24
+ name = name.encode()
25
+
26
+ return name
27
+
28
+ return adjust_encoding
29
+ else:
30
+ return namefunc
31
+
32
+
33
+ # The following is adapted from Alexander Belopolsky's tz library
34
+ # https://github.com/abalkin/tz
35
+ if hasattr(datetime, 'fold'):
36
+ # This is the pre-python 3.6 fold situation
37
+ def enfold(dt, fold=1):
38
+ """
39
+ Provides a unified interface for assigning the ``fold`` attribute to
40
+ datetimes both before and after the implementation of PEP-495.
41
+
42
+ :param fold:
43
+ The value for the ``fold`` attribute in the returned datetime. This
44
+ should be either 0 or 1.
45
+
46
+ :return:
47
+ Returns an object for which ``getattr(dt, 'fold', 0)`` returns
48
+ ``fold`` for all versions of Python. In versions prior to
49
+ Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
50
+ subclass of :py:class:`datetime.datetime` with the ``fold``
51
+ attribute added, if ``fold`` is 1.
52
+
53
+ .. versionadded:: 2.6.0
54
+ """
55
+ return dt.replace(fold=fold)
56
+
57
+ else:
58
+ class _DatetimeWithFold(datetime):
59
+ """
60
+ This is a class designed to provide a PEP 495-compliant interface for
61
+ Python versions before 3.6. It is used only for dates in a fold, so
62
+ the ``fold`` attribute is fixed at ``1``.
63
+
64
+ .. versionadded:: 2.6.0
65
+ """
66
+ __slots__ = ()
67
+
68
+ def replace(self, *args, **kwargs):
69
+ """
70
+ Return a datetime with the same attributes, except for those
71
+ attributes given new values by whichever keyword arguments are
72
+ specified. Note that tzinfo=None can be specified to create a naive
73
+ datetime from an aware datetime with no conversion of date and time
74
+ data.
75
+
76
+ This is reimplemented in ``_DatetimeWithFold`` because pypy3 will
77
+ return a ``datetime.datetime`` even if ``fold`` is unchanged.
78
+ """
79
+ argnames = (
80
+ 'year', 'month', 'day', 'hour', 'minute', 'second',
81
+ 'microsecond', 'tzinfo'
82
+ )
83
+
84
+ for arg, argname in zip(args, argnames):
85
+ if argname in kwargs:
86
+ raise TypeError('Duplicate argument: {}'.format(argname))
87
+
88
+ kwargs[argname] = arg
89
+
90
+ for argname in argnames:
91
+ if argname not in kwargs:
92
+ kwargs[argname] = getattr(self, argname)
93
+
94
+ dt_class = self.__class__ if kwargs.get('fold', 1) else datetime
95
+
96
+ return dt_class(**kwargs)
97
+
98
+ @property
99
+ def fold(self):
100
+ return 1
101
+
102
+ def enfold(dt, fold=1):
103
+ """
104
+ Provides a unified interface for assigning the ``fold`` attribute to
105
+ datetimes both before and after the implementation of PEP-495.
106
+
107
+ :param fold:
108
+ The value for the ``fold`` attribute in the returned datetime. This
109
+ should be either 0 or 1.
110
+
111
+ :return:
112
+ Returns an object for which ``getattr(dt, 'fold', 0)`` returns
113
+ ``fold`` for all versions of Python. In versions prior to
114
+ Python 3.6, this is a ``_DatetimeWithFold`` object, which is a
115
+ subclass of :py:class:`datetime.datetime` with the ``fold``
116
+ attribute added, if ``fold`` is 1.
117
+
118
+ .. versionadded:: 2.6.0
119
+ """
120
+ if getattr(dt, 'fold', 0) == fold:
121
+ return dt
122
+
123
+ args = dt.timetuple()[:6]
124
+ args += (dt.microsecond, dt.tzinfo)
125
+
126
+ if fold:
127
+ return _DatetimeWithFold(*args)
128
+ else:
129
+ return datetime(*args)
130
+
131
+
132
+ def _validate_fromutc_inputs(f):
133
+ """
134
+ The CPython version of ``fromutc`` checks that the input is a ``datetime``
135
+ object and that ``self`` is attached as its ``tzinfo``.
136
+ """
137
+ @wraps(f)
138
+ def fromutc(self, dt):
139
+ if not isinstance(dt, datetime):
140
+ raise TypeError("fromutc() requires a datetime argument")
141
+ if dt.tzinfo is not self:
142
+ raise ValueError("dt.tzinfo is not self")
143
+
144
+ return f(self, dt)
145
+
146
+ return fromutc
147
+
148
+
149
+ class _tzinfo(tzinfo):
150
+ """
151
+ Base class for all ``dateutil`` ``tzinfo`` objects.
152
+ """
153
+
154
+ def is_ambiguous(self, dt):
155
+ """
156
+ Whether or not the "wall time" of a given datetime is ambiguous in this
157
+ zone.
158
+
159
+ :param dt:
160
+ A :py:class:`datetime.datetime`, naive or time zone aware.
161
+
162
+
163
+ :return:
164
+ Returns ``True`` if ambiguous, ``False`` otherwise.
165
+
166
+ .. versionadded:: 2.6.0
167
+ """
168
+
169
+ dt = dt.replace(tzinfo=self)
170
+
171
+ wall_0 = enfold(dt, fold=0)
172
+ wall_1 = enfold(dt, fold=1)
173
+
174
+ same_offset = wall_0.utcoffset() == wall_1.utcoffset()
175
+ same_dt = wall_0.replace(tzinfo=None) == wall_1.replace(tzinfo=None)
176
+
177
+ return same_dt and not same_offset
178
+
179
+ def _fold_status(self, dt_utc, dt_wall):
180
+ """
181
+ Determine the fold status of a "wall" datetime, given a representation
182
+ of the same datetime as a (naive) UTC datetime. This is calculated based
183
+ on the assumption that ``dt.utcoffset() - dt.dst()`` is constant for all
184
+ datetimes, and that this offset is the actual number of hours separating
185
+ ``dt_utc`` and ``dt_wall``.
186
+
187
+ :param dt_utc:
188
+ Representation of the datetime as UTC
189
+
190
+ :param dt_wall:
191
+ Representation of the datetime as "wall time". This parameter must
192
+ either have a `fold` attribute or have a fold-naive
193
+ :class:`datetime.tzinfo` attached, otherwise the calculation may
194
+ fail.
195
+ """
196
+ if self.is_ambiguous(dt_wall):
197
+ delta_wall = dt_wall - dt_utc
198
+ _fold = int(delta_wall == (dt_utc.utcoffset() - dt_utc.dst()))
199
+ else:
200
+ _fold = 0
201
+
202
+ return _fold
203
+
204
+ def _fold(self, dt):
205
+ return getattr(dt, 'fold', 0)
206
+
207
+ def _fromutc(self, dt):
208
+ """
209
+ Given a timezone-aware datetime in a given timezone, calculates a
210
+ timezone-aware datetime in a new timezone.
211
+
212
+ Since this is the one time that we *know* we have an unambiguous
213
+ datetime object, we take this opportunity to determine whether the
214
+ datetime is ambiguous and in a "fold" state (e.g. if it's the first
215
+ occurrence, chronologically, of the ambiguous datetime).
216
+
217
+ :param dt:
218
+ A timezone-aware :class:`datetime.datetime` object.
219
+ """
220
+
221
+ # Re-implement the algorithm from Python's datetime.py
222
+ dtoff = dt.utcoffset()
223
+ if dtoff is None:
224
+ raise ValueError("fromutc() requires a non-None utcoffset() "
225
+ "result")
226
+
227
+ # The original datetime.py code assumes that `dst()` defaults to
228
+ # zero during ambiguous times. PEP 495 inverts this presumption, so
229
+ # for pre-PEP 495 versions of python, we need to tweak the algorithm.
230
+ dtdst = dt.dst()
231
+ if dtdst is None:
232
+ raise ValueError("fromutc() requires a non-None dst() result")
233
+ delta = dtoff - dtdst
234
+
235
+ dt += delta
236
+ # Set fold=1 so we can default to being in the fold for
237
+ # ambiguous dates.
238
+ dtdst = enfold(dt, fold=1).dst()
239
+ if dtdst is None:
240
+ raise ValueError("fromutc(): dt.dst gave inconsistent "
241
+ "results; cannot convert")
242
+ return dt + dtdst
243
+
244
+ @_validate_fromutc_inputs
245
+ def fromutc(self, dt):
246
+ """
247
+ Given a timezone-aware datetime in a given timezone, calculates a
248
+ timezone-aware datetime in a new timezone.
249
+
250
+ Since this is the one time that we *know* we have an unambiguous
251
+ datetime object, we take this opportunity to determine whether the
252
+ datetime is ambiguous and in a "fold" state (e.g. if it's the first
253
+ occurrence, chronologically, of the ambiguous datetime).
254
+
255
+ :param dt:
256
+ A timezone-aware :class:`datetime.datetime` object.
257
+ """
258
+ dt_wall = self._fromutc(dt)
259
+
260
+ # Calculate the fold status given the two datetimes.
261
+ _fold = self._fold_status(dt, dt_wall)
262
+
263
+ # Set the default fold value for ambiguous dates
264
+ return enfold(dt_wall, fold=_fold)
265
+
266
+
267
+ class tzrangebase(_tzinfo):
268
+ """
269
+ This is an abstract base class for time zones represented by an annual
270
+ transition into and out of DST. Child classes should implement the following
271
+ methods:
272
+
273
+ * ``__init__(self, *args, **kwargs)``
274
+ * ``transitions(self, year)`` - this is expected to return a tuple of
275
+ datetimes representing the DST on and off transitions in standard
276
+ time.
277
+
278
+ A fully initialized ``tzrangebase`` subclass should also provide the
279
+ following attributes:
280
+ * ``hasdst``: Boolean whether or not the zone uses DST.
281
+ * ``_dst_offset`` / ``_std_offset``: :class:`datetime.timedelta` objects
282
+ representing the respective UTC offsets.
283
+ * ``_dst_abbr`` / ``_std_abbr``: Strings representing the timezone short
284
+ abbreviations in DST and STD, respectively.
285
+ * ``_hasdst``: Whether or not the zone has DST.
286
+
287
+ .. versionadded:: 2.6.0
288
+ """
289
+ def __init__(self):
290
+ raise NotImplementedError('tzrangebase is an abstract base class')
291
+
292
+ def utcoffset(self, dt):
293
+ isdst = self._isdst(dt)
294
+
295
+ if isdst is None:
296
+ return None
297
+ elif isdst:
298
+ return self._dst_offset
299
+ else:
300
+ return self._std_offset
301
+
302
+ def dst(self, dt):
303
+ isdst = self._isdst(dt)
304
+
305
+ if isdst is None:
306
+ return None
307
+ elif isdst:
308
+ return self._dst_base_offset
309
+ else:
310
+ return ZERO
311
+
312
+ @tzname_in_python2
313
+ def tzname(self, dt):
314
+ if self._isdst(dt):
315
+ return self._dst_abbr
316
+ else:
317
+ return self._std_abbr
318
+
319
+ def fromutc(self, dt):
320
+ """ Given a datetime in UTC, return local time """
321
+ if not isinstance(dt, datetime):
322
+ raise TypeError("fromutc() requires a datetime argument")
323
+
324
+ if dt.tzinfo is not self:
325
+ raise ValueError("dt.tzinfo is not self")
326
+
327
+ # Get transitions - if there are none, fixed offset
328
+ transitions = self.transitions(dt.year)
329
+ if transitions is None:
330
+ return dt + self.utcoffset(dt)
331
+
332
+ # Get the transition times in UTC
333
+ dston, dstoff = transitions
334
+
335
+ dston -= self._std_offset
336
+ dstoff -= self._std_offset
337
+
338
+ utc_transitions = (dston, dstoff)
339
+ dt_utc = dt.replace(tzinfo=None)
340
+
341
+ isdst = self._naive_isdst(dt_utc, utc_transitions)
342
+
343
+ if isdst:
344
+ dt_wall = dt + self._dst_offset
345
+ else:
346
+ dt_wall = dt + self._std_offset
347
+
348
+ _fold = int(not isdst and self.is_ambiguous(dt_wall))
349
+
350
+ return enfold(dt_wall, fold=_fold)
351
+
352
+ def is_ambiguous(self, dt):
353
+ """
354
+ Whether or not the "wall time" of a given datetime is ambiguous in this
355
+ zone.
356
+
357
+ :param dt:
358
+ A :py:class:`datetime.datetime`, naive or time zone aware.
359
+
360
+
361
+ :return:
362
+ Returns ``True`` if ambiguous, ``False`` otherwise.
363
+
364
+ .. versionadded:: 2.6.0
365
+ """
366
+ if not self.hasdst:
367
+ return False
368
+
369
+ start, end = self.transitions(dt.year)
370
+
371
+ dt = dt.replace(tzinfo=None)
372
+ return (end <= dt < end + self._dst_base_offset)
373
+
374
+ def _isdst(self, dt):
375
+ if not self.hasdst:
376
+ return False
377
+ elif dt is None:
378
+ return None
379
+
380
+ transitions = self.transitions(dt.year)
381
+
382
+ if transitions is None:
383
+ return False
384
+
385
+ dt = dt.replace(tzinfo=None)
386
+
387
+ isdst = self._naive_isdst(dt, transitions)
388
+
389
+ # Handle ambiguous dates
390
+ if not isdst and self.is_ambiguous(dt):
391
+ return not self._fold(dt)
392
+ else:
393
+ return isdst
394
+
395
+ def _naive_isdst(self, dt, transitions):
396
+ dston, dstoff = transitions
397
+
398
+ dt = dt.replace(tzinfo=None)
399
+
400
+ if dston < dstoff:
401
+ isdst = dston <= dt < dstoff
402
+ else:
403
+ isdst = not dstoff <= dt < dston
404
+
405
+ return isdst
406
+
407
+ @property
408
+ def _dst_base_offset(self):
409
+ return self._dst_offset - self._std_offset
410
+
411
+ __hash__ = None
412
+
413
+ def __ne__(self, other):
414
+ return not (self == other)
415
+
416
+ def __repr__(self):
417
+ return "%s(...)" % self.__class__.__name__
418
+
419
+ __reduce__ = object.__reduce__
hug/lib/python3.12/site-packages/dateutil/tz/_factories.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import timedelta
2
+ import weakref
3
+ from collections import OrderedDict
4
+
5
+ from six.moves import _thread
6
+
7
+
8
+ class _TzSingleton(type):
9
+ def __init__(cls, *args, **kwargs):
10
+ cls.__instance = None
11
+ super(_TzSingleton, cls).__init__(*args, **kwargs)
12
+
13
+ def __call__(cls):
14
+ if cls.__instance is None:
15
+ cls.__instance = super(_TzSingleton, cls).__call__()
16
+ return cls.__instance
17
+
18
+
19
+ class _TzFactory(type):
20
+ def instance(cls, *args, **kwargs):
21
+ """Alternate constructor that returns a fresh instance"""
22
+ return type.__call__(cls, *args, **kwargs)
23
+
24
+
25
+ class _TzOffsetFactory(_TzFactory):
26
+ def __init__(cls, *args, **kwargs):
27
+ cls.__instances = weakref.WeakValueDictionary()
28
+ cls.__strong_cache = OrderedDict()
29
+ cls.__strong_cache_size = 8
30
+
31
+ cls._cache_lock = _thread.allocate_lock()
32
+
33
+ def __call__(cls, name, offset):
34
+ if isinstance(offset, timedelta):
35
+ key = (name, offset.total_seconds())
36
+ else:
37
+ key = (name, offset)
38
+
39
+ instance = cls.__instances.get(key, None)
40
+ if instance is None:
41
+ instance = cls.__instances.setdefault(key,
42
+ cls.instance(name, offset))
43
+
44
+ # This lock may not be necessary in Python 3. See GH issue #901
45
+ with cls._cache_lock:
46
+ cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
47
+
48
+ # Remove an item if the strong cache is overpopulated
49
+ if len(cls.__strong_cache) > cls.__strong_cache_size:
50
+ cls.__strong_cache.popitem(last=False)
51
+
52
+ return instance
53
+
54
+
55
+ class _TzStrFactory(_TzFactory):
56
+ def __init__(cls, *args, **kwargs):
57
+ cls.__instances = weakref.WeakValueDictionary()
58
+ cls.__strong_cache = OrderedDict()
59
+ cls.__strong_cache_size = 8
60
+
61
+ cls.__cache_lock = _thread.allocate_lock()
62
+
63
+ def __call__(cls, s, posix_offset=False):
64
+ key = (s, posix_offset)
65
+ instance = cls.__instances.get(key, None)
66
+
67
+ if instance is None:
68
+ instance = cls.__instances.setdefault(key,
69
+ cls.instance(s, posix_offset))
70
+
71
+ # This lock may not be necessary in Python 3. See GH issue #901
72
+ with cls.__cache_lock:
73
+ cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance)
74
+
75
+ # Remove an item if the strong cache is overpopulated
76
+ if len(cls.__strong_cache) > cls.__strong_cache_size:
77
+ cls.__strong_cache.popitem(last=False)
78
+
79
+ return instance
80
+
hug/lib/python3.12/site-packages/dateutil/tz/_tzfile.py ADDED
@@ -0,0 +1,1428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ import bisect
3
+ import calendar
4
+ import functools
5
+ import io
6
+ import os
7
+ import re
8
+ import stat
9
+ import struct
10
+ import sys
11
+ from datetime import MAXYEAR, MINYEAR, datetime, timedelta
12
+
13
+ import six
14
+
15
+ from ._common import _tzinfo, enfold, tzname_in_python2
16
+
17
+ EPOCH = datetime(1970, 1, 1)
18
+ EPOCHORDINAL = EPOCH.toordinal()
19
+
20
+ # TODO: Move this to _common.py
21
+ try:
22
+ # Python 3.7 feature
23
+ from contextlib import nullcontext as _nullcontext
24
+ except ImportError:
25
+ import contextlib
26
+
27
+ @contextlib.contextmanager
28
+ def _nullcontext(context):
29
+ yield context
30
+
31
+
32
+ # It is relatively expensive to construct new timedelta objects, and in most
33
+ # cases we're looking at the same deltas, like integer numbers of hours, etc.
34
+ # To improve speed and memory use, we'll keep a dictionary with references
35
+ # to the ones we've already used so far.
36
+ #
37
+ # Loading every time zone in the 2020a version of the time zone database
38
+ # requires 447 timedeltas, which requires approximately the amount of space
39
+ # that ZoneInfo("America/New_York") with 236 transitions takes up, so we will
40
+ # set the cache size to 512 so that in the common case we always get cache
41
+ # hits, but specifically crafted ZoneInfo objects don't leak arbitrary amounts
42
+ # of memory.
43
+ _TIMEDELTA_CACHE_SIZE = 512
44
+ if six.PY2:
45
+
46
+ def _cache_load_timedelta(f):
47
+ from collections import OrderedDict
48
+
49
+ from six.moves import _thread
50
+
51
+ CACHE_LOCK = _thread.allocate_lock()
52
+ TIMEDELTA_CACHE = OrderedDict()
53
+
54
+ @functools.wraps(f)
55
+ def cached_load_timedelta(seconds):
56
+ # OrderedDict is implemented in Python on Python 2, so every
57
+ # access has to hold the lock to keep its linked list intact.
58
+ with CACHE_LOCK:
59
+ rv = TIMEDELTA_CACHE.pop(seconds, None)
60
+ if rv is None:
61
+ rv = f(seconds)
62
+
63
+ TIMEDELTA_CACHE[seconds] = rv
64
+
65
+ if len(TIMEDELTA_CACHE) > _TIMEDELTA_CACHE_SIZE:
66
+ TIMEDELTA_CACHE.popitem(last=False)
67
+
68
+ return rv
69
+
70
+ return cached_load_timedelta
71
+
72
+ else:
73
+ _cache_load_timedelta = functools.lru_cache(maxsize=512)
74
+
75
+
76
+ @_cache_load_timedelta
77
+ def _load_timedelta(seconds):
78
+ return timedelta(seconds=seconds)
79
+
80
+
81
+ def _unpickle(cls, key, filename, data):
82
+ obj = cls.__new__(cls)
83
+ obj._key = key
84
+ obj._filename = filename
85
+
86
+ obj._load_from_data(data)
87
+
88
+ return obj
89
+
90
+
91
+ class tzfile(_tzinfo):
92
+ """
93
+ This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)``
94
+ format timezone files to extract current and historical zone information.
95
+
96
+ :param fileobj:
97
+ This can be an opened file stream or a file name that the time zone
98
+ information can be read from.
99
+
100
+ :param filename:
101
+ This is an optional parameter specifying the source of the time zone
102
+ information in the event that ``fileobj`` is a file object. If omitted
103
+ and ``fileobj`` is a file stream, this parameter will be set either to
104
+ ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.
105
+
106
+ This class is intended to be mostly equivalent to the standard library
107
+ :py:class:`zoneinfo.ZoneInfo` class. The primary differences are:
108
+
109
+ * All caching in ``tzfile`` is handled by :func:`tz.gettz` level (you
110
+ should generally not attempt to construct a ``tzfile`` directly).
111
+ * ``tzfile`` objects are pickled by value rather than by reference to
112
+ the IANA key.
113
+ * ``tzfile`` has equality-by-value semantics, whereas
114
+ :py:class:`~zoneinfo.ZoneInfo` has equality-by-identity semantics.
115
+
116
+ See `Sources for Time Zone and Daylight Saving Time Data
117
+ <https://data.iana.org/time-zones/tz-link.html>`_ for more information.
118
+ Time zone files can be compiled from the `IANA Time Zone database files
119
+ <https://www.iana.org/time-zones>`_ with the `zic time zone compiler
120
+ <https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_
121
+
122
+ .. note::
123
+
124
+ Only construct a ``tzfile`` directly if you have a specific timezone
125
+ file on disk that you want to read into a Python ``tzinfo`` object.
126
+ If you want to get a ``tzfile`` representing a specific IANA zone,
127
+ (e.g. ``'America/New_York'``), you should call
128
+ :func:`dateutil.tz.gettz` with the zone identifier.
129
+
130
+ .. versionchanged:: 3.0.0
131
+ ``tzfile`` is now a partial backport of :py:class:`zoneinfo.ZoneInfo`,
132
+ which brings with it support for Version 2 and 3 ``tzfile(5)`` binaries.
133
+
134
+ **Examples:**
135
+
136
+ Using the US Eastern time zone as an example, we can see that a ``tzfile``
137
+ provides time zone information for the standard Daylight Saving offsets:
138
+
139
+ .. testsetup:: tzfile
140
+
141
+ from dateutil.tz import gettz
142
+ from datetime import datetime
143
+
144
+ .. doctest:: tzfile
145
+
146
+ >>> NYC = gettz('America/New_York')
147
+ >>> NYC
148
+ tzfile('/usr/share/zoneinfo/America/New_York')
149
+
150
+ >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST
151
+ 2016-01-03 00:00:00-05:00
152
+
153
+ >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT
154
+ 2016-07-07 00:00:00-04:00
155
+
156
+
157
+ The ``tzfile`` structure contains a fully history of the time zone,
158
+ so historical dates will also have the right offsets. For example, before
159
+ the adoption of standard time on November 18, 1883, New York used local
160
+ mean time:
161
+
162
+ .. doctest:: tzfile
163
+
164
+ >>> print(datetime(1883, 11, 18, tzinfo=NYC)) # LMT
165
+ 1883-11-18 00:00:00-04:56:02
166
+
167
+ Python versions before 3.6 do not support sub-minute offsets, so there the
168
+ offset is rounded to ``-04:56``.
169
+
170
+ And during World War II, New York was on "Eastern War Time", which was a
171
+ state of permanent daylight saving time:
172
+
173
+ .. doctest:: tzfile
174
+
175
+ >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT
176
+ 1944-02-07 00:00:00-04:00
177
+
178
+ """
179
+
180
+ def __init__(self, fileobj, filename=None, key=None):
181
+ super(tzfile, self).__init__()
182
+
183
+ file_opened_here = False
184
+ if isinstance(fileobj, six.string_types):
185
+ self._filename = fileobj
186
+ fileobj = open(fileobj, "rb")
187
+ file_opened_here = True
188
+ elif filename is not None:
189
+ self._filename = filename
190
+ elif hasattr(fileobj, "name"):
191
+ self._filename = fileobj.name
192
+ elif key is not None:
193
+ self._filename = key
194
+ else:
195
+ self._filename = repr(fileobj)
196
+
197
+ if fileobj is not None:
198
+ if not file_opened_here:
199
+ fileobj = _nullcontext(fileobj)
200
+
201
+ with fileobj as file_stream:
202
+ self._load_file(file_stream)
203
+
204
+ self._key = key
205
+
206
+ @property
207
+ def key(self):
208
+ """
209
+ Read-only attribute returning the ``key`` constructor argument.
210
+
211
+ For zones generated from :func:`gettz`, this will be the IANA key
212
+ passed to ``gettz`` — no normalization is done, so although in most
213
+ distributions of the time zone database "US/Eastern" is a link to
214
+ "America/New_York", ``tz.gettz("US/Eastern").key`` should always return
215
+ ``"US/Eastern"``.
216
+
217
+ For zones constructed via another mechanism without specifying ``key``,
218
+ this returns :obj:`None`.
219
+ """
220
+ return self._key
221
+
222
+ def is_ambiguous(self, dt):
223
+ """
224
+ Whether or not the "wall time" of a given datetime is ambiguous in this
225
+ zone.
226
+
227
+ :param dt:
228
+ A :py:class:`datetime.datetime`, naive or time zone aware.
229
+
230
+
231
+ :return:
232
+ Returns ``True`` if ambiguous, ``False`` otherwise.
233
+
234
+ .. versionadded:: 2.6.0
235
+ """
236
+ return self._is_ambiguous_or_imaginary(dt) == 1
237
+
238
+ def _is_ambiguous_or_imaginary(self, dt):
239
+ if dt is None or self._fixed_offset:
240
+ return 0
241
+
242
+ timestamp = self._get_local_timestamp(dt)
243
+ tti_0 = self._find_trans(dt, fold=0, timestamp=timestamp)
244
+ tti_1 = self._find_trans(dt, fold=1, timestamp=timestamp)
245
+
246
+ # Returns 1 if ambiguous, -1 if imaginary and 0 otherwise.
247
+ return int(tti_0.utcoff > tti_1.utcoff) - int(
248
+ tti_1.utcoff > tti_0.utcoff
249
+ )
250
+
251
+ def utcoffset(self, dt):
252
+ return self._find_trans(dt).utcoff
253
+
254
+ def dst(self, dt):
255
+ return self._find_trans(dt).dstoff
256
+
257
+ @tzname_in_python2
258
+ def tzname(self, dt):
259
+ return self._find_trans(dt).tzname
260
+
261
+ def fromutc(self, dt):
262
+ """
263
+ The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.
264
+
265
+ :param dt:
266
+ A :py:class:`datetime.datetime` object.
267
+
268
+ :raises TypeError:
269
+ Raised if ``dt`` is not a :py:class:`datetime.datetime` object.
270
+
271
+ :raises ValueError:
272
+ Raised if this is called with a ``dt`` which does not have this
273
+ ``tzinfo`` attached.
274
+
275
+ :return:
276
+ Returns a :py:class:`datetime.datetime` object representing the
277
+ wall time in ``self``'s time zone.
278
+ """
279
+
280
+ if not isinstance(dt, datetime):
281
+ raise TypeError("fromutc() requires a datetime argument")
282
+ if dt.tzinfo is not self:
283
+ raise ValueError("dt.tzinfo is not self")
284
+
285
+ timestamp = self._get_local_timestamp(dt)
286
+ num_trans = len(self._trans_utc)
287
+
288
+ if num_trans >= 1 and timestamp < self._trans_utc[0]:
289
+ tti = self._tti_before
290
+ fold = 0
291
+ elif (
292
+ num_trans == 0 or timestamp > self._trans_utc[-1]
293
+ ) and not isinstance(self._tz_after, _ttinfo):
294
+ tti, fold = self._tz_after.get_trans_info_fromutc(
295
+ timestamp, dt.year
296
+ )
297
+
298
+ # The TZ string only knows about its own transitions, so check
299
+ # whether we are in a fold created by the last explicit one.
300
+ if num_trans and not fold:
301
+ if num_trans > 1:
302
+ tti_prev = self._ttinfos[-2]
303
+ else:
304
+ tti_prev = self._tti_before
305
+
306
+ shift = tti_prev.utcoff - tti.utcoff
307
+ fold = shift.total_seconds() > timestamp - self._trans_utc[-1]
308
+ elif num_trans == 0:
309
+ tti = self._tz_after
310
+ fold = 0
311
+ else:
312
+ idx = bisect.bisect_right(self._trans_utc, timestamp)
313
+
314
+ if num_trans > 1 and timestamp >= self._trans_utc[1]:
315
+ tti_prev, tti = self._ttinfos[idx - 2 : idx]
316
+ elif timestamp > self._trans_utc[-1]:
317
+ # Only reachable with exactly one transition, so the
318
+ # offset before it is _tti_before, not _ttinfos[-1].
319
+ tti_prev = self._tti_before
320
+ tti = self._tz_after
321
+ else:
322
+ tti_prev = self._tti_before
323
+ tti = self._ttinfos[0]
324
+
325
+ # Detect fold
326
+ shift = tti_prev.utcoff - tti.utcoff
327
+ fold = shift.total_seconds() > timestamp - self._trans_utc[idx - 1]
328
+ dt += tti.utcoff
329
+ if fold:
330
+ return enfold(dt, fold=1)
331
+ else:
332
+ return dt
333
+
334
+ def _find_trans(self, dt, fold=None, timestamp=None):
335
+ if dt is None:
336
+ if self._fixed_offset:
337
+ return self._tz_after
338
+ else:
339
+ return _NO_TTINFO
340
+
341
+ if timestamp is None:
342
+ ts = self._get_local_timestamp(dt)
343
+ else:
344
+ ts = timestamp
345
+
346
+ if fold is None:
347
+ fold = getattr(dt, "fold", 0)
348
+
349
+ lt = self._trans_local[fold]
350
+
351
+ num_trans = len(lt)
352
+
353
+ if num_trans and ts < lt[0]:
354
+ return self._tti_before
355
+ elif not num_trans or ts > lt[-1]:
356
+ if isinstance(self._tz_after, _TZStr):
357
+ tti = self._tz_after.get_trans_info(ts, dt.year, fold)
358
+
359
+ # The TZ string only describes times after the last explicit
360
+ # transition. If its answer for this local time is a different
361
+ # offset from the last transition's, and corresponds to a time
362
+ # before that transition (e.g. when the TZ string's own DST
363
+ # period for that year ends after the transition), the offset
364
+ # from the last explicit transition applies instead.
365
+ last_tti = self._ttinfos[-1] if num_trans else None
366
+ if (
367
+ last_tti is not None
368
+ and tti.utcoff != last_tti.utcoff
369
+ and ts - tti.utcoff.total_seconds() < self._trans_utc[-1]
370
+ ):
371
+ return last_tti
372
+
373
+ return tti
374
+ else:
375
+ return self._tz_after
376
+ else:
377
+ # idx is the transition that occurs after this timestamp, so we
378
+ # subtract off 1 to get the current ttinfo
379
+ idx = bisect.bisect_right(lt, ts) - 1
380
+ assert idx >= 0
381
+ return self._ttinfos[idx]
382
+
383
+ def _get_local_timestamp(self, dt):
384
+ return (
385
+ (dt.toordinal() - EPOCHORDINAL) * 86400
386
+ + dt.hour * 3600
387
+ + dt.minute * 60
388
+ + dt.second
389
+ )
390
+
391
+ # The attributes set by _unpickle and _load_from_data
392
+ _UNPICKLE_ATTRS = frozenset(
393
+ [
394
+ "_filename",
395
+ "_fixed_offset",
396
+ "_isdst",
397
+ "_key",
398
+ "_trans_idx",
399
+ "_trans_local",
400
+ "_trans_utc",
401
+ "_tti_before",
402
+ "_ttinfo_list",
403
+ "_ttinfos",
404
+ "_tz_after",
405
+ "_tz_str",
406
+ "_utcoff_raw",
407
+ ]
408
+ )
409
+
410
+ def __reduce__(self):
411
+ return self.__reduce_ex__(None)
412
+
413
+ def __reduce_ex__(self, protocol):
414
+ utcoff = [
415
+ int(ttinfo.utcoff.total_seconds()) for ttinfo in self._ttinfo_list
416
+ ]
417
+ utcoff = getattr(self, "_utcoff_raw", utcoff)
418
+ abbr = [ttinfo.tzname for ttinfo in self._ttinfo_list]
419
+
420
+ # dst() is inferred per transition, so the transitions don't map back
421
+ # to the local time types, and these are kept as they were in the file
422
+ trans_idx = self._trans_idx
423
+ isdst = self._isdst
424
+
425
+ trans_utc = self._trans_utc
426
+ tz_str = self._tz_str
427
+
428
+ data = (trans_idx, trans_utc, utcoff, isdst, abbr, tz_str)
429
+ args = (self.__class__, self._key, self._filename, data)
430
+
431
+ # Preserve any other instance state, such as attributes added by a
432
+ # subclass, but not the attributes that _unpickle rebuilds from data
433
+ state = {
434
+ k: v for k, v in vars(self).items() if k not in self._UNPICKLE_ATTRS
435
+ }
436
+ if state:
437
+ return (_unpickle, args, state)
438
+
439
+ return (_unpickle, args)
440
+
441
+ def _load_file(self, fobj):
442
+ # Reading the file requires seeking past the parts we don't use and
443
+ # reading the footer a line at a time, so read anything that doesn't
444
+ # support that (e.g. a pipe) into memory first; TZif files are small.
445
+ if not _is_seekable(fobj) or not hasattr(fobj, "readline"):
446
+ fobj = io.BytesIO(fobj.read())
447
+
448
+ # Retrieve all the data as it exists in the zoneinfo file
449
+ data = load_data(fobj)
450
+ self._load_from_data(data)
451
+
452
+ def _load_from_data(self, data):
453
+ trans_idx, trans_utc, utcoff, isdst, abbr, tz_str = data
454
+
455
+ # Infer the DST offsets (needed for .dst()) from the data, for each
456
+ # transition and for each local time type
457
+ trans_dstoff = self._utcoff_to_dstoff(
458
+ trans_idx, trans_utc, utcoff, isdst
459
+ )
460
+ dstoff = [3600 if dst else 0 for dst in isdst]
461
+ for idx, trans_off in zip(trans_idx, trans_dstoff):
462
+ dstoff[idx] = trans_off
463
+
464
+ if sys.version_info < (3, 6):
465
+ # For python pre-3.6, round to full-minutes if that's not the case.
466
+ # Python's datetime doesn't accept sub-minute timezones. Check
467
+ # http://python.org/sf/1447945 or https://bugs.python.org/issue5288
468
+ # for some information.
469
+ utcoff_adjusted = [60 * ((s + 30) // 60) for s in utcoff]
470
+ dstoff = [60 * ((s + 30) // 60) for s in dstoff]
471
+ trans_dstoff = [60 * ((s + 30) // 60) for s in trans_dstoff]
472
+ self._utcoff_raw = utcoff
473
+ else:
474
+ utcoff_adjusted = utcoff
475
+
476
+ # Convert all the transition times (UTC) into "seconds since 1970-01-01 local time"
477
+ trans_local = self._ts_to_local(trans_idx, trans_utc, utcoff_adjusted)
478
+
479
+ # Construct `_ttinfo` objects for each transition in the file
480
+ _ttinfo_list = [
481
+ _ttinfo(
482
+ _load_timedelta(utcoffset), _load_timedelta(dstoffset), tzname
483
+ )
484
+ for utcoffset, dstoffset, tzname in zip(
485
+ utcoff_adjusted, dstoff, abbr
486
+ )
487
+ ]
488
+
489
+ self._ttinfo_list = _ttinfo_list
490
+
491
+ self._trans_idx = trans_idx
492
+ self._isdst = isdst
493
+ self._trans_utc = trans_utc
494
+ self._trans_local = trans_local
495
+
496
+ # Transitions to the same local time type usually have the same DST
497
+ # offset, so share the _ttinfo objects between them where possible.
498
+ ttinfos = {}
499
+ self._ttinfos = []
500
+ for idx, trans_off in zip(trans_idx, trans_dstoff):
501
+ tti = _ttinfo_list[idx]
502
+ if tti.dstoff.total_seconds() != trans_off:
503
+ key = (idx, trans_off)
504
+ if key not in ttinfos:
505
+ ttinfos[key] = _ttinfo(
506
+ tti.utcoff, _load_timedelta(trans_off), tti.tzname
507
+ )
508
+ tti = ttinfos[key]
509
+ self._ttinfos.append(tti)
510
+
511
+ # Find the first non-DST transition
512
+ for i in range(len(isdst)):
513
+ if not isdst[i]:
514
+ self._tti_before = _ttinfo_list[i]
515
+ break
516
+ else:
517
+ if self._ttinfos:
518
+ self._tti_before = self._ttinfos[0]
519
+ else:
520
+ self._tti_before = None
521
+
522
+ # Set the "fallback" time zone
523
+ self._tz_str = tz_str
524
+ if tz_str is not None and tz_str != b"":
525
+ self._tz_after = _parse_tz_str(tz_str.decode())
526
+ else:
527
+ if not self._ttinfos and not _ttinfo_list:
528
+ raise ValueError("No time zone information found.")
529
+
530
+ if self._ttinfos:
531
+ self._tz_after = self._ttinfos[-1]
532
+ else:
533
+ # With no transitions and no TZ string, RFC 9636 Section 3.2
534
+ # says that local time is specified by time type 0
535
+ self._tz_after = _ttinfo_list[0]
536
+
537
+ # Determine if this is a "fixed offset" zone, meaning that the output
538
+ # of the utcoffset, dst and tzname functions does not depend on the
539
+ # specific datetime passed.
540
+ #
541
+ # We make three simplifying assumptions here:
542
+ #
543
+ # 1. If _tz_after is not a _ttinfo, it has transitions that might
544
+ # actually occur (it is possible to construct TZ strings that
545
+ # specify STD and DST but no transitions ever occur, such as
546
+ # AAA0BBB,0/0,J365/25).
547
+ # 2. If _ttinfo_list contains more than one _ttinfo object, the objects
548
+ # represent different offsets.
549
+ # 3. _ttinfo_list contains no unused _ttinfos (in which case an
550
+ # otherwise fixed-offset zone with extra _ttinfos defined may
551
+ # appear to *not* be a fixed offset zone).
552
+ #
553
+ # Violations to these assumptions would be fairly exotic, and exotic
554
+ # zones should almost certainly not be used with datetime.time (the
555
+ # only thing that would be affected by this).
556
+ if len(_ttinfo_list) > 1 or not isinstance(self._tz_after, _ttinfo):
557
+ self._fixed_offset = False
558
+ elif not _ttinfo_list:
559
+ self._fixed_offset = True
560
+ else:
561
+ self._fixed_offset = _ttinfo_list[0] == self._tz_after
562
+
563
+ @staticmethod
564
+ def _utcoff_to_dstoff(trans_idx, trans_utc, utcoffsets, isdsts):
565
+ """Infer the DST offset (the value of dst()) for each transition.
566
+
567
+ TZif files give the total UTC offset of each local time type and
568
+ whether it is DST, but not how much of the offset is DST, so this has
569
+ to be worked out from the standard time on either side of each DST
570
+ period. That is done for each transition rather than for each local
571
+ time type, because zic reuses a local time type for periods with
572
+ different standard offsets (e.g. UTC+0 DST in the Azores was double
573
+ summer time in the 1940s and summer time later).
574
+ """
575
+ n = len(trans_idx)
576
+
577
+ # The DST offset implied by the last standard time before and the
578
+ # first standard time after each transition. Before the first
579
+ # transition, it's the first standard time type, as for _tti_before.
580
+ first_std_utcoff = next(
581
+ (utcoff for utcoff, dst in zip(utcoffsets, isdsts) if not dst),
582
+ None,
583
+ )
584
+ before = [None] * n
585
+ after = [None] * n
586
+ for indices, implied, std_utcoff in (
587
+ (range(n), before, first_std_utcoff),
588
+ (range(n - 1, -1, -1), after, None),
589
+ ):
590
+ for i in indices:
591
+ idx = trans_idx[i]
592
+ if not isdsts[idx]:
593
+ std_utcoff = utcoffsets[idx]
594
+ elif std_utcoff is not None:
595
+ implied[i] = utcoffsets[idx] - std_utcoff
596
+
597
+ # DST offsets where the standard time is the same on both sides
598
+ clear_cut = [
599
+ (trans_utc[i], before[i])
600
+ for i in range(n)
601
+ if before[i] and before[i] == after[i]
602
+ ]
603
+
604
+ dstoffs = [0] * n
605
+ for i in range(n):
606
+ if not isdsts[trans_idx[i]]:
607
+ continue
608
+
609
+ candidates = {c for c in (before[i], after[i]) if c}
610
+ if len(candidates) > 1:
611
+ # The standard offset changed during the DST period. A change
612
+ # from local mean time can give something like 0:34:39, which
613
+ # is not a plausible amount of DST.
614
+ plausible = {c for c in candidates if c % 300 == 0}
615
+ candidates = plausible or candidates
616
+
617
+ if len(candidates) > 1:
618
+ # Prefer the amount of DST the zone used around the same
619
+ # time, e.g. Tehran used 1:00 and Rarotonga 0:30, though the
620
+ # TZif data for Tehran in 1977 and for Rarotonga in 1978 look
621
+ # the same. This has to be local in time, because some zones
622
+ # used different amounts in different eras (e.g. Montevideo
623
+ # used 0:30 in the 1920s and 1:00 later).
624
+ nearby = {
625
+ dstoff
626
+ for ts, dstoff in clear_cut
627
+ if abs(ts - trans_utc[i]) <= _DST_CONSENSUS_WINDOW
628
+ }
629
+ consensus = candidates & nearby
630
+ if len(consensus) == 1:
631
+ candidates = consensus
632
+
633
+ if candidates:
634
+ # Otherwise, prefer positive DST, then the smaller amount. In
635
+ # the 1942 occupation zones, e.g. Europe/Kyiv, the standard
636
+ # time before gives -1:00 and the one after +1:00.
637
+ dstoffs[i] = min(candidates, key=lambda c: (c < 0, abs(c)))
638
+ else:
639
+ # Without any standard time to compare to, one hour is a much
640
+ # better guess than 0
641
+ dstoffs[i] = 3600
642
+
643
+ return dstoffs
644
+
645
+ @staticmethod
646
+ def _ts_to_local(trans_idx, trans_list_utc, utcoffsets):
647
+ """Generate number of seconds since 1970 *in the local time*.
648
+
649
+ This is necessary to easily find the transition times in local time"""
650
+ if not trans_list_utc:
651
+ return [[], []]
652
+
653
+ # Start with the timestamps and modify in-place
654
+ trans_list_wall = [list(trans_list_utc), list(trans_list_utc)]
655
+
656
+ if len(utcoffsets) > 1:
657
+ offset_0 = utcoffsets[0]
658
+ offset_1 = utcoffsets[trans_idx[0]]
659
+ if offset_1 > offset_0:
660
+ offset_1, offset_0 = offset_0, offset_1
661
+ else:
662
+ offset_0 = offset_1 = utcoffsets[0]
663
+
664
+ trans_list_wall[0][0] += offset_0
665
+ trans_list_wall[1][0] += offset_1
666
+
667
+ for i in range(1, len(trans_idx)):
668
+ offset_0 = utcoffsets[trans_idx[i - 1]]
669
+ offset_1 = utcoffsets[trans_idx[i]]
670
+
671
+ if offset_1 > offset_0:
672
+ offset_1, offset_0 = offset_0, offset_1
673
+
674
+ trans_list_wall[0][i] += offset_0
675
+ trans_list_wall[1][i] += offset_1
676
+
677
+ return trans_list_wall
678
+
679
+ def __eq__(self, other):
680
+ if not isinstance(other, tzfile):
681
+ return NotImplemented
682
+
683
+ return (
684
+ self._trans_utc == other._trans_utc
685
+ and self._trans_local == other._trans_local
686
+ and self._ttinfos == other._ttinfos
687
+ and self._tti_before == other._tti_before
688
+ and self._fallback_key() == other._fallback_key()
689
+ )
690
+
691
+ def _fallback_key(self):
692
+ # The behavior after the last transition is determined either by the
693
+ # TZ string or, in its absence, by a single _ttinfo; _TZStr objects
694
+ # do not define equality, so compare the string they came from.
695
+ if isinstance(self._tz_after, _ttinfo):
696
+ return self._tz_after
697
+ return self._tz_str
698
+
699
+ __hash__ = None
700
+
701
+ def __ne__(self, other):
702
+ return not (self == other)
703
+
704
+ def __repr__(self):
705
+ return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
706
+
707
+
708
+ # Before Python 3.16, pipes on Windows claim to be seekable, but seeking on
709
+ # them silently does nothing. See https://github.com/python/cpython/issues/86768
710
+ _SEEKABLE_PIPES_BUG = sys.platform == "win32" and sys.version_info < (3, 16)
711
+
712
+
713
+ def _is_seekable(fobj):
714
+ if not hasattr(fobj, "seek"):
715
+ return False
716
+
717
+ seekable = getattr(fobj, "seekable", None)
718
+ # Python 2 file objects can seek but have no seekable() method
719
+ if seekable is not None and not seekable():
720
+ return False
721
+
722
+ if not _SEEKABLE_PIPES_BUG:
723
+ return True
724
+
725
+ # For anything backed by a file descriptor, only trust regular files.
726
+ try:
727
+ fd = fobj.fileno()
728
+ except (AttributeError, OSError, ValueError):
729
+ # io.UnsupportedOperation (e.g. from BytesIO) is both of the latter
730
+ return True
731
+
732
+ try:
733
+ return stat.S_ISREG(os.fstat(fd).st_mode)
734
+ except OSError:
735
+ return True
736
+
737
+
738
+ def load_data(fobj):
739
+ try:
740
+ return _load_data(fobj)
741
+ except struct.error as e:
742
+ # struct.unpack fails when a read comes up short
743
+ six.raise_from(
744
+ ValueError("Invalid TZif file: unexpected end of file"), e
745
+ )
746
+
747
+
748
+ def _load_data(fobj):
749
+ header = _TZifHeader.from_file(fobj)
750
+
751
+ if header.version == 1:
752
+ time_size = 4
753
+ time_type = "l"
754
+ else:
755
+ # Version 2+ has 64-bit integer transition times
756
+ time_size = 8
757
+ time_type = "q"
758
+
759
+ # Version 2+ also starts with a Version 1 header and data, which
760
+ # we need to skip now
761
+ skip_bytes = (
762
+ header.timecnt * 5 # Transition times and types
763
+ + header.typecnt * 6 # Local time type records
764
+ + header.charcnt # Time zone designations
765
+ + header.leapcnt * 8 # Leap second records
766
+ + header.isstdcnt # Standard/wall indicators
767
+ + header.isutcnt # UT/local indicators
768
+ )
769
+
770
+ fobj.seek(skip_bytes, 1)
771
+
772
+ # Now we need to read the second header, which is not the same
773
+ # as the first
774
+ header = _TZifHeader.from_file(fobj)
775
+
776
+ typecnt = header.typecnt
777
+ timecnt = header.timecnt
778
+ charcnt = header.charcnt
779
+
780
+ # The data portion starts with timecnt transitions and indices
781
+ if timecnt:
782
+ trans_list_utc = struct.unpack(
783
+ ">%s%s" % (timecnt, time_type), fobj.read(timecnt * time_size)
784
+ )
785
+ trans_idx = struct.unpack(">%sB" % timecnt, fobj.read(timecnt))
786
+
787
+ if max(trans_idx) >= typecnt:
788
+ raise ValueError(
789
+ "Invalid transition index found while reading TZif: %s"
790
+ % max(trans_idx)
791
+ )
792
+ else:
793
+ trans_list_utc = ()
794
+ trans_idx = ()
795
+
796
+ # Read the ttinfo struct, (utoff, isdst, abbrind)
797
+ if typecnt:
798
+ utcoff, isdst, abbrind = zip(
799
+ *(struct.unpack(">lbb", fobj.read(6)) for i in range(typecnt))
800
+ )
801
+ else:
802
+ utcoff = ()
803
+ isdst = ()
804
+ abbrind = ()
805
+
806
+ # Now read the abbreviations. They are null-terminated strings, indexed
807
+ # not by position in the array but by position in the unsplit
808
+ # abbreviation string. I suppose this makes more sense in C, which uses
809
+ # null to terminate the strings, but it's inconvenient here...
810
+ abbr_vals = {}
811
+ abbr_chars = fobj.read(charcnt)
812
+
813
+ def get_abbr(idx):
814
+ # Gets a string starting at idx and running until the next \x00
815
+ #
816
+ # We cannot pre-populate abbr_vals by splitting on \x00 because there
817
+ # are some zones that use subsets of longer abbreviations, like so:
818
+ #
819
+ # LMT\x00AHST\x00HDT\x00
820
+ #
821
+ # Where the idx to abbr mapping should be:
822
+ #
823
+ # {0: "LMT", 4: "AHST", 5: "HST", 9: "HDT"}
824
+ if idx not in abbr_vals:
825
+ span_end = abbr_chars.find(b"\x00", idx)
826
+ abbr_vals[idx] = abbr_chars[idx:span_end].decode()
827
+
828
+ return abbr_vals[idx]
829
+
830
+ abbr = tuple(get_abbr(idx) for idx in abbrind)
831
+
832
+ # The remainder of the file consists of leap seconds (currently unused) and
833
+ # the standard/wall and ut/local indicators, which are metadata we don't need.
834
+ # In version 2 files, we need to skip the unnecessary data to get at the TZ string,
835
+ # and in version 1 files we'll just hopefully consume the rest of the file.
836
+
837
+ # Each leap second record has size (time_size + 4)
838
+ skip_bytes = (
839
+ header.isutcnt + header.isstdcnt + header.leapcnt * (time_size + 4)
840
+ )
841
+ fobj.seek(skip_bytes, 1)
842
+ if header.version >= 2:
843
+ c = fobj.read(1) # Should be \n
844
+ if c != b"\n":
845
+ raise ValueError("Invalid TZif file: expected newline, got %r" % c)
846
+
847
+ line = fobj.readline()
848
+ if not line.endswith(b"\n"):
849
+ raise ValueError("Invalid TZif file: unexpected end of file")
850
+ tz_str = line.rstrip(b"\n")
851
+ else:
852
+ tz_str = None
853
+
854
+ return trans_idx, trans_list_utc, utcoff, isdst, abbr, tz_str
855
+
856
+
857
+ # How close in time (in seconds) the DST periods that _utcoff_to_dstoff uses
858
+ # to choose between two possible DST offsets have to be
859
+ _DST_CONSENSUS_WINDOW = 10 * 365 * 86400
860
+
861
+
862
+ class _TZifHeader(object):
863
+ __slots__ = [
864
+ "version",
865
+ "isutcnt",
866
+ "isstdcnt",
867
+ "leapcnt",
868
+ "timecnt",
869
+ "typecnt",
870
+ "charcnt",
871
+ ]
872
+
873
+ def __init__(self, *args):
874
+ assert len(self.__slots__) == len(args)
875
+ for attr, val in zip(self.__slots__, args):
876
+ setattr(self, attr, val)
877
+
878
+ @classmethod
879
+ def from_file(cls, stream):
880
+ # The header starts with a 4-byte "magic" value
881
+ if stream.read(4) != b"TZif":
882
+ raise ValueError("Invalid TZif file: magic not found")
883
+
884
+ _version = stream.read(1)
885
+ if _version == b"\x00":
886
+ version = 1
887
+ else:
888
+ version = int(_version)
889
+ stream.read(15)
890
+
891
+ args = (version,)
892
+
893
+ # Slots are defined in the order that the bytes are arranged
894
+ args = args + struct.unpack(">6l", stream.read(24))
895
+
896
+ return cls(*args)
897
+
898
+
899
+ class _ttinfo(object):
900
+ __slots__ = ["utcoff", "dstoff", "tzname"]
901
+
902
+ def __init__(self, utcoff, dstoff, tzname):
903
+ self.utcoff = utcoff
904
+ self.dstoff = dstoff
905
+ self.tzname = tzname
906
+
907
+ def __eq__(self, other):
908
+ if not isinstance(other, _ttinfo):
909
+ return NotImplemented
910
+
911
+ return (
912
+ self.utcoff == other.utcoff
913
+ and self.dstoff == other.dstoff
914
+ and self.tzname == other.tzname
915
+ )
916
+
917
+ def __ne__(self, other):
918
+ # Python 2 does not derive __ne__ from __eq__
919
+ eq = self.__eq__(other)
920
+ if eq is NotImplemented:
921
+ return eq
922
+ return not eq
923
+
924
+ def __repr__(self): # pragma: nocover
925
+ return "%s(%s, %s, %s)" % (
926
+ self.__class__.__name__,
927
+ self.utcoff,
928
+ self.dstoff,
929
+ self.tzname,
930
+ )
931
+
932
+
933
+ _NO_TTINFO = _ttinfo(None, None, None)
934
+
935
+
936
+ def _unpack(t):
937
+ # Python 2-specific backport for x, *y = t
938
+ return t[0], t[1:]
939
+
940
+
941
+ # re.ASCII does not exist on Python 2, where str patterns are ASCII-only
942
+ # anyway (and unicode patterns are too unless re.UNICODE is passed).
943
+ _RE_ASCII = getattr(re, "ASCII", 0)
944
+
945
+
946
+ def _fullmatch(pattern, string, flags=0):
947
+ # Python 2-specific backport of re.fullmatch
948
+ return re.match(r"(?:%s)\Z" % pattern, string, flags)
949
+
950
+
951
+ def _round_offset(seconds):
952
+ # Python < 3.6 does not support sub-minute offsets, see the comment in
953
+ # tzfile._load_from_data.
954
+ if sys.version_info < (3, 6):
955
+ return 60 * ((seconds + 30) // 60)
956
+ return seconds
957
+
958
+
959
+ def _parse_tz_str(tz_str):
960
+ # The tz string has the format:
961
+ #
962
+ # std[offset[dst[offset],start[/time],end[/time]]]
963
+ #
964
+ # std and dst must be 3 or more characters long and must not contain
965
+ # a leading colon, embedded digits, commas, nor a plus or minus signs;
966
+ # The spaces between "std" and "offset" are only for display and are
967
+ # not actually present in the string.
968
+ #
969
+ # The format of the offset is ``[+|-]hh[:mm[:ss]]``
970
+
971
+ # TODO: When we drop Python 2, this should be offset_str, *start_end_str = ...
972
+ offset_str, start_end_str = _unpack(tz_str.split(",", 1))
973
+
974
+ parser_re = (
975
+ r"(?P<std>[a-zA-Z]+|<[a-zA-Z0-9+-]+>)"
976
+ r"(?:"
977
+ r"(?P<stdoff>[+-]?\d{1,3}(?::\d{2}(?::\d{2})?)?)"
978
+ r"(?:"
979
+ r"(?P<dst>[a-zA-Z]+|<[a-zA-Z0-9+-]+>)"
980
+ r"(?P<dstoff>[+-]?\d{1,3}(?::\d{2}(?::\d{2})?)?)?"
981
+ r")?" # dst
982
+ r")?" # stdoff
983
+ )
984
+
985
+ m = _fullmatch(parser_re, offset_str, _RE_ASCII)
986
+
987
+ if m is None:
988
+ raise ValueError("%s is not a valid TZ string" % tz_str)
989
+
990
+ std_abbr = m.group("std")
991
+ dst_abbr = m.group("dst")
992
+ dst_offset = None
993
+
994
+ std_abbr = std_abbr.strip("<>")
995
+
996
+ if dst_abbr:
997
+ dst_abbr = dst_abbr.strip("<>")
998
+
999
+ std_offset = m.group("stdoff")
1000
+ if std_offset:
1001
+ try:
1002
+ std_offset = _round_offset(_parse_tz_delta(std_offset))
1003
+ except ValueError as e:
1004
+ raise ValueError("Invalid STD offset in %s" % tz_str)
1005
+ else:
1006
+ # The STD offset is required
1007
+ raise ValueError("Invalid STD offset in %s" % tz_str)
1008
+
1009
+ if dst_abbr is not None:
1010
+ dst_offset = m.group("dstoff")
1011
+ if dst_offset:
1012
+ try:
1013
+ dst_offset = _round_offset(_parse_tz_delta(dst_offset))
1014
+ except ValueError as e:
1015
+ raise ValueError("Invalid DST offset in %s" % tz_str)
1016
+ else:
1017
+ dst_offset = std_offset + 3600
1018
+
1019
+ if not start_end_str:
1020
+ raise ValueError("Missing transition rules: %s" % tz_str)
1021
+
1022
+ start_end_strs = start_end_str[0].split(",", 1)
1023
+ try:
1024
+ start, end = (_parse_dst_start_end(x) for x in start_end_strs)
1025
+ except ValueError as e:
1026
+ raise ValueError("Invalid TZ string: %s" % tz_str)
1027
+
1028
+ return _TZStr(std_abbr, std_offset, dst_abbr, dst_offset, start, end)
1029
+ elif start_end_str:
1030
+ raise ValueError("Transition rule present without DST: %s" % tz_str)
1031
+ else:
1032
+ # This is a static ttinfo, don't return _TZStr
1033
+ return _ttinfo(
1034
+ _load_timedelta(std_offset), _load_timedelta(0), std_abbr
1035
+ )
1036
+
1037
+
1038
+ def _parse_dst_start_end(dststr):
1039
+ date, time = _unpack(dststr.split("/", 1))
1040
+ date_type = date[:1]
1041
+ if date_type == "M":
1042
+ n_is_julian = False
1043
+ m = _fullmatch(r"M(\d{1,2})\.(\d)\.(\d)", date, _RE_ASCII)
1044
+ if m is None:
1045
+ raise ValueError("Invalid dst start/end date: %s" % dststr)
1046
+ date_offset = tuple(map(int, m.groups()))
1047
+ offset = _CalendarOffset(*date_offset)
1048
+ else:
1049
+ if date_type == "J":
1050
+ n_is_julian = True
1051
+ date = date[1:]
1052
+ else:
1053
+ n_is_julian = False
1054
+
1055
+ if _fullmatch(r"\d{1,3}", date, _RE_ASCII) is None:
1056
+ raise ValueError("Invalid dst start/end date: %s" % dststr)
1057
+ doy = int(date)
1058
+ offset = _DayOffset(doy, n_is_julian)
1059
+
1060
+ if time:
1061
+ offset.hour, offset.minute, offset.second = _parse_transition_time(
1062
+ time[0]
1063
+ )
1064
+
1065
+ return offset
1066
+
1067
+
1068
+ def _parse_transition_time(time_str):
1069
+ match = _fullmatch(
1070
+ r"(?P<sign>[+-])?(?P<h>\d{1,3})(:(?P<m>\d{2})(:(?P<s>\d{2}))?)?",
1071
+ time_str,
1072
+ _RE_ASCII,
1073
+ )
1074
+ if match is None:
1075
+ raise ValueError("Invalid time: %s" % time_str)
1076
+
1077
+ h, m, s = (int(v or 0) for v in map(match.group, ("h", "m", "s")))
1078
+
1079
+ if h > 167:
1080
+ raise ValueError("Hour must be in [0, 167]: %s" % time_str)
1081
+
1082
+ if match.group("sign") == "-":
1083
+ h, m, s = -h, -m, -s
1084
+
1085
+ return h, m, s
1086
+
1087
+
1088
+ def _parse_tz_delta(tz_delta):
1089
+ match = _fullmatch(
1090
+ r"(?P<sign>[+-])?(?P<h>\d{1,3})(:(?P<m>\d{2})(:(?P<s>\d{2}))?)?",
1091
+ tz_delta,
1092
+ _RE_ASCII,
1093
+ )
1094
+ # Anything passed to this function should already have hit an equivalent
1095
+ # regular expression to find the section to parse.
1096
+ assert match is not None, tz_delta
1097
+
1098
+ h, m, s = (int(v or 0) for v in map(match.group, ("h", "m", "s")))
1099
+
1100
+ total = h * 3600 + m * 60 + s
1101
+
1102
+ if h > 24:
1103
+ raise ValueError("Offset hours must be in [0, 24]: %s" % tz_delta)
1104
+
1105
+ # Yes, +5 maps to an offset of -5h
1106
+ if match.group("sign") != "-":
1107
+ total *= -1
1108
+
1109
+ return total
1110
+
1111
+
1112
+ class _TZStr(object):
1113
+ __slots__ = (
1114
+ "std",
1115
+ "dst",
1116
+ "start",
1117
+ "end",
1118
+ "get_trans_info",
1119
+ "get_trans_info_fromutc",
1120
+ "dst_diff",
1121
+ )
1122
+
1123
+ def __init__(
1124
+ self, std_abbr, std_offset, dst_abbr, dst_offset, start=None, end=None
1125
+ ):
1126
+ self.dst_diff = dst_offset - std_offset
1127
+ std_offset = _load_timedelta(std_offset)
1128
+ self.std = _ttinfo(
1129
+ utcoff=std_offset, dstoff=_load_timedelta(0), tzname=std_abbr
1130
+ )
1131
+
1132
+ self.start = start
1133
+ self.end = end
1134
+
1135
+ dst_offset = _load_timedelta(dst_offset)
1136
+ delta = _load_timedelta(self.dst_diff)
1137
+ self.dst = _ttinfo(utcoff=dst_offset, dstoff=delta, tzname=dst_abbr)
1138
+
1139
+ # These are assertions because the constructor should only be called
1140
+ # by functions that would fail before passing start or end
1141
+ assert start is not None, "No transition start specified"
1142
+ assert end is not None, "No transition end specified"
1143
+
1144
+ self.get_trans_info = self._get_trans_info
1145
+ self.get_trans_info_fromutc = self._get_trans_info_fromutc
1146
+
1147
+ def transitions(self, year):
1148
+ start = self.start.year_to_epoch(year)
1149
+ end = self.end.year_to_epoch(year)
1150
+ return start, end
1151
+
1152
+ def _near_new_year(self, ts, year):
1153
+ """Whether ts is close enough to the new year that the rules for the
1154
+ previous or next year might matter."""
1155
+ year_start = _post_epoch_days_before_year(year) * 86400
1156
+ year_end = _post_epoch_days_before_year(year + 1) * 86400
1157
+ return (
1158
+ ts < year_start + _NEW_YEAR_MARGIN
1159
+ or ts >= year_end - _NEW_YEAR_MARGIN
1160
+ )
1161
+
1162
+ def _get_trans_info(self, ts, year, fold):
1163
+ """Get the information about the current transition - tti"""
1164
+ if self._near_new_year(ts, year):
1165
+ return self._get_trans_info_near_new_year(ts, fold)
1166
+
1167
+ start, end = self.transitions(year)
1168
+
1169
+ # With fold = 0, the period (denominated in local time) with the
1170
+ # smaller offset starts at the end of the gap and ends at the end of
1171
+ # the fold; with fold = 1, it runs from the start of the gap to the
1172
+ # beginning of the fold.
1173
+ #
1174
+ # So in order to determine the DST boundaries we need to know both
1175
+ # the fold and whether DST is positive or negative (rare), and it
1176
+ # turns out that this boils down to fold XOR is_positive.
1177
+ if fold == (self.dst_diff >= 0):
1178
+ end -= self.dst_diff
1179
+ else:
1180
+ start += self.dst_diff
1181
+
1182
+ if start < end:
1183
+ isdst = start <= ts < end
1184
+ else:
1185
+ isdst = not (end <= ts < start)
1186
+
1187
+ return self.dst if isdst else self.std
1188
+
1189
+ def _get_trans_info_near_new_year(self, ts, fold):
1190
+ # Answer the question in terms of UTC, where the transitions from the
1191
+ # adjacent years are taken into account: a local time is valid for
1192
+ # an offset if the rules give that offset at the corresponding UTC
1193
+ # time.
1194
+ valid = []
1195
+ for tti in (self.std, self.dst):
1196
+ utc_ts = ts - tti.utcoff.total_seconds()
1197
+ try:
1198
+ utc_year = (EPOCH + timedelta(seconds=utc_ts)).year
1199
+ except OverflowError:
1200
+ # Before year 1 or after year 9999
1201
+ utc_year = MINYEAR if utc_ts < 0 else MAXYEAR
1202
+ actual, _ = self._get_trans_info_fromutc_near_new_year(
1203
+ utc_ts, utc_year
1204
+ )
1205
+ if actual is tti:
1206
+ valid.append(tti)
1207
+
1208
+ if len(valid) == 1:
1209
+ return valid[0]
1210
+
1211
+ smaller, larger = sorted((self.std, self.dst), key=lambda t: t.utcoff)
1212
+ if valid:
1213
+ # Ambiguous: fold=0 is the first occurrence, with the larger offset
1214
+ return larger if fold == 0 else smaller
1215
+ else:
1216
+ # Imaginary: fold=0 uses the offset from before the transition
1217
+ return smaller if fold == 0 else larger
1218
+
1219
+ def _get_trans_info_fromutc_near_new_year(self, ts, year):
1220
+ # The rules are in terms of the local year, which near the new year
1221
+ # is not always the UTC year, and a rule can put a transition up to a
1222
+ # week into the previous or next year (e.g. "J365/25"), so look at the
1223
+ # transitions for the years on either side as well and use the last
1224
+ # one before ts.
1225
+ std_utcoff = self.std.utcoff.total_seconds()
1226
+ dst_utcoff = self.dst.utcoff.total_seconds()
1227
+
1228
+ transitions = []
1229
+ for y in (year - 1, year, year + 1):
1230
+ start, end = self.transitions(y)
1231
+ transitions.append((start - std_utcoff, True))
1232
+ transitions.append((end - dst_utcoff, False))
1233
+
1234
+ # When one DST period ends at the same instant as the next one starts,
1235
+ # as with "DST all year" rules like "0/0,J365/25", sorting puts the
1236
+ # end first, so these stay in DST without a transition in between.
1237
+ transitions.sort()
1238
+
1239
+ isdst = not transitions[0][1]
1240
+ last_change = None
1241
+ i = 0
1242
+ while i < len(transitions) and transitions[i][0] <= ts:
1243
+ trans_ts = transitions[i][0]
1244
+ new_isdst = isdst
1245
+ while i < len(transitions) and transitions[i][0] == trans_ts:
1246
+ new_isdst = transitions[i][1]
1247
+ i += 1
1248
+
1249
+ if new_isdst != isdst:
1250
+ isdst = new_isdst
1251
+ last_change = trans_ts
1252
+
1253
+ if isdst:
1254
+ tti, tti_prev = self.dst, self.std
1255
+ else:
1256
+ tti, tti_prev = self.std, self.dst
1257
+
1258
+ # A transition to a smaller offset creates a fold just after it.
1259
+ fold = False
1260
+ if last_change is not None:
1261
+ shift = (tti_prev.utcoff - tti.utcoff).total_seconds()
1262
+ fold = shift > ts - last_change
1263
+
1264
+ return (tti, fold)
1265
+
1266
+ def _get_trans_info_fromutc(self, ts, year):
1267
+ if self._near_new_year(ts, year):
1268
+ return self._get_trans_info_fromutc_near_new_year(ts, year)
1269
+
1270
+ start, end = self.transitions(year)
1271
+ start -= self.std.utcoff.total_seconds()
1272
+ end -= self.dst.utcoff.total_seconds()
1273
+
1274
+ if start < end:
1275
+ isdst = start <= ts < end
1276
+ else:
1277
+ isdst = not (end <= ts < start)
1278
+
1279
+ # For positive DST, the ambiguous period is one dst_diff after the end
1280
+ # of DST; for negative DST, the ambiguous period is one dst_diff before
1281
+ # the start of DST.
1282
+ if self.dst_diff > 0:
1283
+ ambig_start = end
1284
+ ambig_end = end + self.dst_diff
1285
+ else:
1286
+ ambig_start = start
1287
+ ambig_end = start - self.dst_diff
1288
+
1289
+ fold = ambig_start <= ts < ambig_end
1290
+
1291
+ return (self.dst if isdst else self.std, fold)
1292
+
1293
+
1294
+ # A rule can put a transition up to 167 hours (just under a week) away from
1295
+ # the day it names, and local time is at most a day or so away from UTC, so
1296
+ # only times within this distance of the new year can be affected by the
1297
+ # rules for the previous or next year.
1298
+ _NEW_YEAR_MARGIN = 10 * 86400
1299
+
1300
+
1301
+ def _post_epoch_days_before_year(year):
1302
+ """Get the number of days between 1970-01-01 and YEAR-01-01"""
1303
+ y = year - 1
1304
+ return y * 365 + y // 4 - y // 100 + y // 400 + 1 - EPOCHORDINAL
1305
+
1306
+
1307
+ class _DayOffset(object):
1308
+ __slots__ = ["d", "julian", "hour", "minute", "second"]
1309
+
1310
+ def __init__(self, d, julian, hour=2, minute=0, second=0):
1311
+ min_day = 0 + julian # convert bool to int
1312
+ if not min_day <= d <= 365:
1313
+ raise ValueError("d must be in [%s, 365], not: %s" % (min_day, d))
1314
+
1315
+ self.d = d
1316
+ self.julian = julian
1317
+ self.hour = hour
1318
+ self.minute = minute
1319
+ self.second = second
1320
+
1321
+ def year_to_epoch(self, year):
1322
+ days_before_year = _post_epoch_days_before_year(year)
1323
+
1324
+ # "n" counts days from 0 and includes February 29; "Jn" counts days
1325
+ # from 1 and never counts February 29, so in leap years, J60 (March 1)
1326
+ # and every day after it is one day later than its number suggests.
1327
+ d = self.d
1328
+ if self.julian:
1329
+ d -= 1
1330
+ if d >= 59 and calendar.isleap(year):
1331
+ d += 1
1332
+
1333
+ epoch = (days_before_year + d) * 86400
1334
+ epoch += self.hour * 3600 + self.minute * 60 + self.second
1335
+
1336
+ return epoch
1337
+
1338
+
1339
+ class _CalendarOffset(object):
1340
+ __slots__ = ["m", "w", "d", "hour", "minute", "second"]
1341
+
1342
+ _DAYS_BEFORE_MONTH = (
1343
+ -1,
1344
+ 0,
1345
+ 31,
1346
+ 59,
1347
+ 90,
1348
+ 120,
1349
+ 151,
1350
+ 181,
1351
+ 212,
1352
+ 243,
1353
+ 273,
1354
+ 304,
1355
+ 334,
1356
+ )
1357
+
1358
+ def __init__(self, m, w, d, hour=2, minute=0, second=0):
1359
+ if not 1 <= m <= 12:
1360
+ raise ValueError("m must be in [1, 12]")
1361
+
1362
+ if not 1 <= w <= 5:
1363
+ raise ValueError("w must be in [1, 5]")
1364
+
1365
+ if not 0 <= d <= 6:
1366
+ raise ValueError("d must be in [0, 6]")
1367
+
1368
+ self.m = m
1369
+ self.w = w
1370
+ self.d = d
1371
+ self.hour = hour
1372
+ self.minute = minute
1373
+ self.second = second
1374
+
1375
+ @classmethod
1376
+ def _ymd2ord(cls, year, month, day):
1377
+ return (
1378
+ _post_epoch_days_before_year(year)
1379
+ + cls._DAYS_BEFORE_MONTH[month]
1380
+ + (month > 2 and calendar.isleap(year))
1381
+ + day
1382
+ - 1
1383
+ )
1384
+
1385
+ # TODO: These are not actually epoch dates as they are expressed in local time
1386
+ def year_to_epoch(self, year):
1387
+ """Calculates the datetime of the occurrence from the year"""
1388
+ # We know year and month, we need to convert w, d into day of month
1389
+ #
1390
+ # Week 1 is the first week in which day `d` (where 0 = Sunday) appears.
1391
+ # Week 5 represents the last occurrence of day `d`, so we need to know
1392
+ # the range of the month.
1393
+ #
1394
+ # This doesn't use calendar.monthrange because the rules for years 0
1395
+ # and 10000 can affect times that datetime can represent, and
1396
+ # calendar.monthrange constructs a datetime.date. Instead, get the
1397
+ # weekday (0 = Monday) from the ordinal: 1970-01-01 was a Thursday.
1398
+ first_ordinal = self._ymd2ord(year, self.m, 1)
1399
+ first_day = (first_ordinal + 3) % 7
1400
+ if self.m == 12:
1401
+ next_ordinal = self._ymd2ord(year + 1, 1, 1)
1402
+ else:
1403
+ next_ordinal = self._ymd2ord(year, self.m + 1, 1)
1404
+ days_in_month = next_ordinal - first_ordinal
1405
+
1406
+ # This equation seems magical, so I'll break it down:
1407
+ # 1. calendar says 0 = Monday, POSIX says 0 = Sunday
1408
+ # so we need first_day + 1 to get 1 = Monday -> 7 = Sunday,
1409
+ # which is still equivalent because this math is mod 7
1410
+ # 2. Get first day - desired day mod 7: -1 % 7 = 6, so we don't need
1411
+ # to do anything to adjust negative numbers.
1412
+ # 3. Add 1 because month days are a 1-based index.
1413
+ month_day = (self.d - (first_day + 1)) % 7 + 1
1414
+
1415
+ # Now use a 0-based index version of `w` to calculate the w-th
1416
+ # occurrence of `d`
1417
+ month_day += (self.w - 1) * 7
1418
+
1419
+ # month_day will only be > days_in_month if w was 5, and `w` means
1420
+ # "last occurrence of `d`", so now we just check if we over-shot the
1421
+ # end of the month and if so knock off 1 week.
1422
+ if month_day > days_in_month:
1423
+ month_day -= 7
1424
+
1425
+ ordinal = self._ymd2ord(year, self.m, month_day)
1426
+ epoch = ordinal * 86400
1427
+ epoch += self.hour * 3600 + self.minute * 60 + self.second
1428
+ return epoch
hug/lib/python3.12/site-packages/dateutil/tz/_tzpath.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ try:
2
+ import zoneinfo
3
+
4
+ reset_tzpath = zoneinfo.reset_tzpath
5
+ InvalidTZPathWarning = zoneinfo.InvalidTZPathWarning
6
+
7
+ def __getattr__(name):
8
+ if name == "TZPATH":
9
+ return zoneinfo.TZPATH
10
+ else:
11
+ raise AttributeError(
12
+ "Module %s has no attribute %s" % (repr(__name__), repr(name))
13
+ )
14
+
15
+ except ImportError:
16
+ import os
17
+ import sys
18
+ import warnings
19
+
20
+ import six
21
+
22
+ class InvalidTZPathWarning(RuntimeWarning):
23
+ """Warning raised if an invalid path is specified in PYTHONTZPATH."""
24
+
25
+ def _parse_python_tzpath(env_var, stacklevel):
26
+ if not env_var:
27
+ return ()
28
+
29
+ raw_tzpath = env_var.split(os.pathsep)
30
+ new_tzpath = tuple(filter(os.path.isabs, raw_tzpath))
31
+
32
+ # If anything has been filtered out, we will warn about it
33
+ if len(new_tzpath) != len(raw_tzpath):
34
+ msg = _get_invalid_paths_message(raw_tzpath)
35
+
36
+ warnings.warn(
37
+ "Invalid paths specified in PYTHONTZPATH environment variable. "
38
+ + msg,
39
+ InvalidTZPathWarning,
40
+ stacklevel=stacklevel,
41
+ )
42
+
43
+ return new_tzpath
44
+
45
+ def _get_invalid_paths_message(tzpaths):
46
+ invalid_paths = (path for path in tzpaths if not os.path.isabs(path))
47
+
48
+ prefix = "\n "
49
+ indented_str = prefix + prefix.join(invalid_paths)
50
+
51
+ return (
52
+ "Paths should be absolute but found the following relative paths:"
53
+ + indented_str
54
+ )
55
+
56
+ def _reset_tzpath(to=None, stacklevel=4):
57
+ global TZPATH
58
+ tzpaths = to
59
+ if tzpaths is not None:
60
+ if isinstance(tzpaths, six.string_types + (bytes,)):
61
+ raise TypeError(
62
+ "tzpaths must be a sequence, not %s: %s"
63
+ % (
64
+ type(tzpaths),
65
+ repr(tzpaths),
66
+ )
67
+ )
68
+
69
+ # Path-like objects not supported by os.path operations before 3.6
70
+ if sys.version_info < (3, 6):
71
+ tzpaths = tuple(map(str, tzpaths))
72
+
73
+ if not all(map(os.path.isabs, tzpaths)):
74
+ raise ValueError(_get_invalid_paths_message(tzpaths))
75
+ base_tzpath = tzpaths
76
+ else:
77
+ env_var = os.environ.get("PYTHONTZPATH", None)
78
+ if env_var is not None:
79
+ base_tzpath = _parse_python_tzpath(env_var, stacklevel)
80
+ elif sys.platform != "win32":
81
+ base_tzpath = (
82
+ "/usr/share/zoneinfo",
83
+ "/usr/lib/zoneinfo",
84
+ "/usr/share/lib/zoneinfo",
85
+ "/etc/zoneinfo",
86
+ )
87
+ else:
88
+ base_tzpath = ()
89
+
90
+ TZPATH = tuple(base_tzpath)
91
+ if TZPATH_CALLBACKS:
92
+ for callback in TZPATH_CALLBACKS:
93
+ callback(TZPATH)
94
+
95
+ def reset_tzpath(to=None):
96
+ # As in CPython, the warning about PYTHONTZPATH is raised from a
97
+ # helper that is used both at import time and by this function, so
98
+ # that each can pass the stacklevel that points at its caller.
99
+ _reset_tzpath(to)
100
+
101
+ TZPATH = ()
102
+ TZPATH_CALLBACKS = []
103
+
104
+ _reset_tzpath(stacklevel=5)
hug/lib/python3.12/site-packages/dateutil/tz/tz.py ADDED
@@ -0,0 +1,1472 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ This module offers timezone implementations subclassing the abstract
4
+ :py:class:`datetime.tzinfo` type. There are classes to handle tzfile format
5
+ files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`,
6
+ etc), TZ environment string (in all known formats), given ranges (with help
7
+ from relative deltas), local machine timezone, fixed offset timezone, and UTC
8
+ timezone.
9
+ """
10
+
11
+ import bisect
12
+ import datetime
13
+ import os
14
+ import struct
15
+ import sys
16
+ import time
17
+ import warnings
18
+ import weakref
19
+ from collections import OrderedDict
20
+
21
+ import six
22
+ from six import string_types
23
+ from six.moves import _thread
24
+
25
+ from .. import _tzdata_impl
26
+ from . import _tzpath
27
+ from ._common import (
28
+ _tzinfo,
29
+ _validate_fromutc_inputs,
30
+ enfold,
31
+ tzname_in_python2,
32
+ tzrangebase,
33
+ )
34
+ from ._factories import _TzOffsetFactory, _TzSingleton, _TzStrFactory
35
+ from ._tzfile import tzfile
36
+
37
+ try:
38
+ from .win import tzwin, tzwinlocal
39
+ except ImportError:
40
+ tzwin = tzwinlocal = None
41
+
42
+ # For warning about rounding tzinfo
43
+ from warnings import warn
44
+
45
+ ZERO = datetime.timedelta(0)
46
+ EPOCH = datetime.datetime(1970, 1, 1, 0, 0)
47
+
48
+
49
+ @six.add_metaclass(_TzSingleton)
50
+ class tzutc(datetime.tzinfo):
51
+ """
52
+ This is a tzinfo object that represents the UTC time zone.
53
+
54
+ **Examples:**
55
+
56
+ .. doctest::
57
+
58
+ >>> from datetime import *
59
+ >>> from dateutil.tz import *
60
+
61
+ >>> datetime.now()
62
+ datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)
63
+
64
+ >>> datetime.now(tzutc())
65
+ datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())
66
+
67
+ >>> datetime.now(tzutc()).tzname()
68
+ 'UTC'
69
+
70
+ .. versionchanged:: 2.7.0
71
+ ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will
72
+ always return the same object.
73
+
74
+ .. doctest::
75
+
76
+ >>> from dateutil.tz import tzutc, UTC
77
+ >>> tzutc() is tzutc()
78
+ True
79
+ >>> tzutc() is UTC
80
+ True
81
+ """
82
+ def utcoffset(self, dt):
83
+ return ZERO
84
+
85
+ def dst(self, dt):
86
+ return ZERO
87
+
88
+ @tzname_in_python2
89
+ def tzname(self, dt):
90
+ return "UTC"
91
+
92
+ def is_ambiguous(self, dt):
93
+ """
94
+ Whether or not the "wall time" of a given datetime is ambiguous in this
95
+ zone.
96
+
97
+ :param dt:
98
+ A :py:class:`datetime.datetime`, naive or time zone aware.
99
+
100
+
101
+ :return:
102
+ Returns ``True`` if ambiguous, ``False`` otherwise.
103
+
104
+ .. versionadded:: 2.6.0
105
+ """
106
+ return False
107
+
108
+ @_validate_fromutc_inputs
109
+ def fromutc(self, dt):
110
+ """
111
+ Fast track version of fromutc() returns the original ``dt`` object for
112
+ any valid :py:class:`datetime.datetime` object.
113
+ """
114
+ return dt
115
+
116
+ def __eq__(self, other):
117
+ if not isinstance(other, (tzutc, tzoffset)):
118
+ return NotImplemented
119
+
120
+ return (isinstance(other, tzutc) or
121
+ (isinstance(other, tzoffset) and other._offset == ZERO))
122
+
123
+ __hash__ = None
124
+
125
+ def __ne__(self, other):
126
+ return not (self == other)
127
+
128
+ def __repr__(self):
129
+ return "%s()" % self.__class__.__name__
130
+
131
+ __reduce__ = object.__reduce__
132
+
133
+
134
+ #: Convenience constant providing a :class:`tzutc()` instance
135
+ #:
136
+ #: .. versionadded:: 2.7.0
137
+ UTC = tzutc()
138
+
139
+
140
+ @six.add_metaclass(_TzOffsetFactory)
141
+ class tzoffset(datetime.tzinfo):
142
+ """
143
+ A simple class for representing a fixed offset from UTC.
144
+
145
+ :param name:
146
+ The timezone name, to be returned when ``tzname()`` is called.
147
+ :param offset:
148
+ The time zone offset in seconds, or (since version 2.6.0, represented
149
+ as a :py:class:`datetime.timedelta` object).
150
+ """
151
+ def __init__(self, name, offset):
152
+ self._name = name
153
+
154
+ try:
155
+ # Allow a timedelta
156
+ offset = offset.total_seconds()
157
+ except (TypeError, AttributeError):
158
+ pass
159
+
160
+ self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
161
+
162
+ def utcoffset(self, dt):
163
+ return self._offset
164
+
165
+ def dst(self, dt):
166
+ return ZERO
167
+
168
+ @tzname_in_python2
169
+ def tzname(self, dt):
170
+ return self._name
171
+
172
+ @_validate_fromutc_inputs
173
+ def fromutc(self, dt):
174
+ return dt + self._offset
175
+
176
+ def is_ambiguous(self, dt):
177
+ """
178
+ Whether or not the "wall time" of a given datetime is ambiguous in this
179
+ zone.
180
+
181
+ :param dt:
182
+ A :py:class:`datetime.datetime`, naive or time zone aware.
183
+ :return:
184
+ Returns ``True`` if ambiguous, ``False`` otherwise.
185
+
186
+ .. versionadded:: 2.6.0
187
+ """
188
+ return False
189
+
190
+ def __eq__(self, other):
191
+ if not isinstance(other, tzoffset):
192
+ return NotImplemented
193
+
194
+ return self._offset == other._offset
195
+
196
+ __hash__ = None
197
+
198
+ def __ne__(self, other):
199
+ return not (self == other)
200
+
201
+ def __repr__(self):
202
+ return "%s(%s, %s)" % (self.__class__.__name__,
203
+ repr(self._name),
204
+ int(self._offset.total_seconds()))
205
+
206
+ __reduce__ = object.__reduce__
207
+
208
+
209
+ class tzlocal(_tzinfo):
210
+ """
211
+ A :class:`tzinfo` subclass built around the ``time`` timezone functions.
212
+ """
213
+ def __init__(self):
214
+ super(tzlocal, self).__init__()
215
+
216
+ self._std_offset = datetime.timedelta(seconds=-time.timezone)
217
+ if time.daylight:
218
+ self._dst_offset = datetime.timedelta(seconds=-time.altzone)
219
+ else:
220
+ self._dst_offset = self._std_offset
221
+
222
+ self._dst_saved = self._dst_offset - self._std_offset
223
+ self._hasdst = bool(self._dst_saved)
224
+ self._tznames = tuple(time.tzname)
225
+
226
+ def utcoffset(self, dt):
227
+ if dt is None and self._hasdst:
228
+ return None
229
+
230
+ if self._isdst(dt):
231
+ return self._dst_offset
232
+ else:
233
+ return self._std_offset
234
+
235
+ def dst(self, dt):
236
+ if dt is None and self._hasdst:
237
+ return None
238
+
239
+ if self._isdst(dt):
240
+ return self._dst_offset - self._std_offset
241
+ else:
242
+ return ZERO
243
+
244
+ @tzname_in_python2
245
+ def tzname(self, dt):
246
+ return self._tznames[self._isdst(dt)]
247
+
248
+ def is_ambiguous(self, dt):
249
+ """
250
+ Whether or not the "wall time" of a given datetime is ambiguous in this
251
+ zone.
252
+
253
+ :param dt:
254
+ A :py:class:`datetime.datetime`, naive or time zone aware.
255
+
256
+
257
+ :return:
258
+ Returns ``True`` if ambiguous, ``False`` otherwise.
259
+
260
+ .. versionadded:: 2.6.0
261
+ """
262
+ naive_dst = self._naive_is_dst(dt)
263
+ return (not naive_dst and
264
+ (naive_dst != self._naive_is_dst(dt - self._dst_saved)))
265
+
266
+ def _naive_is_dst(self, dt):
267
+ timestamp = _datetime_to_timestamp(dt)
268
+ return time.localtime(timestamp + time.timezone).tm_isdst
269
+
270
+ def _isdst(self, dt, fold_naive=True):
271
+ # We can't use mktime here. It is unstable when deciding if
272
+ # the hour near to a change is DST or not.
273
+ #
274
+ # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
275
+ # dt.minute, dt.second, dt.weekday(), 0, -1))
276
+ # return time.localtime(timestamp).tm_isdst
277
+ #
278
+ # The code above yields the following result:
279
+ #
280
+ # >>> import tz, datetime
281
+ # >>> t = tz.tzlocal()
282
+ # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
283
+ # 'BRDT'
284
+ # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
285
+ # 'BRST'
286
+ # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
287
+ # 'BRST'
288
+ # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
289
+ # 'BRDT'
290
+ # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
291
+ # 'BRDT'
292
+ #
293
+ # Here is a more stable implementation:
294
+ #
295
+ if not self._hasdst:
296
+ return False
297
+
298
+ # Check for ambiguous times:
299
+ dstval = self._naive_is_dst(dt)
300
+ fold = getattr(dt, 'fold', None)
301
+
302
+ if self.is_ambiguous(dt):
303
+ if fold is not None:
304
+ return not self._fold(dt)
305
+ else:
306
+ return True
307
+
308
+ return dstval
309
+
310
+ def __eq__(self, other):
311
+ if isinstance(other, tzlocal):
312
+ return (self._std_offset == other._std_offset and
313
+ self._dst_offset == other._dst_offset)
314
+ elif isinstance(other, tzutc):
315
+ return (not self._hasdst and
316
+ self._tznames[0] in {'UTC', 'GMT'} and
317
+ self._std_offset == ZERO)
318
+ elif isinstance(other, tzoffset):
319
+ return (not self._hasdst and
320
+ self._tznames[0] == other._name and
321
+ self._std_offset == other._offset)
322
+ else:
323
+ return NotImplemented
324
+
325
+ __hash__ = None
326
+
327
+ def __ne__(self, other):
328
+ return not (self == other)
329
+
330
+ def __repr__(self):
331
+ return "%s()" % self.__class__.__name__
332
+
333
+ __reduce__ = object.__reduce__
334
+
335
+
336
+ class tzrange(tzrangebase):
337
+ """
338
+ The ``tzrange`` object is a time zone specified by a set of offsets and
339
+ abbreviations, equivalent to the way the ``TZ`` variable can be specified
340
+ in POSIX-like systems, but using Python delta objects to specify DST
341
+ start, end and offsets.
342
+
343
+ :param stdabbr:
344
+ The abbreviation for standard time (e.g. ``'EST'``).
345
+
346
+ :param stdoffset:
347
+ An integer or :class:`datetime.timedelta` object or equivalent
348
+ specifying the base offset from UTC.
349
+
350
+ If unspecified, +00:00 is used.
351
+
352
+ :param dstabbr:
353
+ The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).
354
+
355
+ If specified, with no other DST information, DST is assumed to occur
356
+ and the default behavior or ``dstoffset``, ``start`` and ``end`` is
357
+ used. If unspecified and no other DST information is specified, it
358
+ is assumed that this zone has no DST.
359
+
360
+ If this is unspecified and other DST information is *is* specified,
361
+ DST occurs in the zone but the time zone abbreviation is left
362
+ unchanged.
363
+
364
+ :param dstoffset:
365
+ A an integer or :class:`datetime.timedelta` object or equivalent
366
+ specifying the UTC offset during DST. If unspecified and any other DST
367
+ information is specified, it is assumed to be the STD offset +1 hour.
368
+
369
+ :param start:
370
+ A :class:`relativedelta.relativedelta` object or equivalent specifying
371
+ the time and time of year that daylight savings time starts. To
372
+ specify, for example, that DST starts at 2AM on the 2nd Sunday in
373
+ March, pass:
374
+
375
+ ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
376
+
377
+ If unspecified and any other DST information is specified, the default
378
+ value is 2 AM on the first Sunday in April.
379
+
380
+ :param end:
381
+ A :class:`relativedelta.relativedelta` object or equivalent
382
+ representing the time and time of year that daylight savings time
383
+ ends, with the same specification method as in ``start``. One note is
384
+ that this should point to the first time in the *standard* zone, so if
385
+ a transition occurs at 2AM in the DST zone and the clocks are set back
386
+ 1 hour to 1AM, set the ``hours`` parameter to +1.
387
+
388
+
389
+ **Examples:**
390
+
391
+ .. testsetup:: tzrange
392
+
393
+ from dateutil.tz import tzrange, tzstr
394
+
395
+ .. doctest:: tzrange
396
+
397
+ >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")
398
+ True
399
+
400
+ >>> from dateutil.relativedelta import *
401
+ >>> range1 = tzrange("EST", -18000, "EDT")
402
+ >>> range2 = tzrange("EST", -18000, "EDT", -14400,
403
+ ... relativedelta(hours=+2, month=4, day=1,
404
+ ... weekday=SU(+1)),
405
+ ... relativedelta(hours=+1, month=10, day=31,
406
+ ... weekday=SU(-1)))
407
+ >>> tzstr('EST5EDT') == range1 == range2
408
+ True
409
+
410
+ """
411
+ def __init__(self, stdabbr, stdoffset=None,
412
+ dstabbr=None, dstoffset=None,
413
+ start=None, end=None):
414
+
415
+ global relativedelta
416
+ from dateutil import relativedelta
417
+
418
+ self._std_abbr = stdabbr
419
+ self._dst_abbr = dstabbr
420
+
421
+ try:
422
+ stdoffset = stdoffset.total_seconds()
423
+ except (TypeError, AttributeError):
424
+ pass
425
+
426
+ try:
427
+ dstoffset = dstoffset.total_seconds()
428
+ except (TypeError, AttributeError):
429
+ pass
430
+
431
+ if stdoffset is not None:
432
+ self._std_offset = datetime.timedelta(seconds=stdoffset)
433
+ else:
434
+ self._std_offset = ZERO
435
+
436
+ if dstoffset is not None:
437
+ self._dst_offset = datetime.timedelta(seconds=dstoffset)
438
+ elif dstabbr and stdoffset is not None:
439
+ self._dst_offset = self._std_offset + datetime.timedelta(hours=+1)
440
+ else:
441
+ self._dst_offset = ZERO
442
+
443
+ if dstabbr and start is None:
444
+ self._start_delta = relativedelta.relativedelta(
445
+ hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
446
+ else:
447
+ self._start_delta = start
448
+
449
+ if dstabbr and end is None:
450
+ self._end_delta = relativedelta.relativedelta(
451
+ hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
452
+ else:
453
+ self._end_delta = end
454
+
455
+ self._dst_base_offset_ = self._dst_offset - self._std_offset
456
+ self.hasdst = bool(self._start_delta)
457
+
458
+ def transitions(self, year):
459
+ """
460
+ For a given year, get the DST on and off transition times, expressed
461
+ always on the standard time side. For zones with no transitions, this
462
+ function returns ``None``.
463
+
464
+ :param year:
465
+ The year whose transitions you would like to query.
466
+
467
+ :return:
468
+ Returns a :class:`tuple` of :class:`datetime.datetime` objects,
469
+ ``(dston, dstoff)`` for zones with an annual DST transition, or
470
+ ``None`` for fixed offset zones.
471
+ """
472
+ if not self.hasdst:
473
+ return None
474
+
475
+ base_year = datetime.datetime(year, 1, 1)
476
+
477
+ start = base_year + self._start_delta
478
+ end = base_year + self._end_delta
479
+
480
+ return (start, end)
481
+
482
+ def __eq__(self, other):
483
+ if not isinstance(other, tzrange):
484
+ return NotImplemented
485
+
486
+ return (self._std_abbr == other._std_abbr and
487
+ self._dst_abbr == other._dst_abbr and
488
+ self._std_offset == other._std_offset and
489
+ self._dst_offset == other._dst_offset and
490
+ self._start_delta == other._start_delta and
491
+ self._end_delta == other._end_delta)
492
+
493
+ @property
494
+ def _dst_base_offset(self):
495
+ return self._dst_base_offset_
496
+
497
+
498
+ @six.add_metaclass(_TzStrFactory)
499
+ class tzstr(tzrange):
500
+ """
501
+ ``tzstr`` objects are time zone objects specified by a time-zone string as
502
+ it would be passed to a ``TZ`` variable on POSIX-style systems (see
503
+ the `GNU C Library: TZ Variable`_ for more details).
504
+
505
+ There is one notable exception, which is that POSIX-style time zones use an
506
+ inverted offset format, so normally ``GMT+3`` would be parsed as an offset
507
+ 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an
508
+ offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX
509
+ behavior, pass a ``True`` value to ``posix_offset``.
510
+
511
+ The :class:`tzrange` object provides the same functionality, but is
512
+ specified using :class:`relativedelta.relativedelta` objects. rather than
513
+ strings.
514
+
515
+ :param s:
516
+ A time zone string in ``TZ`` variable format. This can be a
517
+ :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:
518
+ :class:`unicode`) or a stream emitting unicode characters
519
+ (e.g. :class:`StringIO`).
520
+
521
+ :param posix_offset:
522
+ Optional. If set to ``True``, interpret strings such as ``GMT+3`` or
523
+ ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the
524
+ POSIX standard.
525
+
526
+ .. caution::
527
+
528
+ Prior to version 2.7.0, this function also supported time zones
529
+ in the format:
530
+
531
+ * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``
532
+ * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``
533
+
534
+ This format is non-standard and has been deprecated; this function
535
+ will raise a :class:`DeprecatedTZFormatWarning` until
536
+ support is removed in a future version.
537
+
538
+ .. _`GNU C Library: TZ Variable`:
539
+ https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
540
+ """
541
+ def __init__(self, s, posix_offset=False):
542
+ global parser
543
+ from dateutil.parser import _parser as parser
544
+
545
+ self._s = s
546
+
547
+ res = parser._parsetz(s)
548
+ if res is None or res.any_unused_tokens:
549
+ raise ValueError("unknown string format")
550
+
551
+ # Here we break the compatibility with the TZ variable handling.
552
+ # GMT-3 actually *means* the timezone -3.
553
+ if res.stdabbr in ("GMT", "UTC") and not posix_offset:
554
+ res.stdoffset *= -1
555
+
556
+ # We must initialize it first, since _delta() needs
557
+ # _std_offset and _dst_offset set. Use False in start/end
558
+ # to avoid building it two times.
559
+ tzrange.__init__(self, res.stdabbr, res.stdoffset,
560
+ res.dstabbr, res.dstoffset,
561
+ start=False, end=False)
562
+
563
+ if not res.dstabbr:
564
+ self._start_delta = None
565
+ self._end_delta = None
566
+ else:
567
+ self._start_delta = self._delta(res.start)
568
+ if self._start_delta:
569
+ self._end_delta = self._delta(res.end, isend=1)
570
+
571
+ self.hasdst = bool(self._start_delta)
572
+
573
+ def _delta(self, x, isend=0):
574
+ from dateutil import relativedelta
575
+ kwargs = {}
576
+ if x.month is not None:
577
+ kwargs["month"] = x.month
578
+ if x.weekday is not None:
579
+ kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
580
+ if x.week > 0:
581
+ kwargs["day"] = 1
582
+ else:
583
+ kwargs["day"] = 31
584
+ elif x.day:
585
+ kwargs["day"] = x.day
586
+ elif x.yday is not None:
587
+ kwargs["yearday"] = x.yday
588
+ elif x.jyday is not None:
589
+ kwargs["nlyearday"] = x.jyday
590
+ if not kwargs:
591
+ # Default is to start on first sunday of april, and end
592
+ # on last sunday of october.
593
+ if not isend:
594
+ kwargs["month"] = 4
595
+ kwargs["day"] = 1
596
+ kwargs["weekday"] = relativedelta.SU(+1)
597
+ else:
598
+ kwargs["month"] = 10
599
+ kwargs["day"] = 31
600
+ kwargs["weekday"] = relativedelta.SU(-1)
601
+ if x.time is not None:
602
+ kwargs["seconds"] = x.time
603
+ else:
604
+ # Default is 2AM.
605
+ kwargs["seconds"] = 7200
606
+ if isend:
607
+ # Convert to standard time, to follow the documented way
608
+ # of working with the extra hour. See the documentation
609
+ # of the tzinfo class.
610
+ delta = self._dst_offset - self._std_offset
611
+ kwargs["seconds"] -= delta.seconds + delta.days * 86400
612
+ return relativedelta.relativedelta(**kwargs)
613
+
614
+ def __repr__(self):
615
+ return "%s(%s)" % (self.__class__.__name__, repr(self._s))
616
+
617
+
618
+ class _tzicalvtzcomp(object):
619
+ def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
620
+ tzname=None, rrule=None):
621
+ self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
622
+ self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
623
+ self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom
624
+ self.isdst = isdst
625
+ self.tzname = tzname
626
+ self.rrule = rrule
627
+
628
+
629
+ class _tzicalvtz(_tzinfo):
630
+ def __init__(self, tzid, comps=[]):
631
+ super(_tzicalvtz, self).__init__()
632
+
633
+ self._tzid = tzid
634
+ self._comps = comps
635
+ self._cachedate = []
636
+ self._cachecomp = []
637
+ self._cache_lock = _thread.allocate_lock()
638
+
639
+ def _find_comp(self, dt):
640
+ if len(self._comps) == 1:
641
+ return self._comps[0]
642
+
643
+ dt = dt.replace(tzinfo=None)
644
+
645
+ try:
646
+ with self._cache_lock:
647
+ return self._cachecomp[self._cachedate.index(
648
+ (dt, self._fold(dt)))]
649
+ except ValueError:
650
+ pass
651
+
652
+ lastcompdt = None
653
+ lastcomp = None
654
+
655
+ for comp in self._comps:
656
+ compdt = self._find_compdt(comp, dt)
657
+
658
+ if compdt and (not lastcompdt or lastcompdt < compdt):
659
+ lastcompdt = compdt
660
+ lastcomp = comp
661
+
662
+ if not lastcomp:
663
+ # RFC says nothing about what to do when a given
664
+ # time is before the first onset date. We'll look for the
665
+ # first standard component, or the first component, if
666
+ # none is found.
667
+ for comp in self._comps:
668
+ if not comp.isdst:
669
+ lastcomp = comp
670
+ break
671
+ else:
672
+ lastcomp = comp[0]
673
+
674
+ with self._cache_lock:
675
+ self._cachedate.insert(0, (dt, self._fold(dt)))
676
+ self._cachecomp.insert(0, lastcomp)
677
+
678
+ if len(self._cachedate) > 10:
679
+ self._cachedate.pop()
680
+ self._cachecomp.pop()
681
+
682
+ return lastcomp
683
+
684
+ def _find_compdt(self, comp, dt):
685
+ if comp.tzoffsetdiff < ZERO and self._fold(dt):
686
+ dt -= comp.tzoffsetdiff
687
+
688
+ compdt = comp.rrule.before(dt, inc=True)
689
+
690
+ return compdt
691
+
692
+ def utcoffset(self, dt):
693
+ if dt is None:
694
+ return None
695
+
696
+ return self._find_comp(dt).tzoffsetto
697
+
698
+ def dst(self, dt):
699
+ comp = self._find_comp(dt)
700
+ if comp.isdst:
701
+ return comp.tzoffsetdiff
702
+ else:
703
+ return ZERO
704
+
705
+ @tzname_in_python2
706
+ def tzname(self, dt):
707
+ return self._find_comp(dt).tzname
708
+
709
+ def __repr__(self):
710
+ return "<tzicalvtz %s>" % repr(self._tzid)
711
+
712
+ __reduce__ = object.__reduce__
713
+
714
+
715
+ class tzical(object):
716
+ """
717
+ This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure
718
+ as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects.
719
+
720
+ :param `fileobj`:
721
+ A file or stream in iCalendar format, which should be UTF-8 encoded
722
+ with CRLF endings.
723
+
724
+ .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545
725
+ """
726
+ def __init__(self, fileobj):
727
+ global rrule
728
+ from dateutil import rrule
729
+
730
+ if isinstance(fileobj, string_types):
731
+ self._s = fileobj
732
+ # ical should be encoded in UTF-8 with CRLF
733
+ fileobj = open(fileobj, 'r')
734
+ else:
735
+ self._s = getattr(fileobj, 'name', repr(fileobj))
736
+ fileobj = _nullcontext(fileobj)
737
+
738
+ self._vtz = {}
739
+
740
+ with fileobj as fobj:
741
+ self._parse_rfc(fobj.read())
742
+
743
+ def keys(self):
744
+ """
745
+ Retrieves the available time zones as a list.
746
+ """
747
+ return list(self._vtz.keys())
748
+
749
+ def get(self, tzid=None):
750
+ """
751
+ Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``.
752
+
753
+ :param tzid:
754
+ If there is exactly one time zone available, omitting ``tzid``
755
+ or passing :py:const:`None` value returns it. Otherwise a valid
756
+ key (which can be retrieved from :func:`keys`) is required.
757
+
758
+ :raises ValueError:
759
+ Raised if ``tzid`` is not specified but there are either more
760
+ or fewer than 1 zone defined.
761
+
762
+ :returns:
763
+ Returns either a :py:class:`datetime.tzinfo` object representing
764
+ the relevant time zone or :py:const:`None` if the ``tzid`` was
765
+ not found.
766
+ """
767
+ if tzid is None:
768
+ if len(self._vtz) == 0:
769
+ raise ValueError("no timezones defined")
770
+ elif len(self._vtz) > 1:
771
+ raise ValueError("more than one timezone available")
772
+ tzid = next(iter(self._vtz))
773
+
774
+ return self._vtz.get(tzid)
775
+
776
+ def _parse_offset(self, s):
777
+ s = s.strip()
778
+ if not s:
779
+ raise ValueError("empty offset")
780
+ if s[0] in ('+', '-'):
781
+ signal = (-1, +1)[s[0] == '+']
782
+ s = s[1:]
783
+ else:
784
+ signal = +1
785
+ if len(s) == 4:
786
+ return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal
787
+ elif len(s) == 6:
788
+ return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal
789
+ else:
790
+ raise ValueError("invalid offset: " + s)
791
+
792
+ def _parse_rfc(self, s):
793
+ lines = s.splitlines()
794
+ if not lines:
795
+ raise ValueError("empty string")
796
+
797
+ # Unfold
798
+ i = 0
799
+ while i < len(lines):
800
+ line = lines[i].rstrip()
801
+ if not line:
802
+ del lines[i]
803
+ elif i > 0 and line[0] == " ":
804
+ lines[i-1] += line[1:]
805
+ del lines[i]
806
+ else:
807
+ i += 1
808
+
809
+ tzid = None
810
+ comps = []
811
+ invtz = False
812
+ comptype = None
813
+ for line in lines:
814
+ if not line:
815
+ continue
816
+ name, value = line.split(':', 1)
817
+ parms = name.split(';')
818
+ if not parms:
819
+ raise ValueError("empty property name")
820
+ name = parms[0].upper()
821
+ parms = parms[1:]
822
+ if invtz:
823
+ if name == "BEGIN":
824
+ if value in ("STANDARD", "DAYLIGHT"):
825
+ # Process component
826
+ pass
827
+ else:
828
+ raise ValueError("unknown component: "+value)
829
+ comptype = value
830
+ founddtstart = False
831
+ tzoffsetfrom = None
832
+ tzoffsetto = None
833
+ rrulelines = []
834
+ tzname = None
835
+ elif name == "END":
836
+ if value == "VTIMEZONE":
837
+ if comptype:
838
+ raise ValueError("component not closed: "+comptype)
839
+ if not tzid:
840
+ raise ValueError("mandatory TZID not found")
841
+ if not comps:
842
+ raise ValueError(
843
+ "at least one component is needed")
844
+ # Process vtimezone
845
+ self._vtz[tzid] = _tzicalvtz(tzid, comps)
846
+ invtz = False
847
+ elif value == comptype:
848
+ if not founddtstart:
849
+ raise ValueError("mandatory DTSTART not found")
850
+ if tzoffsetfrom is None:
851
+ raise ValueError(
852
+ "mandatory TZOFFSETFROM not found")
853
+ if tzoffsetto is None:
854
+ raise ValueError(
855
+ "mandatory TZOFFSETFROM not found")
856
+ # Process component
857
+ rr = None
858
+ if rrulelines:
859
+ rr = rrule.rrulestr("\n".join(rrulelines),
860
+ compatible=True,
861
+ ignoretz=True,
862
+ cache=True)
863
+ comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
864
+ (comptype == "DAYLIGHT"),
865
+ tzname, rr)
866
+ comps.append(comp)
867
+ comptype = None
868
+ else:
869
+ raise ValueError("invalid component end: "+value)
870
+ elif comptype:
871
+ if name == "DTSTART":
872
+ # DTSTART in VTIMEZONE takes a subset of valid RRULE
873
+ # values under RFC 5545.
874
+ for parm in parms:
875
+ if parm != 'VALUE=DATE-TIME':
876
+ msg = ('Unsupported DTSTART param in ' +
877
+ 'VTIMEZONE: ' + parm)
878
+ raise ValueError(msg)
879
+ rrulelines.append(line)
880
+ founddtstart = True
881
+ elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
882
+ rrulelines.append(line)
883
+ elif name == "TZOFFSETFROM":
884
+ if parms:
885
+ raise ValueError(
886
+ "unsupported %s parm: %s " % (name, parms[0]))
887
+ tzoffsetfrom = self._parse_offset(value)
888
+ elif name == "TZOFFSETTO":
889
+ if parms:
890
+ raise ValueError(
891
+ "unsupported TZOFFSETTO parm: "+parms[0])
892
+ tzoffsetto = self._parse_offset(value)
893
+ elif name == "TZNAME":
894
+ if parms:
895
+ raise ValueError(
896
+ "unsupported TZNAME parm: "+parms[0])
897
+ tzname = value
898
+ elif name == "COMMENT":
899
+ pass
900
+ else:
901
+ raise ValueError("unsupported property: "+name)
902
+ else:
903
+ if name == "TZID":
904
+ if parms:
905
+ raise ValueError(
906
+ "unsupported TZID parm: "+parms[0])
907
+ tzid = value
908
+ elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
909
+ pass
910
+ else:
911
+ raise ValueError("unsupported property: "+name)
912
+ elif name == "BEGIN" and value == "VTIMEZONE":
913
+ tzid = None
914
+ comps = []
915
+ invtz = True
916
+
917
+ def __repr__(self):
918
+ return "%s(%s)" % (self.__class__.__name__, repr(self._s))
919
+
920
+
921
+ TZFILES = ["/etc/localtime", "localtime"]
922
+
923
+ def __get_gettz():
924
+ tzlocal_classes = (tzlocal,)
925
+ if tzwinlocal is not None:
926
+ tzlocal_classes += (tzwinlocal,)
927
+
928
+ class GettzFunc(object):
929
+ """
930
+ Retrieve a time zone object from a string representation
931
+
932
+ This function is intended to retrieve the :py:class:`tzinfo` subclass
933
+ that best represents the time zone that would be used if a POSIX
934
+ `TZ variable`_ were set to the same value.
935
+
936
+ If no argument or an empty string is passed to ``gettz``, local time
937
+ is returned:
938
+
939
+ .. code-block:: python3
940
+
941
+ >>> gettz()
942
+ tzfile('/etc/localtime')
943
+
944
+ This function is also the preferred way to map IANA tz database keys
945
+ to :class:`tzfile` objects. It searches for these keys in the
946
+ following order:
947
+
948
+ 1. On the search path defined in :data:`TZPATH`.
949
+ 2. In the `tzdata <https://pypi.org/project/tzdata/>`_ Python package.
950
+ 3. Using the system-specific mechanisms (e.g. the Windows registry).
951
+
952
+ .. code-block:: python3
953
+
954
+ >>> gettz('Pacific/Kiritimati')
955
+ tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
956
+
957
+ On Windows, the standard is extended to include the Windows-specific
958
+ zone names provided by the operating system:
959
+
960
+ .. code-block:: python3
961
+
962
+ >>> gettz('Egypt Standard Time')
963
+ tzwin('Egypt Standard Time')
964
+
965
+ Passing a GNU ``TZ`` style string time zone specification returns a
966
+ :class:`tzstr` object:
967
+
968
+ .. code-block:: python3
969
+
970
+ >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
971
+ tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
972
+
973
+ :param name:
974
+ A time zone name (IANA, or, on Windows, Windows keys), location of
975
+ a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone
976
+ specifier. An empty string, no argument or ``None`` is interpreted
977
+ as local time.
978
+
979
+ :return:
980
+ Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
981
+ subclasses.
982
+
983
+ .. versionchanged:: 2.7.0
984
+
985
+ After version 2.7.0, any two calls to ``gettz`` using the same
986
+ input strings will return the same object:
987
+
988
+ .. code-block:: python3
989
+
990
+ >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
991
+ True
992
+
993
+ In addition to improving performance, this ensures that
994
+ `"same zone" semantics`_ are used for datetimes in the same zone.
995
+
996
+
997
+ .. _`TZ variable`:
998
+ https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
999
+
1000
+ .. _`"same zone" semantics`:
1001
+ https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
1002
+ """
1003
+ def __init__(self):
1004
+
1005
+ self.__instances = weakref.WeakValueDictionary()
1006
+ self.__strong_cache_size = 8
1007
+ self.__strong_cache = OrderedDict()
1008
+ self._cache_lock = _thread.allocate_lock()
1009
+
1010
+ def __call__(self, name=None):
1011
+ with self._cache_lock:
1012
+ rv = self.__instances.get(name, None)
1013
+
1014
+ if rv is None:
1015
+ # TODO: This holds the cache lock while nocache() reads
1016
+ # the file, so a slow lookup (e.g. walking TZPATH on a
1017
+ # network file system) blocks every other gettz call,
1018
+ # including cache hits for unrelated keys.
1019
+ rv = self.nocache(name=name)
1020
+ if not (name is None
1021
+ or isinstance(rv, tzlocal_classes)
1022
+ or rv is None):
1023
+ # tzlocal is slightly more complicated than the other
1024
+ # time zone providers because it depends on environment
1025
+ # at construction time, so don't cache that.
1026
+ #
1027
+ # We also cannot store weak references to None, so we
1028
+ # will also not store that.
1029
+ self.__instances[name] = rv
1030
+ else:
1031
+ # No need for strong caching, return immediately
1032
+ return rv
1033
+
1034
+ self.__strong_cache[name] = self.__strong_cache.pop(name, rv)
1035
+
1036
+ if len(self.__strong_cache) > self.__strong_cache_size:
1037
+ self.__strong_cache.popitem(last=False)
1038
+
1039
+ return rv
1040
+
1041
+ def set_cache_size(self, size):
1042
+ with self._cache_lock:
1043
+ self.__strong_cache_size = size
1044
+ while len(self.__strong_cache) > size:
1045
+ self.__strong_cache.popitem(last=False)
1046
+
1047
+ def cache_clear(self):
1048
+ with self._cache_lock:
1049
+ self.__instances = weakref.WeakValueDictionary()
1050
+ self.__strong_cache.clear()
1051
+
1052
+ @staticmethod
1053
+ def nocache(name=None):
1054
+ """A non-cached version of gettz"""
1055
+ tz = None
1056
+ if not name:
1057
+ try:
1058
+ name = os.environ["TZ"]
1059
+ except KeyError:
1060
+ pass
1061
+ if name is None or name in ("", ":"):
1062
+ for filepath in TZFILES:
1063
+ if not os.path.isabs(filepath):
1064
+ filename = filepath
1065
+ for path in _tzpath.TZPATH:
1066
+ filepath = os.path.join(path, filename)
1067
+ if _isfile(filepath):
1068
+ break
1069
+ else:
1070
+ continue
1071
+ if _isfile(filepath):
1072
+ try:
1073
+ tz = tzfile(filepath)
1074
+ break
1075
+ except (IOError, OSError, ValueError):
1076
+ pass
1077
+ else:
1078
+ tz = tzlocal()
1079
+ else:
1080
+ try:
1081
+ if name.startswith(":"):
1082
+ name = name[1:]
1083
+ except TypeError as e:
1084
+ if isinstance(name, bytes):
1085
+ new_msg = "gettz argument should be str, not bytes"
1086
+ six.raise_from(TypeError(new_msg), e)
1087
+ else:
1088
+ raise
1089
+
1090
+ # A NUL byte can never be part of a key or a path, and
1091
+ # depending on the platform and Python version it raises
1092
+ # ValueError from os.path, winreg or open(), so reject it
1093
+ # up front rather than catching it in every branch.
1094
+ if "\x00" in name:
1095
+ return None
1096
+
1097
+ if os.path.isabs(name):
1098
+ if _isfile(name):
1099
+ tz = tzfile(name)
1100
+ else:
1101
+ tz = None
1102
+ else:
1103
+ if _escapes_tzpath(name):
1104
+ _warn_tzpath_escape(name)
1105
+
1106
+ for path in _tzpath.TZPATH:
1107
+ filepath = os.path.join(path, name)
1108
+ if not _isfile(filepath):
1109
+ filepath = filepath.replace(' ', '_')
1110
+ if not _isfile(filepath):
1111
+ continue
1112
+ try:
1113
+ tz = tzfile(filepath, key=name)
1114
+ break
1115
+ except (IOError, OSError, ValueError):
1116
+ # TODO: A file that exists but can't be read or
1117
+ # isn't a valid TZif file is silently skipped,
1118
+ # and the key is looked up in the next TZPATH
1119
+ # entry and then in tzdata. Consider warning
1120
+ # here, since this usually means the system time
1121
+ # zone data is broken.
1122
+ pass
1123
+ else:
1124
+ tz = None
1125
+ # As with the search path lookup above, also try
1126
+ # the name with spaces replaced by underscores.
1127
+ candidates = [name]
1128
+ if " " in name:
1129
+ candidates.append(name.replace(" ", "_"))
1130
+
1131
+ for candidate in candidates:
1132
+ try:
1133
+ with _tzdata_impl._load_tzdata(candidate) as f:
1134
+ return tzfile(f, key=name)
1135
+ except ValueError:
1136
+ # TZFileNotFound is a ValueError, and so is
1137
+ # the error raised for files in tzdata that
1138
+ # are not TZif files (e.g. "zone.tab"),
1139
+ # which are skipped just as on TZPATH.
1140
+ pass
1141
+
1142
+ if tzwin is not None:
1143
+ try:
1144
+ tz = tzwin(name)
1145
+ except (WindowsError, UnicodeEncodeError):
1146
+ # UnicodeEncodeError is for Python 2.7 compat
1147
+ tz = None
1148
+
1149
+ if not tz:
1150
+ for c in name:
1151
+ # name is not a tzstr unless it has at least
1152
+ # one offset. For short values of "name", an
1153
+ # explicit for loop seems to be the fastest way
1154
+ # To determine if a string contains a digit
1155
+ if c in "0123456789":
1156
+ try:
1157
+ tz = tzstr(name)
1158
+ except ValueError:
1159
+ pass
1160
+ break
1161
+ else:
1162
+ if name in ("GMT", "UTC"):
1163
+ tz = UTC
1164
+ elif name in time.tzname:
1165
+ tz = tzlocal()
1166
+ return tz
1167
+
1168
+ return GettzFunc()
1169
+
1170
+
1171
+ gettz = __get_gettz()
1172
+ del __get_gettz
1173
+
1174
+
1175
+ class DeprecatedTzKeyWarning(FutureWarning):
1176
+ """Warning raised when :func:`gettz` is given a key outside of TZPATH.
1177
+
1178
+ Relative keys like ``"../../etc/localtime"`` resolve to files outside of
1179
+ every directory on :data:`TZPATH`. Support for these is deprecated, and a
1180
+ warnings filter turns this warning into an error by default. To keep the
1181
+ old behavior for now, filter it out, e.g.::
1182
+
1183
+ warnings.filterwarnings(
1184
+ "ignore", category=dateutil.tz.DeprecatedTzKeyWarning
1185
+ )
1186
+
1187
+ Absolute paths to TZif files are not affected.
1188
+ """
1189
+
1190
+
1191
+ def _escapes_tzpath(name):
1192
+ # A relative key stays inside every TZPATH entry unless, once normalized
1193
+ # (which also turns os.altsep into os.sep), it starts by going up a
1194
+ # directory.
1195
+ return os.path.normpath(name).split(os.sep, 1)[0] == os.pardir
1196
+
1197
+
1198
+ def _warn_tzpath_escape(name):
1199
+ # As in dateutil.zoneinfo.rebuild, the filter is appended at call time,
1200
+ # so any filter the user has installed for this category wins.
1201
+ warnings.filterwarnings(
1202
+ "error", category=DeprecatedTzKeyWarning, append=True
1203
+ )
1204
+ warnings.warn(
1205
+ "gettz(%r) refers to a file outside of TZPATH; support for keys "
1206
+ "like this is deprecated and will be removed in a future version. "
1207
+ "Use an absolute path instead." % (name,),
1208
+ DeprecatedTzKeyWarning,
1209
+ stacklevel=4,
1210
+ )
1211
+
1212
+
1213
+ def available_iana_timezones():
1214
+ """Returns a set containing all available IANA time zones keys.
1215
+
1216
+ These are suitable for use with :func:`gettz`. This is essentially a direct
1217
+ port of :py:func:`zoneinfo.available_timezones`.
1218
+
1219
+ The search includes time zones found on :data:`TZPATH` as well as any
1220
+ time zones provided by the `tzdata <https://pypi.org/project/tzdata/>`_
1221
+ package if it is installed.
1222
+
1223
+ :return:
1224
+ Returns a mutable set containing strings of time zones. Each call will
1225
+ return a fresh set.
1226
+
1227
+ .. caution::
1228
+
1229
+ This may attempt to open a large number of files, since the best way to
1230
+ determine if a given file on the time zone search path is to open it
1231
+ and check for the "magic string" at the beginning.
1232
+
1233
+ .. note::
1234
+
1235
+ These values are not designed to be exposed to end-users; for
1236
+ user-facing elements, applications should use something like CLDR (the
1237
+ Unicode Common Locale Data Repository) to get more user-friendly
1238
+ strings.
1239
+ """
1240
+ # Start with loading from the tzdata package if it exists: this has a
1241
+ # pre-assembled list of zones that only requires opening one file.
1242
+ try:
1243
+ valid_zones = set(_tzdata_impl._load_tzdata_keys())
1244
+ except ImportError:
1245
+ valid_zones = set()
1246
+
1247
+ def valid_key(fpath):
1248
+ try:
1249
+ with open(fpath, "rb") as f:
1250
+ return f.read(4) == b"TZif"
1251
+ except Exception: # pragma: nocover
1252
+ return False
1253
+
1254
+ for tz_root in _tzpath.TZPATH:
1255
+ if not os.path.exists(tz_root):
1256
+ continue
1257
+
1258
+ for root, dirnames, files in os.walk(tz_root):
1259
+ if root == tz_root:
1260
+ # right/ and posix/ are special directories and shouldn't be
1261
+ # included in the output of available zones
1262
+ if "right" in dirnames:
1263
+ dirnames.remove("right")
1264
+ if "posix" in dirnames:
1265
+ dirnames.remove("posix")
1266
+
1267
+ for file in files:
1268
+ fpath = os.path.join(root, file)
1269
+
1270
+ key = os.path.relpath(fpath, start=tz_root)
1271
+ if os.sep != "/": # pragma: nocover
1272
+ key = key.replace(os.sep, "/")
1273
+
1274
+ if not key or key in valid_zones:
1275
+ continue
1276
+
1277
+ if valid_key(fpath):
1278
+ valid_zones.add(key)
1279
+
1280
+ if "posixrules" in valid_zones:
1281
+ # posixrules is a special symlink-only time zone where it exists, it
1282
+ # should not be included in the output
1283
+ valid_zones.remove("posixrules")
1284
+ if "localtime" in valid_zones:
1285
+ # localtime is a special symlink-only time zone where it exists, it
1286
+ # should not be included in the output
1287
+ valid_zones.remove("localtime")
1288
+
1289
+ return valid_zones
1290
+
1291
+
1292
+ def datetime_exists(dt, tz=None):
1293
+ """
1294
+ Given a datetime and a time zone, determine whether or not a given datetime
1295
+ would fall in a gap.
1296
+
1297
+ :param dt:
1298
+ A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
1299
+ is provided.)
1300
+
1301
+ :param tz:
1302
+ A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
1303
+ ``None`` or not provided, the datetime's own time zone will be used.
1304
+
1305
+ :return:
1306
+ Returns a boolean value whether or not the "wall time" exists in
1307
+ ``tz``.
1308
+
1309
+ .. versionadded:: 2.7.0
1310
+ """
1311
+ if tz is None:
1312
+ if dt.tzinfo is None:
1313
+ raise ValueError('Datetime is naive and no time zone provided.')
1314
+ tz = dt.tzinfo
1315
+
1316
+ dt = dt.replace(tzinfo=None)
1317
+
1318
+ # This is essentially a test of whether or not the datetime can survive
1319
+ # a round trip to UTC.
1320
+ dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz)
1321
+ dt_rt = dt_rt.replace(tzinfo=None)
1322
+
1323
+ return dt == dt_rt
1324
+
1325
+
1326
+ def datetime_ambiguous(dt, tz=None):
1327
+ """
1328
+ Given a datetime and a time zone, determine whether or not a given datetime
1329
+ is ambiguous (i.e if there are two times differentiated only by their DST
1330
+ status).
1331
+
1332
+ :param dt:
1333
+ A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
1334
+ is provided.)
1335
+
1336
+ :param tz:
1337
+ A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
1338
+ ``None`` or not provided, the datetime's own time zone will be used.
1339
+
1340
+ :return:
1341
+ Returns a boolean value whether or not the "wall time" is ambiguous in
1342
+ ``tz``.
1343
+
1344
+ .. versionadded:: 2.6.0
1345
+ """
1346
+ if tz is None:
1347
+ if dt.tzinfo is None:
1348
+ raise ValueError('Datetime is naive and no time zone provided.')
1349
+
1350
+ tz = dt.tzinfo
1351
+
1352
+ # If a time zone defines its own "is_ambiguous" function, we'll use that.
1353
+ is_ambiguous_fn = getattr(tz, 'is_ambiguous', None)
1354
+ if is_ambiguous_fn is not None:
1355
+ try:
1356
+ return tz.is_ambiguous(dt)
1357
+ except Exception:
1358
+ pass
1359
+
1360
+ # If it doesn't come out and tell us it's ambiguous, we'll just check if
1361
+ # the fold attribute has any effect on this particular date and time.
1362
+ dt = dt.replace(tzinfo=tz)
1363
+ wall_0 = enfold(dt, fold=0)
1364
+ wall_1 = enfold(dt, fold=1)
1365
+
1366
+ same_offset = wall_0.utcoffset() == wall_1.utcoffset()
1367
+ same_dst = wall_0.dst() == wall_1.dst()
1368
+
1369
+ return not (same_offset and same_dst)
1370
+
1371
+
1372
+ def resolve_imaginary(dt):
1373
+ """
1374
+ Given a datetime that may be imaginary, return an existing datetime.
1375
+
1376
+ This function assumes that an imaginary datetime represents what the
1377
+ wall time would be in a zone had the offset transition not occurred, so
1378
+ it will always fall forward by the transition's change in offset.
1379
+
1380
+ .. doctest::
1381
+
1382
+ >>> from dateutil import tz
1383
+ >>> from datetime import datetime
1384
+ >>> NYC = tz.gettz('America/New_York')
1385
+ >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC)))
1386
+ 2017-03-12 03:30:00-04:00
1387
+
1388
+ >>> KIR = tz.gettz('Pacific/Kiritimati')
1389
+ >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR)))
1390
+ 1995-01-02 12:30:00+14:00
1391
+
1392
+ As a note, :func:`datetime.astimezone` is guaranteed to produce a valid,
1393
+ existing datetime, so a round-trip to and from UTC is sufficient to get
1394
+ an extant datetime, however, this generally "falls back" to an earlier time
1395
+ rather than falling forward to the STD side (though no guarantees are made
1396
+ about this behavior).
1397
+
1398
+ :param dt:
1399
+ A :class:`datetime.datetime` which may or may not exist.
1400
+
1401
+ :return:
1402
+ Returns an existing :class:`datetime.datetime`. If ``dt`` was not
1403
+ imaginary, the datetime returned is guaranteed to be the same object
1404
+ passed to the function.
1405
+
1406
+ .. versionadded:: 2.7.0
1407
+ """
1408
+ if dt.tzinfo is not None and not datetime_exists(dt):
1409
+
1410
+ curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset()
1411
+ old_offset = (dt - datetime.timedelta(hours=24)).utcoffset()
1412
+
1413
+ dt += curr_offset - old_offset
1414
+
1415
+ return dt
1416
+
1417
+
1418
+ def _datetime_to_timestamp(dt):
1419
+ """
1420
+ Convert a :class:`datetime.datetime` object to an epoch timestamp in
1421
+ seconds since January 1, 1970, ignoring the time zone.
1422
+ """
1423
+ return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
1424
+
1425
+
1426
+ if sys.version_info >= (3, 6):
1427
+ def _get_supported_offset(second_offset):
1428
+ return second_offset
1429
+ else:
1430
+ def _get_supported_offset(second_offset):
1431
+ # For python pre-3.6, round to full-minutes if that's not the case.
1432
+ # Python's datetime doesn't accept sub-minute timezones. Check
1433
+ # http://python.org/sf/1447945 or https://bugs.python.org/issue5288
1434
+ # for some information.
1435
+ old_offset = second_offset
1436
+ calculated_offset = 60 * ((second_offset + 30) // 60)
1437
+ return calculated_offset
1438
+
1439
+
1440
+ if sys.version_info < (3, 8):
1441
+
1442
+ def _isfile(path):
1443
+ # bpo-33721: In Python 3.8 non-UTF8 paths return False rather than
1444
+ # raising an error. See https://bugs.python.org/issue33721
1445
+ try:
1446
+ return os.path.isfile(path)
1447
+ except ValueError:
1448
+ return False
1449
+
1450
+ else:
1451
+ _isfile = os.path.isfile
1452
+
1453
+
1454
+ try:
1455
+ # Python 3.7 feature
1456
+ from contextlib import nullcontext as _nullcontext
1457
+ except ImportError:
1458
+ class _nullcontext(object):
1459
+ """
1460
+ Class for wrapping contexts so that they are passed through in a
1461
+ with statement.
1462
+ """
1463
+ def __init__(self, context):
1464
+ self.context = context
1465
+
1466
+ def __enter__(self):
1467
+ return self.context
1468
+
1469
+ def __exit__(*args, **kwargs):
1470
+ pass
1471
+
1472
+ # vim:ts=4:sw=4:et
hug/lib/python3.12/site-packages/dateutil/tz/win.py ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ This module provides an interface to the native time zone data on Windows,
4
+ including :py:class:`datetime.tzinfo` implementations.
5
+
6
+ Attempting to import this module on a non-Windows platform will raise an
7
+ :py:obj:`ImportError`.
8
+ """
9
+ # This code was originally contributed by Jeffrey Harris.
10
+ import datetime
11
+ import struct
12
+
13
+ from six.moves import winreg
14
+ from six import text_type
15
+
16
+ try:
17
+ import ctypes
18
+ from ctypes import wintypes
19
+ except ValueError:
20
+ # ValueError is raised on non-Windows systems for some horrible reason.
21
+ raise ImportError("Running tzwin on non-Windows system")
22
+
23
+ from ._common import tzrangebase
24
+
25
+ __all__ = ["tzwin", "tzwinlocal", "tzres"]
26
+
27
+ ONEWEEK = datetime.timedelta(7)
28
+
29
+ TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
30
+ TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
31
+ TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
32
+
33
+
34
+ def _settzkeyname():
35
+ handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
36
+ try:
37
+ winreg.OpenKey(handle, TZKEYNAMENT).Close()
38
+ TZKEYNAME = TZKEYNAMENT
39
+ except WindowsError:
40
+ TZKEYNAME = TZKEYNAME9X
41
+ handle.Close()
42
+ return TZKEYNAME
43
+
44
+
45
+ TZKEYNAME = _settzkeyname()
46
+
47
+
48
+ class tzres(object):
49
+ """
50
+ Class for accessing ``tzres.dll``, which contains timezone name related
51
+ resources.
52
+
53
+ .. versionadded:: 2.5.0
54
+ """
55
+ p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char
56
+
57
+ def __init__(self, tzres_loc='tzres.dll'):
58
+ # Load the user32 DLL so we can load strings from tzres
59
+ user32 = ctypes.WinDLL('user32')
60
+
61
+ # Specify the LoadStringW function
62
+ user32.LoadStringW.argtypes = (wintypes.HINSTANCE,
63
+ wintypes.UINT,
64
+ wintypes.LPWSTR,
65
+ ctypes.c_int)
66
+
67
+ self.LoadStringW = user32.LoadStringW
68
+ self._tzres = ctypes.WinDLL(tzres_loc)
69
+ self.tzres_loc = tzres_loc
70
+
71
+ def load_name(self, offset):
72
+ """
73
+ Load a timezone name from a DLL offset (integer).
74
+
75
+ >>> from dateutil.tzwin import tzres
76
+ >>> tzr = tzres()
77
+ >>> print(tzr.load_name(112))
78
+ 'Eastern Standard Time'
79
+
80
+ :param offset:
81
+ A positive integer value referring to a string from the tzres dll.
82
+
83
+ .. note::
84
+
85
+ Offsets found in the registry are generally of the form
86
+ ``@tzres.dll,-114``. The offset in this case is 114, not -114.
87
+
88
+ """
89
+ resource = self.p_wchar()
90
+ lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR)
91
+ nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0)
92
+ return resource[:nchar]
93
+
94
+ def name_from_string(self, tzname_str):
95
+ """
96
+ Parse strings as returned from the Windows registry into the time zone
97
+ name as defined in the registry.
98
+
99
+ >>> from dateutil.tzwin import tzres
100
+ >>> tzr = tzres()
101
+ >>> print(tzr.name_from_string('@tzres.dll,-251'))
102
+ 'Dateline Daylight Time'
103
+ >>> print(tzr.name_from_string('Eastern Standard Time'))
104
+ 'Eastern Standard Time'
105
+
106
+ :param tzname_str:
107
+ A timezone name string as returned from a Windows registry key.
108
+
109
+ :return:
110
+ Returns the localized timezone string from tzres.dll if the string
111
+ is of the form `@tzres.dll,-offset`, else returns the input string.
112
+ """
113
+ if not tzname_str.startswith('@'):
114
+ return tzname_str
115
+
116
+ name_splt = tzname_str.split(',-')
117
+ try:
118
+ offset = int(name_splt[1])
119
+ except:
120
+ raise ValueError("Malformed timezone string.")
121
+
122
+ return self.load_name(offset)
123
+
124
+
125
+ class tzwinbase(tzrangebase):
126
+ """tzinfo class based on win32's timezones available in the registry."""
127
+ def __init__(self):
128
+ raise NotImplementedError('tzwinbase is an abstract base class')
129
+
130
+ def __eq__(self, other):
131
+ # Compare on all relevant dimensions, including name.
132
+ if not isinstance(other, tzwinbase):
133
+ return NotImplemented
134
+
135
+ return (self._std_offset == other._std_offset and
136
+ self._dst_offset == other._dst_offset and
137
+ self._stddayofweek == other._stddayofweek and
138
+ self._dstdayofweek == other._dstdayofweek and
139
+ self._stdweeknumber == other._stdweeknumber and
140
+ self._dstweeknumber == other._dstweeknumber and
141
+ self._stdhour == other._stdhour and
142
+ self._dsthour == other._dsthour and
143
+ self._stdminute == other._stdminute and
144
+ self._dstminute == other._dstminute and
145
+ self._std_abbr == other._std_abbr and
146
+ self._dst_abbr == other._dst_abbr)
147
+
148
+ @staticmethod
149
+ def list():
150
+ """Return a list of all time zones known to the system."""
151
+ with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
152
+ with winreg.OpenKey(handle, TZKEYNAME) as tzkey:
153
+ result = [winreg.EnumKey(tzkey, i)
154
+ for i in range(winreg.QueryInfoKey(tzkey)[0])]
155
+ return result
156
+
157
+ def display(self):
158
+ """
159
+ Return the display name of the time zone.
160
+ """
161
+ return self._display
162
+
163
+ def transitions(self, year):
164
+ """
165
+ For a given year, get the DST on and off transition times, expressed
166
+ always on the standard time side. For zones with no transitions, this
167
+ function returns ``None``.
168
+
169
+ :param year:
170
+ The year whose transitions you would like to query.
171
+
172
+ :return:
173
+ Returns a :class:`tuple` of :class:`datetime.datetime` objects,
174
+ ``(dston, dstoff)`` for zones with an annual DST transition, or
175
+ ``None`` for fixed offset zones.
176
+ """
177
+
178
+ if not self.hasdst:
179
+ return None
180
+
181
+ dston = picknthweekday(year, self._dstmonth, self._dstdayofweek,
182
+ self._dsthour, self._dstminute,
183
+ self._dstweeknumber)
184
+
185
+ dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek,
186
+ self._stdhour, self._stdminute,
187
+ self._stdweeknumber)
188
+
189
+ # Ambiguous dates default to the STD side
190
+ dstoff -= self._dst_base_offset
191
+
192
+ return dston, dstoff
193
+
194
+ def _get_hasdst(self):
195
+ return self._dstmonth != 0
196
+
197
+ @property
198
+ def _dst_base_offset(self):
199
+ return self._dst_base_offset_
200
+
201
+
202
+ class tzwin(tzwinbase):
203
+ """
204
+ Time zone object created from the zone info in the Windows registry
205
+
206
+ These are similar to :py:class:`dateutil.tz.tzrange` objects in that
207
+ the time zone data is provided in the format of a single offset rule
208
+ for either 0 or 2 time zone transitions per year.
209
+
210
+ :param: name
211
+ The name of a Windows time zone key, e.g. "Eastern Standard Time".
212
+ The full list of keys can be retrieved with :func:`tzwin.list`.
213
+ """
214
+
215
+ def __init__(self, name):
216
+ self._name = name
217
+
218
+ with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
219
+ tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name)
220
+ with winreg.OpenKey(handle, tzkeyname) as tzkey:
221
+ keydict = valuestodict(tzkey)
222
+
223
+ self._std_abbr = keydict["Std"]
224
+ self._dst_abbr = keydict["Dlt"]
225
+
226
+ self._display = keydict["Display"]
227
+
228
+ # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
229
+ tup = struct.unpack("=3l16h", keydict["TZI"])
230
+ stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
231
+ dstoffset = stdoffset-tup[2] # + DaylightBias * -1
232
+ self._std_offset = datetime.timedelta(minutes=stdoffset)
233
+ self._dst_offset = datetime.timedelta(minutes=dstoffset)
234
+
235
+ # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs
236
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx
237
+ (self._stdmonth,
238
+ self._stddayofweek, # Sunday = 0
239
+ self._stdweeknumber, # Last = 5
240
+ self._stdhour,
241
+ self._stdminute) = tup[4:9]
242
+
243
+ (self._dstmonth,
244
+ self._dstdayofweek, # Sunday = 0
245
+ self._dstweeknumber, # Last = 5
246
+ self._dsthour,
247
+ self._dstminute) = tup[12:17]
248
+
249
+ self._dst_base_offset_ = self._dst_offset - self._std_offset
250
+ self.hasdst = self._get_hasdst()
251
+
252
+ def __repr__(self):
253
+ return "tzwin(%s)" % repr(self._name)
254
+
255
+ def __reduce__(self):
256
+ return (self.__class__, (self._name,))
257
+
258
+
259
+ class tzwinlocal(tzwinbase):
260
+ """
261
+ Class representing the local time zone information in the Windows registry
262
+
263
+ While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time`
264
+ module) to retrieve time zone information, ``tzwinlocal`` retrieves the
265
+ rules directly from the Windows registry and creates an object like
266
+ :class:`dateutil.tz.tzwin`.
267
+
268
+ Because Windows does not have an equivalent of :func:`time.tzset`, on
269
+ Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the
270
+ time zone settings *at the time that the process was started*, meaning
271
+ changes to the machine's time zone settings during the run of a program
272
+ on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`.
273
+ Because ``tzwinlocal`` reads the registry directly, it is unaffected by
274
+ this issue.
275
+ """
276
+ def __init__(self):
277
+ with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle:
278
+ with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey:
279
+ keydict = valuestodict(tzlocalkey)
280
+
281
+ self._std_abbr = keydict["StandardName"]
282
+ self._dst_abbr = keydict["DaylightName"]
283
+
284
+ try:
285
+ tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME,
286
+ sn=self._std_abbr)
287
+ with winreg.OpenKey(handle, tzkeyname) as tzkey:
288
+ _keydict = valuestodict(tzkey)
289
+ self._display = _keydict["Display"]
290
+ except OSError:
291
+ self._display = None
292
+
293
+ stdoffset = -keydict["Bias"]-keydict["StandardBias"]
294
+ dstoffset = stdoffset-keydict["DaylightBias"]
295
+
296
+ self._std_offset = datetime.timedelta(minutes=stdoffset)
297
+ self._dst_offset = datetime.timedelta(minutes=dstoffset)
298
+
299
+ # For reasons unclear, in this particular key, the day of week has been
300
+ # moved to the END of the SYSTEMTIME structure.
301
+ tup = struct.unpack("=8h", keydict["StandardStart"])
302
+
303
+ (self._stdmonth,
304
+ self._stdweeknumber, # Last = 5
305
+ self._stdhour,
306
+ self._stdminute) = tup[1:5]
307
+
308
+ self._stddayofweek = tup[7]
309
+
310
+ tup = struct.unpack("=8h", keydict["DaylightStart"])
311
+
312
+ (self._dstmonth,
313
+ self._dstweeknumber, # Last = 5
314
+ self._dsthour,
315
+ self._dstminute) = tup[1:5]
316
+
317
+ self._dstdayofweek = tup[7]
318
+
319
+ self._dst_base_offset_ = self._dst_offset - self._std_offset
320
+ self.hasdst = self._get_hasdst()
321
+
322
+ def __repr__(self):
323
+ return "tzwinlocal()"
324
+
325
+ def __str__(self):
326
+ # str will return the standard name, not the daylight name.
327
+ return "tzwinlocal(%s)" % repr(self._std_abbr)
328
+
329
+ def __reduce__(self):
330
+ return (self.__class__, ())
331
+
332
+
333
+ def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
334
+ """ dayofweek == 0 means Sunday, whichweek 5 means last instance """
335
+ first = datetime.datetime(year, month, 1, hour, minute)
336
+
337
+ # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6),
338
+ # Because 7 % 7 = 0
339
+ weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1)
340
+ wd = weekdayone + ((whichweek - 1) * ONEWEEK)
341
+ if (wd.month != month):
342
+ wd -= ONEWEEK
343
+
344
+ return wd
345
+
346
+
347
+ def valuestodict(key):
348
+ """Convert a registry key's values to a dictionary."""
349
+ dout = {}
350
+ size = winreg.QueryInfoKey(key)[1]
351
+ tz_res = None
352
+
353
+ for i in range(size):
354
+ key_name, value, dtype = winreg.EnumValue(key, i)
355
+ if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN:
356
+ # If it's a DWORD (32-bit integer), it's stored as unsigned - convert
357
+ # that to a proper signed integer
358
+ if value & (1 << 31):
359
+ value = value - (1 << 32)
360
+ elif dtype == winreg.REG_SZ:
361
+ # If it's a reference to the tzres DLL, load the actual string
362
+ if value.startswith('@tzres'):
363
+ tz_res = tz_res or tzres()
364
+ value = tz_res.name_from_string(value)
365
+
366
+ value = value.rstrip('\x00') # Remove trailing nulls
367
+
368
+ dout[key_name] = value
369
+
370
+ return dout
hug/lib/python3.12/site-packages/dateutil/tzwin.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # tzwin has moved to dateutil.tz.win
2
+ from .tz.win import *
hug/lib/python3.12/site-packages/dateutil/utils.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ This module offers general convenience and utility functions for dealing with
4
+ datetimes.
5
+
6
+ .. versionadded:: 2.7.0
7
+ """
8
+ from __future__ import unicode_literals
9
+
10
+ from datetime import datetime, time
11
+
12
+
13
+ def today(tzinfo=None):
14
+ """
15
+ Returns a :py:class:`datetime` representing the current day at midnight
16
+
17
+ :param tzinfo:
18
+ The time zone to attach (also used to determine the current day).
19
+
20
+ :return:
21
+ A :py:class:`datetime.datetime` object representing the current day
22
+ at midnight.
23
+ """
24
+
25
+ dt = datetime.now(tzinfo)
26
+ return datetime.combine(dt.date(), time(0, tzinfo=tzinfo))
27
+
28
+
29
+ def default_tzinfo(dt, tzinfo):
30
+ """
31
+ Sets the ``tzinfo`` parameter on naive datetimes only
32
+
33
+ This is useful for example when you are provided a datetime that may have
34
+ either an implicit or explicit time zone, such as when parsing a time zone
35
+ string.
36
+
37
+ .. doctest::
38
+
39
+ >>> from dateutil.tz import tzoffset
40
+ >>> from dateutil.parser import parse
41
+ >>> from dateutil.utils import default_tzinfo
42
+ >>> dflt_tz = tzoffset("EST", -18000)
43
+ >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz))
44
+ 2014-01-01 12:30:00+00:00
45
+ >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz))
46
+ 2014-01-01 12:30:00-05:00
47
+
48
+ :param dt:
49
+ The datetime on which to replace the time zone
50
+
51
+ :param tzinfo:
52
+ The :py:class:`datetime.tzinfo` subclass instance to assign to
53
+ ``dt`` if (and only if) it is naive.
54
+
55
+ :return:
56
+ Returns an aware :py:class:`datetime.datetime`.
57
+ """
58
+ if dt.tzinfo is not None:
59
+ return dt
60
+ else:
61
+ return dt.replace(tzinfo=tzinfo)
62
+
63
+
64
+ def within_delta(dt1, dt2, delta):
65
+ """
66
+ Useful for comparing two datetimes that may have a negligible difference
67
+ to be considered equal.
68
+ """
69
+ delta = abs(delta)
70
+ difference = dt1 - dt2
71
+ return -delta <= difference <= delta
hug/lib/python3.12/site-packages/dateutil/zoneinfo/__init__.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ The ``dateutil.zoneinfo`` module was originally designed to work with a tarball
4
+ shipped with ``dateutil``. Since ``dateutil`` now uses data from the same
5
+ sources as :py:mod:`zoneinfo`, this module is no longer necessary.
6
+
7
+ .. caution::
8
+ This module is deprecated, and its functionality has been dramatically
9
+ curtailed. It is recommended that you adopt the standard library module
10
+ :py:mod:`zoneinfo` or :func:`dateutil.tz.gettz` instead.
11
+
12
+ """
13
+
14
+ import json
15
+ import sys
16
+ import warnings
17
+ from tarfile import TarFile
18
+
19
+ import six
20
+
21
+ from dateutil import _tzdata_impl
22
+ from dateutil.tz import tzfile as _tzfile
23
+
24
+ __all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]
25
+
26
+ ZONEFILENAME = None
27
+ METADATA_FN = 'METADATA'
28
+
29
+
30
+ def _importer_stacklevel():
31
+ """The ``stacklevel`` that attributes a warning to the importing code.
32
+
33
+ This module can be imported through ``dateutil.__getattr__`` (e.g. by
34
+ ``from dateutil import zoneinfo``), which calls
35
+ ``importlib.import_module``; the warnings machinery skips the import
36
+ system's own frames, but not those two, so skip them here.
37
+ """
38
+ level = 1
39
+ frame = sys._getframe(1) # The body of this module
40
+ while frame.f_back is not None:
41
+ frame = frame.f_back
42
+ filename = frame.f_code.co_filename
43
+ if "importlib" in filename and "_bootstrap" in filename:
44
+ # Since Python 3.5, the warnings machinery skips these frames
45
+ # without counting them; before that they count like any other.
46
+ if sys.version_info < (3, 5):
47
+ level += 1
48
+ continue
49
+
50
+ level += 1
51
+ if frame.f_globals.get("__name__") not in ("importlib", "dateutil"):
52
+ return level
53
+
54
+ return 2
55
+
56
+
57
+ warnings.warn(
58
+ "The `dateutil.zoneinfo` module has been replaced with a wrapper around "
59
+ "the tzdata package, and its use is deprecated, to be removed in a future "
60
+ "version. Use the standard library module `zoneinfo` or `dateutil.tz` "
61
+ "instead.",
62
+ DeprecationWarning,
63
+ stacklevel=_importer_stacklevel(),
64
+ )
65
+
66
+
67
+ tzfile = _tzfile
68
+
69
+ class ZoneInfoFile(object):
70
+ def __init__(self, zonefile_stream=None):
71
+ if zonefile_stream is not None:
72
+ self._load_legacy_zonefile_stream(zonefile_stream)
73
+ else:
74
+ self._eager_load_tzdata()
75
+
76
+ def _eager_load_tzdata(self):
77
+ try:
78
+ import tzdata
79
+ except ImportError as e:
80
+ six.raise_from(
81
+ ImportError(
82
+ "The tzdata module is required to use ZoneInfoFile; either add "
83
+ "a dependency on tzdata or migrate away from dateutil.zoneinfo."
84
+ ),
85
+ e,
86
+ )
87
+
88
+ zone_dict = {}
89
+
90
+ for key in _tzdata_impl._load_tzdata_keys():
91
+ with _tzdata_impl._load_tzdata(key) as f:
92
+ zone_dict[key] = tzfile(f, key=key)
93
+
94
+ self.zones = zone_dict
95
+ self.metadata = {
96
+ "metadata_version": "3.0",
97
+ "tzversion": tzdata.IANA_VERSION,
98
+ }
99
+
100
+ def _load_legacy_zonefile_stream(self, zonefile_stream):
101
+ with TarFile.open(fileobj=zonefile_stream) as tf:
102
+ self.zones = {
103
+ zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
104
+ for zf in tf.getmembers()
105
+ if zf.isfile() and zf.name != METADATA_FN
106
+ }
107
+ # deal with links: They'll point to their parent object. Less
108
+ # waste of memory
109
+ links = {
110
+ zl.name: self.zones[zl.linkname]
111
+ for zl in tf.getmembers()
112
+ if zl.islnk() or zl.issym()
113
+ }
114
+ self.zones.update(links)
115
+ try:
116
+ metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
117
+ metadata_str = metadata_json.read().decode("UTF-8")
118
+ self.metadata = json.loads(metadata_str)
119
+ except KeyError:
120
+ # no metadata in tar file
121
+ self.metadata = None
122
+
123
+ def get(self, name, default=None):
124
+ """
125
+ Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
126
+ for retrieving zones from the zone dictionary.
127
+
128
+ :param name:
129
+ The name of the zone to retrieve. (Generally IANA zone names)
130
+
131
+ :param default:
132
+ The value to return in the event of a missing key.
133
+
134
+ .. versionadded:: 2.6.0
135
+
136
+ """
137
+ return self.zones.get(name, default)
138
+
139
+
140
+ # The current API has gettz as a module function, although in fact it taps into
141
+ # a stateful class. So as a workaround for now, without changing the API, we
142
+ # will create a new "global" class instance the first time a user requests a
143
+ # timezone. Ugly, but adheres to the api.
144
+ #
145
+ # TODO: Remove after deprecation period.
146
+ _CLASS_ZONE_INSTANCE = []
147
+
148
+
149
+ def get_zonefile_instance(new_instance=False):
150
+ """
151
+ This is a convenience function which provides a :class:`ZoneInfoFile`
152
+ instance using the data provided by the ``dateutil`` package. By default, it
153
+ caches a single instance of the ZoneInfoFile object and returns that.
154
+
155
+ :param new_instance:
156
+ If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
157
+ used as the cached instance for the next call. Otherwise, new instances
158
+ are created only as necessary.
159
+
160
+ :return:
161
+ Returns a :class:`ZoneInfoFile` object.
162
+
163
+ .. versionadded:: 2.6
164
+ """
165
+ if new_instance:
166
+ zif = None
167
+ else:
168
+ zif = getattr(get_zonefile_instance, '_cached_instance', None)
169
+
170
+ if zif is None:
171
+ zif = ZoneInfoFile()
172
+
173
+ get_zonefile_instance._cached_instance = zif
174
+
175
+ return zif
176
+
177
+
178
+ def gettz(name):
179
+ """
180
+ This retrieves a time zone from the ``tzdata`` package.
181
+
182
+ :param name:
183
+ An IANA-style time zone name, as found in the zoneinfo file.
184
+
185
+ :return:
186
+ Returns a :class:`dateutil.tz.tzfile` time zone object.
187
+
188
+ .. warning::
189
+ It is generally inadvisable to use this function, and it is only
190
+ provided for API compatibility with earlier versions. This is *not*
191
+ equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
192
+ time zone based on the inputs, favoring system zoneinfo. This is ONLY
193
+ for accessing the dateutil-specific zoneinfo (which may be out of
194
+ date compared to the system zoneinfo).
195
+
196
+ .. deprecated:: 2.6
197
+ If you need to use a specific zoneinfofile over the system zoneinfo,
198
+ instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
199
+ :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
200
+
201
+ Use :func:`get_zonefile_instance` to retrieve an instance of the
202
+ dateutil-provided zoneinfo.
203
+ """
204
+ warnings.warn("zoneinfo.gettz() will be removed in future versions, "
205
+ "to use the dateutil-provided zoneinfo files, instantiate a "
206
+ "ZoneInfoFile object and use ZoneInfoFile.zones.get() "
207
+ "instead. See the documentation for details.",
208
+ DeprecationWarning)
209
+
210
+ if len(_CLASS_ZONE_INSTANCE) == 0:
211
+ _CLASS_ZONE_INSTANCE.append(ZoneInfoFile())
212
+ return _CLASS_ZONE_INSTANCE[0].zones.get(name)
213
+
214
+
215
+ def gettz_db_metadata():
216
+ """Get the zonefile metadata
217
+
218
+ :returns:
219
+ A dictionary with the database metadata
220
+
221
+ .. deprecated:: 2.6
222
+ See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
223
+ query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
224
+ """
225
+ warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
226
+ "versions, to use the dateutil-provided zoneinfo files, "
227
+ "ZoneInfoFile object and query the 'metadata' attribute "
228
+ "instead. See the documentation for details.",
229
+ DeprecationWarning)
230
+
231
+ if len(_CLASS_ZONE_INSTANCE) == 0:
232
+ _CLASS_ZONE_INSTANCE.append(ZoneInfoFile())
233
+ return _CLASS_ZONE_INSTANCE[0].metadata
hug/lib/python3.12/site-packages/dateutil/zoneinfo/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (9.5 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/zoneinfo/__pycache__/rebuild.cpython-312.pyc ADDED
Binary file (1.8 kB). View file
 
hug/lib/python3.12/site-packages/dateutil/zoneinfo/rebuild.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import warnings
2
+
3
+ __all__ = ["rebuild", "RebuildDeprecationWarning"]
4
+
5
+
6
+ class RebuildDeprecationWarning(FutureWarning):
7
+ """Warning raised by :func:`rebuild`, which no longer does anything.
8
+
9
+ A warnings filter turns this into an error by default. To keep calling
10
+ :func:`rebuild` as a no-op, filter it out, e.g.::
11
+
12
+ warnings.filterwarnings(
13
+ "ignore", category=dateutil.zoneinfo.rebuild.RebuildDeprecationWarning
14
+ )
15
+ """
16
+
17
+
18
+ def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
19
+ """Formerly rebuilt the zoneinfo tarball bundled with ``dateutil``.
20
+
21
+ ``dateutil`` no longer ships a zoneinfo tarball; it reads time zone data
22
+ from the system and from the `tzdata <https://pypi.org/project/tzdata/>`_
23
+ package instead. This function now does nothing except raise
24
+ :class:`RebuildDeprecationWarning`, which is an error unless a warnings
25
+ filter says otherwise.
26
+
27
+ .. deprecated:: 3.0.0
28
+ """
29
+ # The filter is added at call time rather than at import time, so that it
30
+ # is still in place if the module was first imported inside a
31
+ # warnings.catch_warnings() block. It is appended, so any filter the user
32
+ # has installed for this category takes precedence.
33
+ warnings.filterwarnings(
34
+ "error", category=RebuildDeprecationWarning, append=True
35
+ )
36
+ warnings.warn(
37
+ "dateutil.zoneinfo.rebuild() no longer does anything, because dateutil "
38
+ "no longer ships a zoneinfo tarball. Use the tzdata package or the "
39
+ "system time zone data instead.",
40
+ RebuildDeprecationWarning,
41
+ stacklevel=2,
42
+ )
hug/lib/python3.12/site-packages/distutils-precedence.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2638ce9e2500e572a5e0de7faed6661eb569d1b696fcba07b0dd223da5f5d224
3
+ size 151
hug/lib/python3.12/site-packages/example.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #################################################################################
2
+ # Copyright (c) 2020, NVIDIA Corporation. All rights reserved. #
3
+ # #
4
+ # Redistribution and use in source and binary forms, with or without #
5
+ # modification, are permitted provided that the following conditions are met: #
6
+ # #
7
+ # * Redistributions of source code must retain the above copyright notice, #
8
+ # this list of conditions and the following disclaimer. #
9
+ # * Redistributions in binary form must reproduce the above copyright #
10
+ # notice, this list of conditions and the following disclaimer in the #
11
+ # documentation and/or other materials provided with the distribution. #
12
+ # * Neither the name of the NVIDIA Corporation nor the names of its #
13
+ # contributors may be used to endorse or promote products derived from #
14
+ # this software without specific prior written permission. #
15
+ # #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" #
17
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE #
18
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE #
19
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE #
20
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR #
21
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF #
22
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS #
23
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN #
24
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) #
25
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF #
26
+ # THE POSSIBILITY OF SUCH DAMAGE. #
27
+ #################################################################################
28
+
29
+ #
30
+ # Sample script to demonstrate the usage of NVML API python bindings
31
+ #
32
+
33
+ # To Run:
34
+ # $ python ./example.py
35
+
36
+ from pynvml import *
37
+
38
+ #
39
+ # Helper function
40
+ #
41
+ def StrVirt(mode):
42
+ if mode == NVML_GPU_VIRTUALIZATION_MODE_NONE:
43
+ return "None";
44
+ elif mode == NVML_GPU_VIRTUALIZATION_MODE_PASSTHROUGH:
45
+ return "Pass-Through";
46
+ elif mode == NVML_GPU_VIRTUALIZATION_MODE_VGPU:
47
+ return "VGPU";
48
+ elif mode == NVML_GPU_VIRTUALIZATION_MODE_HOST_VGPU:
49
+ return "Host VGPU";
50
+ elif mode == NVML_GPU_VIRTUALIZATION_MODE_HOST_VSGA:
51
+ return "Host VSGA";
52
+ else:
53
+ return "Unknown";
54
+
55
+ #
56
+ # Converts errors into string messages
57
+ #
58
+ def handleError(err):
59
+ if (err.value == NVML_ERROR_NOT_SUPPORTED):
60
+ return "N/A"
61
+ else:
62
+ return err.__str__()
63
+
64
+ #######
65
+ def deviceQuery():
66
+
67
+ strResult = ''
68
+ try:
69
+ #
70
+ # Initialize NVML
71
+ #
72
+ nvmlInit()
73
+
74
+ strResult += ' <driver_version>' + str(nvmlSystemGetDriverVersion()) + '</driver_version>\n'
75
+
76
+ deviceCount = nvmlDeviceGetCount()
77
+ strResult += ' <attached_gpus>' + str(deviceCount) + '</attached_gpus>\n'
78
+
79
+ for i in range(0, deviceCount):
80
+ handle = nvmlDeviceGetHandleByIndex(i)
81
+
82
+ pciInfo = nvmlDeviceGetPciInfo(handle)
83
+
84
+ strResult += ' <gpu id="%s">\n' % pciInfo.busId
85
+
86
+ strResult += ' <product_name>' + nvmlDeviceGetName(handle) + '</product_name>\n'
87
+
88
+ brandNames = {NVML_BRAND_UNKNOWN : "Unknown",
89
+ NVML_BRAND_QUADRO : "Quadro",
90
+ NVML_BRAND_TESLA : "Tesla",
91
+ NVML_BRAND_NVS : "NVS",
92
+ NVML_BRAND_GRID : "Grid",
93
+ NVML_BRAND_TITAN : "Titan",
94
+ NVML_BRAND_GEFORCE : "GeForce",
95
+ NVML_BRAND_NVIDIA_VAPPS : "NVIDIA Virtual Applications",
96
+ NVML_BRAND_NVIDIA_VPC : "NVIDIA Virtual PC",
97
+ NVML_BRAND_NVIDIA_VCS : "NVIDIA vGPU for Compute",
98
+ NVML_BRAND_NVIDIA_VWS : "NVIDIA RTX Virtual Workstation",
99
+ NVML_BRAND_NVIDIA_CLOUD_GAMING : "NVIDIA Cloud Gaming",
100
+ NVML_BRAND_NVIDIA_VGAMEDEV : "NVIDIA RTX Virtual Game Dev",
101
+ NVML_BRAND_QUADRO_RTX : "Quadro RTX",
102
+ NVML_BRAND_NVIDIA_RTX : "NVIDIA RTX",
103
+ NVML_BRAND_NVIDIA : "NVIDIA",
104
+ NVML_BRAND_GEFORCE_RTX : "GeForce RTX",
105
+ NVML_BRAND_TITAN_RTX : "TITAN RTX",
106
+
107
+ }
108
+
109
+ try:
110
+ # If nvmlDeviceGetBrand() succeeds it is guaranteed to be in the dictionary
111
+ brandName = brandNames[nvmlDeviceGetBrand(handle)]
112
+ except NVMLError as err:
113
+ brandName = handleError(err)
114
+
115
+ strResult += ' <product_brand>' + brandName + '</product_brand>\n'
116
+
117
+ try:
118
+ serial = nvmlDeviceGetSerial(handle)
119
+ except NVMLError as err:
120
+ serial = handleError(err)
121
+
122
+ strResult += ' <serial>' + serial + '</serial>\n'
123
+
124
+ try:
125
+ uuid = nvmlDeviceGetUUID(handle)
126
+ except NVMLError as err:
127
+ uuid = handleError(err)
128
+
129
+ strResult += ' <uuid>' + uuid + '</uuid>\n'
130
+
131
+ strResult += ' <gpu_virtualization_mode>\n'
132
+ try:
133
+ mode = StrVirt(nvmlDeviceGetVirtualizationMode(handle))
134
+ except NVMLError as err:
135
+ mode = handleError(err)
136
+ strResult += ' <virtualization_mode>' + mode + '</virtualization_mode>\n'
137
+ strResult += ' </gpu_virtualization_mode>\n'
138
+
139
+ try:
140
+ gridLicensableFeatures = nvmlDeviceGetGridLicensableFeatures(handle)
141
+ if gridLicensableFeatures.isGridLicenseSupported == 1:
142
+ strResult += ' <vgpu_software_licensed_product>\n'
143
+ for i in range(gridLicensableFeatures.licensableFeaturesCount):
144
+ if gridLicensableFeatures.gridLicensableFeatures[i].featureState == 0:
145
+ if nvmlDeviceGetVirtualizationMode(handle) == NVML_GPU_VIRTUALIZATION_MODE_PASSTHROUGH:
146
+ strResult += ' <licensed_product_name>' + 'NVIDIA Virtual Applications' + '</licensed_product_name>\n'
147
+ strResult += ' <license_status>' + 'Licensed' + '</license_status>\n'
148
+ else:
149
+ strResult += ' <licensed_product_name>' + gridLicensableFeatures.gridLicensableFeatures[i].productName + '</licensed_product_name>\n'
150
+ strResult += ' <license_status>' + 'Unlicensed' + '</license_status>\n'
151
+ else:
152
+ strResult += ' <licensed_product_name>' + gridLicensableFeatures.gridLicensableFeatures[i].productName + '</licensed_product_name>\n'
153
+ strResult += ' <license_status>' + 'Licensed' + '</license_status>\n'
154
+ strResult += ' </vgpu_software_licensed_product>\n'
155
+ except NVMLError as err:
156
+ gridLicensableFeatures = handleError(err)
157
+
158
+ strResult += ' </gpu>\n'
159
+
160
+ except NVMLError as err:
161
+ strResult += 'example.py: ' + err.__str__() + '\n'
162
+
163
+ nvmlShutdown()
164
+
165
+ return strResult
166
+
167
+ # If this is not exectued when module is imported
168
+ if __name__ == "__main__":
169
+ print(deviceQuery())
170
+
hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/INSTALLER ADDED
@@ -0,0 +1 @@
 
 
1
+ pip
hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/METADATA ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Metadata-Version: 2.5
2
+ Name: filelock
3
+ Version: 4.2.4
4
+ Summary: A platform independent file lock.
5
+ Project-URL: Documentation, https://py-filelock.readthedocs.io
6
+ Project-URL: Homepage, https://github.com/tox-dev/py-filelock
7
+ Project-URL: Source, https://github.com/tox-dev/py-filelock
8
+ Project-URL: Tracker, https://github.com/tox-dev/py-filelock/issues
9
+ Maintainer-email: Bernát Gábor <gaborjbernat@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: application,cache,directory,log,user
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3.15
24
+ Classifier: Topic :: Internet
25
+ Classifier: Topic :: Software Development :: Libraries
26
+ Classifier: Topic :: System
27
+ Requires-Python: >=3.11
28
+ Description-Content-Type: text/markdown
29
+
30
+ # filelock
31
+
32
+ [![PyPI](https://img.shields.io/pypi/v/filelock)](https://pypi.org/project/filelock/)
33
+ [![Supported Python versions](https://img.shields.io/pypi/pyversions/filelock.svg)](https://pypi.org/project/filelock/)
34
+ [![Documentation status](https://readthedocs.org/projects/py-filelock/badge/?version=latest)](https://py-filelock.readthedocs.io/en/latest/?badge=latest)
35
+ [![Downloads](https://static.pepy.tech/badge/filelock/month)](https://pepy.tech/project/filelock)
36
+ [![check](https://github.com/tox-dev/py-filelock/actions/workflows/check.yaml/badge.svg)](https://github.com/tox-dev/py-filelock/actions/workflows/check.yaml)
37
+
38
+ For more information checkout the [official documentation](https://py-filelock.readthedocs.io/en/latest/index.html).
hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/RECORD ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ filelock-4.2.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
2
+ filelock-4.2.4.dist-info/METADATA,sha256=2ZwPcqerhMkktzQBiiilABUF_g-WKAxEYrhkMT7d21A,1976
3
+ filelock-4.2.4.dist-info/RECORD,,
4
+ filelock-4.2.4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
5
+ filelock-4.2.4.dist-info/licenses/LICENSE,sha256=YIyJ1QYK6ZIa3M8yNmlbxlSplG4SMj72wCHfoE4pTUg,1088
6
+ filelock/__init__.py,sha256=LaXgCbdErp-ugbr3o5bzpYJMBwi1hg0_hO4_vrwP1lk,5693
7
+ filelock/__pycache__/__init__.cpython-312.pyc,,
8
+ filelock/__pycache__/_api.cpython-312.pyc,,
9
+ filelock/__pycache__/_async.cpython-312.pyc,,
10
+ filelock/__pycache__/_async_read_write.cpython-312.pyc,,
11
+ filelock/__pycache__/_descriptor.cpython-312.pyc,,
12
+ filelock/__pycache__/_error.cpython-312.pyc,,
13
+ filelock/__pycache__/_identity.cpython-312.pyc,,
14
+ filelock/__pycache__/_lease.cpython-312.pyc,,
15
+ filelock/__pycache__/_marker.cpython-312.pyc,,
16
+ filelock/__pycache__/_read_write.cpython-312.pyc,,
17
+ filelock/__pycache__/_soft.cpython-312.pyc,,
18
+ filelock/__pycache__/_soft_protocol.cpython-312.pyc,,
19
+ filelock/__pycache__/_strict.cpython-312.pyc,,
20
+ filelock/__pycache__/_unix.cpython-312.pyc,,
21
+ filelock/__pycache__/_util.cpython-312.pyc,,
22
+ filelock/__pycache__/_windows.cpython-312.pyc,,
23
+ filelock/__pycache__/asyncio.cpython-312.pyc,,
24
+ filelock/__pycache__/version.cpython-312.pyc,,
25
+ filelock/_api.py,sha256=OUugdO27gcc_gdfVOJYo0C6Bv6lvXL8Fv84vT7le378,106170
26
+ filelock/_async.py,sha256=jSqb-YUze8cjKaNrlJ2qlMdfN9s1DZbgRMnozp4kDZs,20487
27
+ filelock/_async_read_write.py,sha256=Ts6N_p5-UQLDoYHJKE67NfFV6ZpIcpX0cuu3S55dXzQ,17947
28
+ filelock/_descriptor.py,sha256=Z81x98R7RbN5aHgaRzu48i7c0Ktsheze6Yg5uvWrU0Y,3277
29
+ filelock/_error.py,sha256=sZNfFV3wacnrGCrnOipucPbnHN78NPt1IDnlg1nON1k,3235
30
+ filelock/_identity.py,sha256=s2Kk4SQI0C5en9lHsPkWOVvGzuyY_wgZl8JRSOn63_8,17183
31
+ filelock/_lease.py,sha256=eK0xXpuccVNM8o1BXSQByBE4TaIVYQSbSvfbbS6dReY,19173
32
+ filelock/_marker.py,sha256=dVjalDpcpwqIRF7VUvFK2tkVZ4OCthjmmIinKy9jhFE,7896
33
+ filelock/_read_write.py,sha256=5e0gtCHfLArSNKYH0jGdBXuD60m7stQV8KhuYgUvqQU,68071
34
+ filelock/_soft.py,sha256=fXLlCQKHzDHaXCuwU8bXZw6qgmF1LEKRWVifxnRhRyk,33064
35
+ filelock/_soft_protocol.py,sha256=D64pnXMlM7ysKpdx0fNXhJJgiMcS9T2Qwsu2GpPs2i0,218
36
+ filelock/_soft_rw/__init__.py,sha256=JAMF4NA010_Jq-nEgHHZgWHZ7Of9wI3PKHg24mDdX50,452
37
+ filelock/_soft_rw/__pycache__/__init__.cpython-312.pyc,,
38
+ filelock/_soft_rw/__pycache__/_async.cpython-312.pyc,,
39
+ filelock/_soft_rw/__pycache__/_protocol.cpython-312.pyc,,
40
+ filelock/_soft_rw/__pycache__/_storage.cpython-312.pyc,,
41
+ filelock/_soft_rw/__pycache__/_sync.cpython-312.pyc,,
42
+ filelock/_soft_rw/_async.py,sha256=v3q2adfqb9vQRwqhlctYdKZYlltJ4XutZcJ7IXuDy4M,16939
43
+ filelock/_soft_rw/_protocol.py,sha256=O-ENv_XstRpIIDh4GnFoPNfkIg-xsDc8tVpsfBhrklA,30996
44
+ filelock/_soft_rw/_storage.py,sha256=SbhhE8HQp7RcPo7BPnUNSEi8di2b6M7JwXHBh4EkuSc,13691
45
+ filelock/_soft_rw/_sync.py,sha256=qVgup1GUig8fDSWLkl16z8_9UXojAcPJ_nZWl5BzIaI,37638
46
+ filelock/_strict.py,sha256=iaEnG4QmSESjubZIhz0aq4Fq5cWmBy3STeim-8F6AZs,58570
47
+ filelock/_unix.py,sha256=BLotMhNLFKTrW6zQ_qoI9qZGo3YqYfhGO_qP5W_p6Lw,10985
48
+ filelock/_util.py,sha256=XW3K2JNfJapekS7YgUYBGiOFYcSf8A1cBqnKfU6jxzY,19280
49
+ filelock/_windows.py,sha256=cDFvJN8dJtdDYjHYPt_kplvJCklD_yI7_UiaJkV-V80,38880
50
+ filelock/asyncio.py,sha256=x3-lsFBKzEnPQR_2bQDoSD9ErdmV_3lw5rPyRq7AZc4,42865
51
+ filelock/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
+ filelock/version.py,sha256=v35Uwrw-IApU2AA-2B77yCvOvNQ1Vh7gXvxfaJ9Vd14,520
hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/WHEEL ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
hug/lib/python3.12/site-packages/filelock-4.2.4.dist-info/licenses/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bernát Gábor and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hug/lib/python3.12/site-packages/filelock/__init__.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ A platform independent file lock that supports the with-statement.
3
+
4
+ .. autodata:: filelock.__version__
5
+ :no-value:
6
+
7
+ .. autodata:: filelock.OwnerMode
8
+
9
+ .. autodata:: filelock.CompromiseReason
10
+
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ __lazy_modules__ = ["filelock._descriptor", "filelock._error", "importlib"]
16
+
17
+ import sys
18
+ import warnings
19
+ from importlib import import_module
20
+ from typing import TYPE_CHECKING, Final
21
+
22
+ from filelock._api import AcquireReturnProxy, BaseFileLock, CloseErrorPolicy, ContextErrorPolicy, LockOptions
23
+ from filelock._descriptor import lock_descriptor, unlock_descriptor
24
+ from filelock._error import LeaseSettingsMismatch, SoftFileLockLifetimeWarning, SoftFileLockProtocolError, Timeout
25
+ from filelock._unix import UnixFileLock
26
+ from filelock._unix import has_fcntl as _has_fcntl
27
+ from filelock._windows import WindowsFileLock
28
+
29
+ # Keeps ``filelock.version`` the string 4.0.12 exposed rather than the submodule.
30
+ from filelock.version import version
31
+
32
+ if TYPE_CHECKING:
33
+ from filelock import asyncio
34
+ from filelock._async_read_write import AsyncAcquireReadWriteReturnProxy, AsyncReadWriteLock
35
+ from filelock._lease import CompromiseReason, LeaseCompromise, SoftFileLease
36
+ from filelock._marker import MarkerSoftFileLock, OwnerMode, OwnerRecord
37
+ from filelock._read_write import ReadWriteLock
38
+ from filelock._soft import SoftFileLock
39
+ from filelock._soft_rw import AsyncAcquireSoftReadWriteReturnProxy, AsyncSoftReadWriteLock, SoftReadWriteLock
40
+ from filelock._strict import StrictSoftFileClaim, StrictSoftFileClaimState, StrictSoftFileLock
41
+ from filelock.asyncio import (
42
+ AsyncAcquireReturnProxy,
43
+ AsyncSoftFileLease,
44
+ AsyncSoftFileLock,
45
+ AsyncStrictSoftFileLock,
46
+ AsyncUnixFileLock,
47
+ AsyncWindowsFileLock,
48
+ BaseAsyncFileLock,
49
+ )
50
+
51
+ #: version of the project as a string
52
+ __version__: Final[str] = version
53
+
54
+ #: ``True`` when :func:`fcntl.flock` is available. Off Windows, :data:`FileLock` then resolves to
55
+ #: :class:`UnixFileLock`, otherwise to :class:`SoftFileLock`. Always ``False`` on Windows.
56
+ #:
57
+ #: :meta hide-value:
58
+ has_fcntl: Final[bool] = _has_fcntl
59
+
60
+
61
+ if sys.platform == "win32": # pragma: win32 cover
62
+ _FileLock: type[BaseFileLock] = WindowsFileLock
63
+ else: # pragma: win32 no cover # ruff:ignore[collapsible-else-if] # the else carries the win32 no-cover pragma
64
+ if has_fcntl:
65
+ _FileLock: type[BaseFileLock] = UnixFileLock
66
+ else:
67
+ from filelock._soft import SoftFileLock
68
+
69
+ _FileLock = SoftFileLock
70
+ warnings.warn("only soft file lock is available", stacklevel=2)
71
+
72
+ if TYPE_CHECKING:
73
+ FileLock = SoftFileLock
74
+ AsyncFileLock = AsyncSoftFileLock
75
+ else:
76
+ #: Alias for the lock, which should be used for the current platform.
77
+ FileLock = _FileLock
78
+
79
+ # Importing asyncio and sqlite3 costs more than the rest of the package, so names that need them load on first access.
80
+ _LAZY_ATTRIBUTES: Final[dict[str, str]] = {
81
+ "AsyncAcquireReadWriteReturnProxy": "._async_read_write",
82
+ "AsyncAcquireReturnProxy": ".asyncio",
83
+ "AsyncAcquireSoftReadWriteReturnProxy": "._soft_rw",
84
+ "AsyncFileLock": ".asyncio",
85
+ "AsyncReadWriteLock": "._async_read_write",
86
+ "AsyncSoftFileLease": ".asyncio",
87
+ "AsyncSoftFileLock": ".asyncio",
88
+ "AsyncSoftReadWriteLock": "._soft_rw",
89
+ "AsyncStrictSoftFileLock": ".asyncio",
90
+ "AsyncUnixFileLock": ".asyncio",
91
+ "AsyncWindowsFileLock": ".asyncio",
92
+ "BaseAsyncFileLock": ".asyncio",
93
+ "CompromiseReason": "._lease",
94
+ "LeaseCompromise": "._lease",
95
+ "MarkerSoftFileLock": "._marker",
96
+ "OwnerMode": "._marker",
97
+ "OwnerRecord": "._marker",
98
+ "ReadWriteLock": "._read_write",
99
+ "SoftFileLease": "._lease",
100
+ "SoftFileLock": "._soft",
101
+ "SoftReadWriteLock": "._soft_rw",
102
+ "StrictSoftFileClaim": "._strict",
103
+ "StrictSoftFileClaimState": "._strict",
104
+ "StrictSoftFileLock": "._strict",
105
+ "asyncio": ".asyncio",
106
+ }
107
+
108
+
109
+ def __getattr__(name: str) -> object:
110
+ if (module_name := _LAZY_ATTRIBUTES.get(name)) is None:
111
+ msg = f"module {__name__!r} has no attribute {name!r}"
112
+ raise AttributeError(msg)
113
+ try:
114
+ module = import_module(module_name, __name__)
115
+ except ImportError: # pragma: lacks sqlite3
116
+ if module_name not in {"._read_write", "._async_read_write"}:
117
+ raise
118
+ value = None
119
+ else:
120
+ if name == "asyncio":
121
+ value = module
122
+ else:
123
+ value = getattr(module, f"Async{_FileLock.__name__}" if name == "AsyncFileLock" else name)
124
+ globals()[name] = value
125
+ return value
126
+
127
+
128
+ def __dir__() -> list[str]:
129
+ return sorted({*globals(), *_LAZY_ATTRIBUTES})
130
+
131
+
132
+ __all__ = [
133
+ "AcquireReturnProxy",
134
+ "AsyncAcquireReadWriteReturnProxy",
135
+ "AsyncAcquireReturnProxy",
136
+ "AsyncAcquireSoftReadWriteReturnProxy",
137
+ "AsyncFileLock",
138
+ "AsyncReadWriteLock",
139
+ "AsyncSoftFileLease",
140
+ "AsyncSoftFileLock",
141
+ "AsyncSoftReadWriteLock",
142
+ "AsyncStrictSoftFileLock",
143
+ "AsyncUnixFileLock",
144
+ "AsyncWindowsFileLock",
145
+ "BaseAsyncFileLock",
146
+ "BaseFileLock",
147
+ "CloseErrorPolicy",
148
+ "CompromiseReason",
149
+ "ContextErrorPolicy",
150
+ "FileLock",
151
+ "LeaseCompromise",
152
+ "LeaseSettingsMismatch",
153
+ "LockOptions",
154
+ "MarkerSoftFileLock",
155
+ "OwnerMode",
156
+ "OwnerRecord",
157
+ "ReadWriteLock",
158
+ "SoftFileLease",
159
+ "SoftFileLock",
160
+ "SoftFileLockLifetimeWarning",
161
+ "SoftFileLockProtocolError",
162
+ "SoftReadWriteLock",
163
+ "StrictSoftFileClaim",
164
+ "StrictSoftFileClaimState",
165
+ "StrictSoftFileLock",
166
+ "Timeout",
167
+ "UnixFileLock",
168
+ "WindowsFileLock",
169
+ "__version__",
170
+ "asyncio",
171
+ "has_fcntl",
172
+ "lock_descriptor",
173
+ "unlock_descriptor",
174
+ ]
hug/lib/python3.12/site-packages/filelock/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (4.65 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_api.cpython-312.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:228cffaa0359c96ef77da374611ffc1fc2167ce70ec382681b1242a5ef040863
3
+ size 111999
hug/lib/python3.12/site-packages/filelock/__pycache__/_async.cpython-312.pyc ADDED
Binary file (25.4 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_async_read_write.cpython-312.pyc ADDED
Binary file (21.6 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_descriptor.cpython-312.pyc ADDED
Binary file (3.66 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_error.cpython-312.pyc ADDED
Binary file (5.02 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_identity.cpython-312.pyc ADDED
Binary file (17.6 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_lease.cpython-312.pyc ADDED
Binary file (20.1 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_marker.cpython-312.pyc ADDED
Binary file (10.2 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_read_write.cpython-312.pyc ADDED
Binary file (78.6 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_soft.cpython-312.pyc ADDED
Binary file (34.5 kB). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_soft_protocol.cpython-312.pyc ADDED
Binary file (447 Bytes). View file
 
hug/lib/python3.12/site-packages/filelock/__pycache__/_strict.cpython-312.pyc ADDED
Binary file (65.5 kB). View file