Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- dateutil/parser/__init__.py +61 -0
- dateutil/parser/_parser.py +1613 -0
- dateutil/parser/isoparser.py +416 -0
- dateutil/tz/__init__.py +12 -0
- dateutil/tz/_common.py +419 -0
- dateutil/tz/_factories.py +80 -0
- dateutil/tz/tz.py +1849 -0
- dateutil/tz/win.py +370 -0
- dateutil/zoneinfo/__init__.py +167 -0
- dateutil/zoneinfo/rebuild.py +75 -0
- pandas/_config/__init__.py +45 -0
- pandas/_config/config.py +954 -0
- pandas/_config/dates.py +26 -0
- pandas/_config/display.py +62 -0
- pandas/_config/localization.py +176 -0
- pandas/_libs/__init__.py +27 -0
- pandas/_libs/algos.pyi +443 -0
- pandas/_libs/arrays.pyi +40 -0
- pandas/_libs/byteswap.cpython-312-x86_64-linux-gnu.so +0 -0
- pandas/_libs/byteswap.pyi +5 -0
- pandas/_libs/groupby.pyi +234 -0
- pandas/_libs/hashing.cpython-312-x86_64-linux-gnu.so +0 -0
- pandas/_libs/hashing.pyi +9 -0
- pandas/_libs/hashtable.pyi +274 -0
- pandas/_libs/index.pyi +107 -0
- pandas/_libs/indexing.cpython-312-x86_64-linux-gnu.so +0 -0
- pandas/_libs/indexing.pyi +17 -0
- pandas/_libs/internals.pyi +96 -0
- pandas/_libs/interval.pyi +174 -0
- pandas/_libs/join.pyi +79 -0
- pandas/_libs/json.cpython-312-x86_64-linux-gnu.so +0 -0
- pandas/_libs/json.pyi +23 -0
- pandas/_libs/lib.pyi +238 -0
- pandas/_libs/missing.pyi +17 -0
- pandas/_libs/ops.pyi +53 -0
- pandas/_libs/ops_dispatch.cpython-312-x86_64-linux-gnu.so +0 -0
- pandas/_libs/ops_dispatch.pyi +5 -0
- pandas/_libs/pandas_datetime.cpython-312-x86_64-linux-gnu.so +0 -0
- pandas/_libs/pandas_parser.cpython-312-x86_64-linux-gnu.so +0 -0
- pandas/_libs/parsers.pyi +77 -0
- pandas/_libs/properties.cpython-312-x86_64-linux-gnu.so +0 -0
- pandas/_libs/properties.pyi +27 -0
- pandas/_libs/reshape.pyi +16 -0
- pandas/_libs/sas.pyi +7 -0
- pandas/_libs/sparse.pyi +51 -0
- pandas/_libs/testing.pyi +14 -0
- pandas/_libs/tslib.pyi +33 -0
- pandas/_libs/tslibs/__init__.py +89 -0
- pandas/_libs/tslibs/base.cpython-312-x86_64-linux-gnu.so +0 -0
- pandas/_libs/tslibs/ccalendar.cpython-312-x86_64-linux-gnu.so +0 -0
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)
|
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
|
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
|
dateutil/tz/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
from .tz import *
|
| 3 |
+
from .tz import __doc__
|
| 4 |
+
|
| 5 |
+
__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange",
|
| 6 |
+
"tzstr", "tzical", "tzwin", "tzwinlocal", "gettz",
|
| 7 |
+
"enfold", "datetime_ambiguous", "datetime_exists",
|
| 8 |
+
"resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"]
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class DeprecatedTzFormatWarning(Warning):
|
| 12 |
+
"""Warning raised when time zones are parsed from deprecated formats."""
|
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__
|
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 |
+
|
dateutil/tz/tz.py
ADDED
|
@@ -0,0 +1,1849 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
import datetime
|
| 11 |
+
import struct
|
| 12 |
+
import time
|
| 13 |
+
import sys
|
| 14 |
+
import os
|
| 15 |
+
import bisect
|
| 16 |
+
import weakref
|
| 17 |
+
from collections import OrderedDict
|
| 18 |
+
|
| 19 |
+
import six
|
| 20 |
+
from six import string_types
|
| 21 |
+
from six.moves import _thread
|
| 22 |
+
from ._common import tzname_in_python2, _tzinfo
|
| 23 |
+
from ._common import tzrangebase, enfold
|
| 24 |
+
from ._common import _validate_fromutc_inputs
|
| 25 |
+
|
| 26 |
+
from ._factories import _TzSingleton, _TzOffsetFactory
|
| 27 |
+
from ._factories import _TzStrFactory
|
| 28 |
+
try:
|
| 29 |
+
from .win import tzwin, tzwinlocal
|
| 30 |
+
except ImportError:
|
| 31 |
+
tzwin = tzwinlocal = None
|
| 32 |
+
|
| 33 |
+
# For warning about rounding tzinfo
|
| 34 |
+
from warnings import warn
|
| 35 |
+
|
| 36 |
+
ZERO = datetime.timedelta(0)
|
| 37 |
+
EPOCH = datetime.datetime(1970, 1, 1, 0, 0)
|
| 38 |
+
EPOCHORDINAL = EPOCH.toordinal()
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@six.add_metaclass(_TzSingleton)
|
| 42 |
+
class tzutc(datetime.tzinfo):
|
| 43 |
+
"""
|
| 44 |
+
This is a tzinfo object that represents the UTC time zone.
|
| 45 |
+
|
| 46 |
+
**Examples:**
|
| 47 |
+
|
| 48 |
+
.. doctest::
|
| 49 |
+
|
| 50 |
+
>>> from datetime import *
|
| 51 |
+
>>> from dateutil.tz import *
|
| 52 |
+
|
| 53 |
+
>>> datetime.now()
|
| 54 |
+
datetime.datetime(2003, 9, 27, 9, 40, 1, 521290)
|
| 55 |
+
|
| 56 |
+
>>> datetime.now(tzutc())
|
| 57 |
+
datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc())
|
| 58 |
+
|
| 59 |
+
>>> datetime.now(tzutc()).tzname()
|
| 60 |
+
'UTC'
|
| 61 |
+
|
| 62 |
+
.. versionchanged:: 2.7.0
|
| 63 |
+
``tzutc()`` is now a singleton, so the result of ``tzutc()`` will
|
| 64 |
+
always return the same object.
|
| 65 |
+
|
| 66 |
+
.. doctest::
|
| 67 |
+
|
| 68 |
+
>>> from dateutil.tz import tzutc, UTC
|
| 69 |
+
>>> tzutc() is tzutc()
|
| 70 |
+
True
|
| 71 |
+
>>> tzutc() is UTC
|
| 72 |
+
True
|
| 73 |
+
"""
|
| 74 |
+
def utcoffset(self, dt):
|
| 75 |
+
return ZERO
|
| 76 |
+
|
| 77 |
+
def dst(self, dt):
|
| 78 |
+
return ZERO
|
| 79 |
+
|
| 80 |
+
@tzname_in_python2
|
| 81 |
+
def tzname(self, dt):
|
| 82 |
+
return "UTC"
|
| 83 |
+
|
| 84 |
+
def is_ambiguous(self, dt):
|
| 85 |
+
"""
|
| 86 |
+
Whether or not the "wall time" of a given datetime is ambiguous in this
|
| 87 |
+
zone.
|
| 88 |
+
|
| 89 |
+
:param dt:
|
| 90 |
+
A :py:class:`datetime.datetime`, naive or time zone aware.
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
:return:
|
| 94 |
+
Returns ``True`` if ambiguous, ``False`` otherwise.
|
| 95 |
+
|
| 96 |
+
.. versionadded:: 2.6.0
|
| 97 |
+
"""
|
| 98 |
+
return False
|
| 99 |
+
|
| 100 |
+
@_validate_fromutc_inputs
|
| 101 |
+
def fromutc(self, dt):
|
| 102 |
+
"""
|
| 103 |
+
Fast track version of fromutc() returns the original ``dt`` object for
|
| 104 |
+
any valid :py:class:`datetime.datetime` object.
|
| 105 |
+
"""
|
| 106 |
+
return dt
|
| 107 |
+
|
| 108 |
+
def __eq__(self, other):
|
| 109 |
+
if not isinstance(other, (tzutc, tzoffset)):
|
| 110 |
+
return NotImplemented
|
| 111 |
+
|
| 112 |
+
return (isinstance(other, tzutc) or
|
| 113 |
+
(isinstance(other, tzoffset) and other._offset == ZERO))
|
| 114 |
+
|
| 115 |
+
__hash__ = None
|
| 116 |
+
|
| 117 |
+
def __ne__(self, other):
|
| 118 |
+
return not (self == other)
|
| 119 |
+
|
| 120 |
+
def __repr__(self):
|
| 121 |
+
return "%s()" % self.__class__.__name__
|
| 122 |
+
|
| 123 |
+
__reduce__ = object.__reduce__
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
#: Convenience constant providing a :class:`tzutc()` instance
|
| 127 |
+
#:
|
| 128 |
+
#: .. versionadded:: 2.7.0
|
| 129 |
+
UTC = tzutc()
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
@six.add_metaclass(_TzOffsetFactory)
|
| 133 |
+
class tzoffset(datetime.tzinfo):
|
| 134 |
+
"""
|
| 135 |
+
A simple class for representing a fixed offset from UTC.
|
| 136 |
+
|
| 137 |
+
:param name:
|
| 138 |
+
The timezone name, to be returned when ``tzname()`` is called.
|
| 139 |
+
:param offset:
|
| 140 |
+
The time zone offset in seconds, or (since version 2.6.0, represented
|
| 141 |
+
as a :py:class:`datetime.timedelta` object).
|
| 142 |
+
"""
|
| 143 |
+
def __init__(self, name, offset):
|
| 144 |
+
self._name = name
|
| 145 |
+
|
| 146 |
+
try:
|
| 147 |
+
# Allow a timedelta
|
| 148 |
+
offset = offset.total_seconds()
|
| 149 |
+
except (TypeError, AttributeError):
|
| 150 |
+
pass
|
| 151 |
+
|
| 152 |
+
self._offset = datetime.timedelta(seconds=_get_supported_offset(offset))
|
| 153 |
+
|
| 154 |
+
def utcoffset(self, dt):
|
| 155 |
+
return self._offset
|
| 156 |
+
|
| 157 |
+
def dst(self, dt):
|
| 158 |
+
return ZERO
|
| 159 |
+
|
| 160 |
+
@tzname_in_python2
|
| 161 |
+
def tzname(self, dt):
|
| 162 |
+
return self._name
|
| 163 |
+
|
| 164 |
+
@_validate_fromutc_inputs
|
| 165 |
+
def fromutc(self, dt):
|
| 166 |
+
return dt + self._offset
|
| 167 |
+
|
| 168 |
+
def is_ambiguous(self, dt):
|
| 169 |
+
"""
|
| 170 |
+
Whether or not the "wall time" of a given datetime is ambiguous in this
|
| 171 |
+
zone.
|
| 172 |
+
|
| 173 |
+
:param dt:
|
| 174 |
+
A :py:class:`datetime.datetime`, naive or time zone aware.
|
| 175 |
+
:return:
|
| 176 |
+
Returns ``True`` if ambiguous, ``False`` otherwise.
|
| 177 |
+
|
| 178 |
+
.. versionadded:: 2.6.0
|
| 179 |
+
"""
|
| 180 |
+
return False
|
| 181 |
+
|
| 182 |
+
def __eq__(self, other):
|
| 183 |
+
if not isinstance(other, tzoffset):
|
| 184 |
+
return NotImplemented
|
| 185 |
+
|
| 186 |
+
return self._offset == other._offset
|
| 187 |
+
|
| 188 |
+
__hash__ = None
|
| 189 |
+
|
| 190 |
+
def __ne__(self, other):
|
| 191 |
+
return not (self == other)
|
| 192 |
+
|
| 193 |
+
def __repr__(self):
|
| 194 |
+
return "%s(%s, %s)" % (self.__class__.__name__,
|
| 195 |
+
repr(self._name),
|
| 196 |
+
int(self._offset.total_seconds()))
|
| 197 |
+
|
| 198 |
+
__reduce__ = object.__reduce__
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class tzlocal(_tzinfo):
|
| 202 |
+
"""
|
| 203 |
+
A :class:`tzinfo` subclass built around the ``time`` timezone functions.
|
| 204 |
+
"""
|
| 205 |
+
def __init__(self):
|
| 206 |
+
super(tzlocal, self).__init__()
|
| 207 |
+
|
| 208 |
+
self._std_offset = datetime.timedelta(seconds=-time.timezone)
|
| 209 |
+
if time.daylight:
|
| 210 |
+
self._dst_offset = datetime.timedelta(seconds=-time.altzone)
|
| 211 |
+
else:
|
| 212 |
+
self._dst_offset = self._std_offset
|
| 213 |
+
|
| 214 |
+
self._dst_saved = self._dst_offset - self._std_offset
|
| 215 |
+
self._hasdst = bool(self._dst_saved)
|
| 216 |
+
self._tznames = tuple(time.tzname)
|
| 217 |
+
|
| 218 |
+
def utcoffset(self, dt):
|
| 219 |
+
if dt is None and self._hasdst:
|
| 220 |
+
return None
|
| 221 |
+
|
| 222 |
+
if self._isdst(dt):
|
| 223 |
+
return self._dst_offset
|
| 224 |
+
else:
|
| 225 |
+
return self._std_offset
|
| 226 |
+
|
| 227 |
+
def dst(self, dt):
|
| 228 |
+
if dt is None and self._hasdst:
|
| 229 |
+
return None
|
| 230 |
+
|
| 231 |
+
if self._isdst(dt):
|
| 232 |
+
return self._dst_offset - self._std_offset
|
| 233 |
+
else:
|
| 234 |
+
return ZERO
|
| 235 |
+
|
| 236 |
+
@tzname_in_python2
|
| 237 |
+
def tzname(self, dt):
|
| 238 |
+
return self._tznames[self._isdst(dt)]
|
| 239 |
+
|
| 240 |
+
def is_ambiguous(self, dt):
|
| 241 |
+
"""
|
| 242 |
+
Whether or not the "wall time" of a given datetime is ambiguous in this
|
| 243 |
+
zone.
|
| 244 |
+
|
| 245 |
+
:param dt:
|
| 246 |
+
A :py:class:`datetime.datetime`, naive or time zone aware.
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
:return:
|
| 250 |
+
Returns ``True`` if ambiguous, ``False`` otherwise.
|
| 251 |
+
|
| 252 |
+
.. versionadded:: 2.6.0
|
| 253 |
+
"""
|
| 254 |
+
naive_dst = self._naive_is_dst(dt)
|
| 255 |
+
return (not naive_dst and
|
| 256 |
+
(naive_dst != self._naive_is_dst(dt - self._dst_saved)))
|
| 257 |
+
|
| 258 |
+
def _naive_is_dst(self, dt):
|
| 259 |
+
timestamp = _datetime_to_timestamp(dt)
|
| 260 |
+
return time.localtime(timestamp + time.timezone).tm_isdst
|
| 261 |
+
|
| 262 |
+
def _isdst(self, dt, fold_naive=True):
|
| 263 |
+
# We can't use mktime here. It is unstable when deciding if
|
| 264 |
+
# the hour near to a change is DST or not.
|
| 265 |
+
#
|
| 266 |
+
# timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour,
|
| 267 |
+
# dt.minute, dt.second, dt.weekday(), 0, -1))
|
| 268 |
+
# return time.localtime(timestamp).tm_isdst
|
| 269 |
+
#
|
| 270 |
+
# The code above yields the following result:
|
| 271 |
+
#
|
| 272 |
+
# >>> import tz, datetime
|
| 273 |
+
# >>> t = tz.tzlocal()
|
| 274 |
+
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
| 275 |
+
# 'BRDT'
|
| 276 |
+
# >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname()
|
| 277 |
+
# 'BRST'
|
| 278 |
+
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
| 279 |
+
# 'BRST'
|
| 280 |
+
# >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname()
|
| 281 |
+
# 'BRDT'
|
| 282 |
+
# >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname()
|
| 283 |
+
# 'BRDT'
|
| 284 |
+
#
|
| 285 |
+
# Here is a more stable implementation:
|
| 286 |
+
#
|
| 287 |
+
if not self._hasdst:
|
| 288 |
+
return False
|
| 289 |
+
|
| 290 |
+
# Check for ambiguous times:
|
| 291 |
+
dstval = self._naive_is_dst(dt)
|
| 292 |
+
fold = getattr(dt, 'fold', None)
|
| 293 |
+
|
| 294 |
+
if self.is_ambiguous(dt):
|
| 295 |
+
if fold is not None:
|
| 296 |
+
return not self._fold(dt)
|
| 297 |
+
else:
|
| 298 |
+
return True
|
| 299 |
+
|
| 300 |
+
return dstval
|
| 301 |
+
|
| 302 |
+
def __eq__(self, other):
|
| 303 |
+
if isinstance(other, tzlocal):
|
| 304 |
+
return (self._std_offset == other._std_offset and
|
| 305 |
+
self._dst_offset == other._dst_offset)
|
| 306 |
+
elif isinstance(other, tzutc):
|
| 307 |
+
return (not self._hasdst and
|
| 308 |
+
self._tznames[0] in {'UTC', 'GMT'} and
|
| 309 |
+
self._std_offset == ZERO)
|
| 310 |
+
elif isinstance(other, tzoffset):
|
| 311 |
+
return (not self._hasdst and
|
| 312 |
+
self._tznames[0] == other._name and
|
| 313 |
+
self._std_offset == other._offset)
|
| 314 |
+
else:
|
| 315 |
+
return NotImplemented
|
| 316 |
+
|
| 317 |
+
__hash__ = None
|
| 318 |
+
|
| 319 |
+
def __ne__(self, other):
|
| 320 |
+
return not (self == other)
|
| 321 |
+
|
| 322 |
+
def __repr__(self):
|
| 323 |
+
return "%s()" % self.__class__.__name__
|
| 324 |
+
|
| 325 |
+
__reduce__ = object.__reduce__
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
class _ttinfo(object):
|
| 329 |
+
__slots__ = ["offset", "delta", "isdst", "abbr",
|
| 330 |
+
"isstd", "isgmt", "dstoffset"]
|
| 331 |
+
|
| 332 |
+
def __init__(self):
|
| 333 |
+
for attr in self.__slots__:
|
| 334 |
+
setattr(self, attr, None)
|
| 335 |
+
|
| 336 |
+
def __repr__(self):
|
| 337 |
+
l = []
|
| 338 |
+
for attr in self.__slots__:
|
| 339 |
+
value = getattr(self, attr)
|
| 340 |
+
if value is not None:
|
| 341 |
+
l.append("%s=%s" % (attr, repr(value)))
|
| 342 |
+
return "%s(%s)" % (self.__class__.__name__, ", ".join(l))
|
| 343 |
+
|
| 344 |
+
def __eq__(self, other):
|
| 345 |
+
if not isinstance(other, _ttinfo):
|
| 346 |
+
return NotImplemented
|
| 347 |
+
|
| 348 |
+
return (self.offset == other.offset and
|
| 349 |
+
self.delta == other.delta and
|
| 350 |
+
self.isdst == other.isdst and
|
| 351 |
+
self.abbr == other.abbr and
|
| 352 |
+
self.isstd == other.isstd and
|
| 353 |
+
self.isgmt == other.isgmt and
|
| 354 |
+
self.dstoffset == other.dstoffset)
|
| 355 |
+
|
| 356 |
+
__hash__ = None
|
| 357 |
+
|
| 358 |
+
def __ne__(self, other):
|
| 359 |
+
return not (self == other)
|
| 360 |
+
|
| 361 |
+
def __getstate__(self):
|
| 362 |
+
state = {}
|
| 363 |
+
for name in self.__slots__:
|
| 364 |
+
state[name] = getattr(self, name, None)
|
| 365 |
+
return state
|
| 366 |
+
|
| 367 |
+
def __setstate__(self, state):
|
| 368 |
+
for name in self.__slots__:
|
| 369 |
+
if name in state:
|
| 370 |
+
setattr(self, name, state[name])
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
class _tzfile(object):
|
| 374 |
+
"""
|
| 375 |
+
Lightweight class for holding the relevant transition and time zone
|
| 376 |
+
information read from binary tzfiles.
|
| 377 |
+
"""
|
| 378 |
+
attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list',
|
| 379 |
+
'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first']
|
| 380 |
+
|
| 381 |
+
def __init__(self, **kwargs):
|
| 382 |
+
for attr in self.attrs:
|
| 383 |
+
setattr(self, attr, kwargs.get(attr, None))
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
class tzfile(_tzinfo):
|
| 387 |
+
"""
|
| 388 |
+
This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)``
|
| 389 |
+
format timezone files to extract current and historical zone information.
|
| 390 |
+
|
| 391 |
+
:param fileobj:
|
| 392 |
+
This can be an opened file stream or a file name that the time zone
|
| 393 |
+
information can be read from.
|
| 394 |
+
|
| 395 |
+
:param filename:
|
| 396 |
+
This is an optional parameter specifying the source of the time zone
|
| 397 |
+
information in the event that ``fileobj`` is a file object. If omitted
|
| 398 |
+
and ``fileobj`` is a file stream, this parameter will be set either to
|
| 399 |
+
``fileobj``'s ``name`` attribute or to ``repr(fileobj)``.
|
| 400 |
+
|
| 401 |
+
See `Sources for Time Zone and Daylight Saving Time Data
|
| 402 |
+
<https://data.iana.org/time-zones/tz-link.html>`_ for more information.
|
| 403 |
+
Time zone files can be compiled from the `IANA Time Zone database files
|
| 404 |
+
<https://www.iana.org/time-zones>`_ with the `zic time zone compiler
|
| 405 |
+
<https://www.freebsd.org/cgi/man.cgi?query=zic&sektion=8>`_
|
| 406 |
+
|
| 407 |
+
.. note::
|
| 408 |
+
|
| 409 |
+
Only construct a ``tzfile`` directly if you have a specific timezone
|
| 410 |
+
file on disk that you want to read into a Python ``tzinfo`` object.
|
| 411 |
+
If you want to get a ``tzfile`` representing a specific IANA zone,
|
| 412 |
+
(e.g. ``'America/New_York'``), you should call
|
| 413 |
+
:func:`dateutil.tz.gettz` with the zone identifier.
|
| 414 |
+
|
| 415 |
+
|
| 416 |
+
**Examples:**
|
| 417 |
+
|
| 418 |
+
Using the US Eastern time zone as an example, we can see that a ``tzfile``
|
| 419 |
+
provides time zone information for the standard Daylight Saving offsets:
|
| 420 |
+
|
| 421 |
+
.. testsetup:: tzfile
|
| 422 |
+
|
| 423 |
+
from dateutil.tz import gettz
|
| 424 |
+
from datetime import datetime
|
| 425 |
+
|
| 426 |
+
.. doctest:: tzfile
|
| 427 |
+
|
| 428 |
+
>>> NYC = gettz('America/New_York')
|
| 429 |
+
>>> NYC
|
| 430 |
+
tzfile('/usr/share/zoneinfo/America/New_York')
|
| 431 |
+
|
| 432 |
+
>>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST
|
| 433 |
+
2016-01-03 00:00:00-05:00
|
| 434 |
+
|
| 435 |
+
>>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT
|
| 436 |
+
2016-07-07 00:00:00-04:00
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
The ``tzfile`` structure contains a fully history of the time zone,
|
| 440 |
+
so historical dates will also have the right offsets. For example, before
|
| 441 |
+
the adoption of the UTC standards, New York used local solar mean time:
|
| 442 |
+
|
| 443 |
+
.. doctest:: tzfile
|
| 444 |
+
|
| 445 |
+
>>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT
|
| 446 |
+
1901-04-12 00:00:00-04:56
|
| 447 |
+
|
| 448 |
+
And during World War II, New York was on "Eastern War Time", which was a
|
| 449 |
+
state of permanent daylight saving time:
|
| 450 |
+
|
| 451 |
+
.. doctest:: tzfile
|
| 452 |
+
|
| 453 |
+
>>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT
|
| 454 |
+
1944-02-07 00:00:00-04:00
|
| 455 |
+
|
| 456 |
+
"""
|
| 457 |
+
|
| 458 |
+
def __init__(self, fileobj, filename=None):
|
| 459 |
+
super(tzfile, self).__init__()
|
| 460 |
+
|
| 461 |
+
file_opened_here = False
|
| 462 |
+
if isinstance(fileobj, string_types):
|
| 463 |
+
self._filename = fileobj
|
| 464 |
+
fileobj = open(fileobj, 'rb')
|
| 465 |
+
file_opened_here = True
|
| 466 |
+
elif filename is not None:
|
| 467 |
+
self._filename = filename
|
| 468 |
+
elif hasattr(fileobj, "name"):
|
| 469 |
+
self._filename = fileobj.name
|
| 470 |
+
else:
|
| 471 |
+
self._filename = repr(fileobj)
|
| 472 |
+
|
| 473 |
+
if fileobj is not None:
|
| 474 |
+
if not file_opened_here:
|
| 475 |
+
fileobj = _nullcontext(fileobj)
|
| 476 |
+
|
| 477 |
+
with fileobj as file_stream:
|
| 478 |
+
tzobj = self._read_tzfile(file_stream)
|
| 479 |
+
|
| 480 |
+
self._set_tzdata(tzobj)
|
| 481 |
+
|
| 482 |
+
def _set_tzdata(self, tzobj):
|
| 483 |
+
""" Set the time zone data of this object from a _tzfile object """
|
| 484 |
+
# Copy the relevant attributes over as private attributes
|
| 485 |
+
for attr in _tzfile.attrs:
|
| 486 |
+
setattr(self, '_' + attr, getattr(tzobj, attr))
|
| 487 |
+
|
| 488 |
+
def _read_tzfile(self, fileobj):
|
| 489 |
+
out = _tzfile()
|
| 490 |
+
|
| 491 |
+
# From tzfile(5):
|
| 492 |
+
#
|
| 493 |
+
# The time zone information files used by tzset(3)
|
| 494 |
+
# begin with the magic characters "TZif" to identify
|
| 495 |
+
# them as time zone information files, followed by
|
| 496 |
+
# sixteen bytes reserved for future use, followed by
|
| 497 |
+
# six four-byte values of type long, written in a
|
| 498 |
+
# ``standard'' byte order (the high-order byte
|
| 499 |
+
# of the value is written first).
|
| 500 |
+
if fileobj.read(4).decode() != "TZif":
|
| 501 |
+
raise ValueError("magic not found")
|
| 502 |
+
|
| 503 |
+
fileobj.read(16)
|
| 504 |
+
|
| 505 |
+
(
|
| 506 |
+
# The number of UTC/local indicators stored in the file.
|
| 507 |
+
ttisgmtcnt,
|
| 508 |
+
|
| 509 |
+
# The number of standard/wall indicators stored in the file.
|
| 510 |
+
ttisstdcnt,
|
| 511 |
+
|
| 512 |
+
# The number of leap seconds for which data is
|
| 513 |
+
# stored in the file.
|
| 514 |
+
leapcnt,
|
| 515 |
+
|
| 516 |
+
# The number of "transition times" for which data
|
| 517 |
+
# is stored in the file.
|
| 518 |
+
timecnt,
|
| 519 |
+
|
| 520 |
+
# The number of "local time types" for which data
|
| 521 |
+
# is stored in the file (must not be zero).
|
| 522 |
+
typecnt,
|
| 523 |
+
|
| 524 |
+
# The number of characters of "time zone
|
| 525 |
+
# abbreviation strings" stored in the file.
|
| 526 |
+
charcnt,
|
| 527 |
+
|
| 528 |
+
) = struct.unpack(">6l", fileobj.read(24))
|
| 529 |
+
|
| 530 |
+
# The above header is followed by tzh_timecnt four-byte
|
| 531 |
+
# values of type long, sorted in ascending order.
|
| 532 |
+
# These values are written in ``standard'' byte order.
|
| 533 |
+
# Each is used as a transition time (as returned by
|
| 534 |
+
# time(2)) at which the rules for computing local time
|
| 535 |
+
# change.
|
| 536 |
+
|
| 537 |
+
if timecnt:
|
| 538 |
+
out.trans_list_utc = list(struct.unpack(">%dl" % timecnt,
|
| 539 |
+
fileobj.read(timecnt*4)))
|
| 540 |
+
else:
|
| 541 |
+
out.trans_list_utc = []
|
| 542 |
+
|
| 543 |
+
# Next come tzh_timecnt one-byte values of type unsigned
|
| 544 |
+
# char; each one tells which of the different types of
|
| 545 |
+
# ``local time'' types described in the file is associated
|
| 546 |
+
# with the same-indexed transition time. These values
|
| 547 |
+
# serve as indices into an array of ttinfo structures that
|
| 548 |
+
# appears next in the file.
|
| 549 |
+
|
| 550 |
+
if timecnt:
|
| 551 |
+
out.trans_idx = struct.unpack(">%dB" % timecnt,
|
| 552 |
+
fileobj.read(timecnt))
|
| 553 |
+
else:
|
| 554 |
+
out.trans_idx = []
|
| 555 |
+
|
| 556 |
+
# Each ttinfo structure is written as a four-byte value
|
| 557 |
+
# for tt_gmtoff of type long, in a standard byte
|
| 558 |
+
# order, followed by a one-byte value for tt_isdst
|
| 559 |
+
# and a one-byte value for tt_abbrind. In each
|
| 560 |
+
# structure, tt_gmtoff gives the number of
|
| 561 |
+
# seconds to be added to UTC, tt_isdst tells whether
|
| 562 |
+
# tm_isdst should be set by localtime(3), and
|
| 563 |
+
# tt_abbrind serves as an index into the array of
|
| 564 |
+
# time zone abbreviation characters that follow the
|
| 565 |
+
# ttinfo structure(s) in the file.
|
| 566 |
+
|
| 567 |
+
ttinfo = []
|
| 568 |
+
|
| 569 |
+
for i in range(typecnt):
|
| 570 |
+
ttinfo.append(struct.unpack(">lbb", fileobj.read(6)))
|
| 571 |
+
|
| 572 |
+
abbr = fileobj.read(charcnt).decode()
|
| 573 |
+
|
| 574 |
+
# Then there are tzh_leapcnt pairs of four-byte
|
| 575 |
+
# values, written in standard byte order; the
|
| 576 |
+
# first value of each pair gives the time (as
|
| 577 |
+
# returned by time(2)) at which a leap second
|
| 578 |
+
# occurs; the second gives the total number of
|
| 579 |
+
# leap seconds to be applied after the given time.
|
| 580 |
+
# The pairs of values are sorted in ascending order
|
| 581 |
+
# by time.
|
| 582 |
+
|
| 583 |
+
# Not used, for now (but seek for correct file position)
|
| 584 |
+
if leapcnt:
|
| 585 |
+
fileobj.seek(leapcnt * 8, os.SEEK_CUR)
|
| 586 |
+
|
| 587 |
+
# Then there are tzh_ttisstdcnt standard/wall
|
| 588 |
+
# indicators, each stored as a one-byte value;
|
| 589 |
+
# they tell whether the transition times associated
|
| 590 |
+
# with local time types were specified as standard
|
| 591 |
+
# time or wall clock time, and are used when
|
| 592 |
+
# a time zone file is used in handling POSIX-style
|
| 593 |
+
# time zone environment variables.
|
| 594 |
+
|
| 595 |
+
if ttisstdcnt:
|
| 596 |
+
isstd = struct.unpack(">%db" % ttisstdcnt,
|
| 597 |
+
fileobj.read(ttisstdcnt))
|
| 598 |
+
|
| 599 |
+
# Finally, there are tzh_ttisgmtcnt UTC/local
|
| 600 |
+
# indicators, each stored as a one-byte value;
|
| 601 |
+
# they tell whether the transition times associated
|
| 602 |
+
# with local time types were specified as UTC or
|
| 603 |
+
# local time, and are used when a time zone file
|
| 604 |
+
# is used in handling POSIX-style time zone envi-
|
| 605 |
+
# ronment variables.
|
| 606 |
+
|
| 607 |
+
if ttisgmtcnt:
|
| 608 |
+
isgmt = struct.unpack(">%db" % ttisgmtcnt,
|
| 609 |
+
fileobj.read(ttisgmtcnt))
|
| 610 |
+
|
| 611 |
+
# Build ttinfo list
|
| 612 |
+
out.ttinfo_list = []
|
| 613 |
+
for i in range(typecnt):
|
| 614 |
+
gmtoff, isdst, abbrind = ttinfo[i]
|
| 615 |
+
gmtoff = _get_supported_offset(gmtoff)
|
| 616 |
+
tti = _ttinfo()
|
| 617 |
+
tti.offset = gmtoff
|
| 618 |
+
tti.dstoffset = datetime.timedelta(0)
|
| 619 |
+
tti.delta = datetime.timedelta(seconds=gmtoff)
|
| 620 |
+
tti.isdst = isdst
|
| 621 |
+
tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)]
|
| 622 |
+
tti.isstd = (ttisstdcnt > i and isstd[i] != 0)
|
| 623 |
+
tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0)
|
| 624 |
+
out.ttinfo_list.append(tti)
|
| 625 |
+
|
| 626 |
+
# Replace ttinfo indexes for ttinfo objects.
|
| 627 |
+
out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx]
|
| 628 |
+
|
| 629 |
+
# Set standard, dst, and before ttinfos. before will be
|
| 630 |
+
# used when a given time is before any transitions,
|
| 631 |
+
# and will be set to the first non-dst ttinfo, or to
|
| 632 |
+
# the first dst, if all of them are dst.
|
| 633 |
+
out.ttinfo_std = None
|
| 634 |
+
out.ttinfo_dst = None
|
| 635 |
+
out.ttinfo_before = None
|
| 636 |
+
if out.ttinfo_list:
|
| 637 |
+
if not out.trans_list_utc:
|
| 638 |
+
out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0]
|
| 639 |
+
else:
|
| 640 |
+
for i in range(timecnt-1, -1, -1):
|
| 641 |
+
tti = out.trans_idx[i]
|
| 642 |
+
if not out.ttinfo_std and not tti.isdst:
|
| 643 |
+
out.ttinfo_std = tti
|
| 644 |
+
elif not out.ttinfo_dst and tti.isdst:
|
| 645 |
+
out.ttinfo_dst = tti
|
| 646 |
+
|
| 647 |
+
if out.ttinfo_std and out.ttinfo_dst:
|
| 648 |
+
break
|
| 649 |
+
else:
|
| 650 |
+
if out.ttinfo_dst and not out.ttinfo_std:
|
| 651 |
+
out.ttinfo_std = out.ttinfo_dst
|
| 652 |
+
|
| 653 |
+
for tti in out.ttinfo_list:
|
| 654 |
+
if not tti.isdst:
|
| 655 |
+
out.ttinfo_before = tti
|
| 656 |
+
break
|
| 657 |
+
else:
|
| 658 |
+
out.ttinfo_before = out.ttinfo_list[0]
|
| 659 |
+
|
| 660 |
+
# Now fix transition times to become relative to wall time.
|
| 661 |
+
#
|
| 662 |
+
# I'm not sure about this. In my tests, the tz source file
|
| 663 |
+
# is setup to wall time, and in the binary file isstd and
|
| 664 |
+
# isgmt are off, so it should be in wall time. OTOH, it's
|
| 665 |
+
# always in gmt time. Let me know if you have comments
|
| 666 |
+
# about this.
|
| 667 |
+
lastdst = None
|
| 668 |
+
lastoffset = None
|
| 669 |
+
lastdstoffset = None
|
| 670 |
+
lastbaseoffset = None
|
| 671 |
+
out.trans_list = []
|
| 672 |
+
|
| 673 |
+
for i, tti in enumerate(out.trans_idx):
|
| 674 |
+
offset = tti.offset
|
| 675 |
+
dstoffset = 0
|
| 676 |
+
|
| 677 |
+
if lastdst is not None:
|
| 678 |
+
if tti.isdst:
|
| 679 |
+
if not lastdst:
|
| 680 |
+
dstoffset = offset - lastoffset
|
| 681 |
+
|
| 682 |
+
if not dstoffset and lastdstoffset:
|
| 683 |
+
dstoffset = lastdstoffset
|
| 684 |
+
|
| 685 |
+
tti.dstoffset = datetime.timedelta(seconds=dstoffset)
|
| 686 |
+
lastdstoffset = dstoffset
|
| 687 |
+
|
| 688 |
+
# If a time zone changes its base offset during a DST transition,
|
| 689 |
+
# then you need to adjust by the previous base offset to get the
|
| 690 |
+
# transition time in local time. Otherwise you use the current
|
| 691 |
+
# base offset. Ideally, I would have some mathematical proof of
|
| 692 |
+
# why this is true, but I haven't really thought about it enough.
|
| 693 |
+
baseoffset = offset - dstoffset
|
| 694 |
+
adjustment = baseoffset
|
| 695 |
+
if (lastbaseoffset is not None and baseoffset != lastbaseoffset
|
| 696 |
+
and tti.isdst != lastdst):
|
| 697 |
+
# The base DST has changed
|
| 698 |
+
adjustment = lastbaseoffset
|
| 699 |
+
|
| 700 |
+
lastdst = tti.isdst
|
| 701 |
+
lastoffset = offset
|
| 702 |
+
lastbaseoffset = baseoffset
|
| 703 |
+
|
| 704 |
+
out.trans_list.append(out.trans_list_utc[i] + adjustment)
|
| 705 |
+
|
| 706 |
+
out.trans_idx = tuple(out.trans_idx)
|
| 707 |
+
out.trans_list = tuple(out.trans_list)
|
| 708 |
+
out.trans_list_utc = tuple(out.trans_list_utc)
|
| 709 |
+
|
| 710 |
+
return out
|
| 711 |
+
|
| 712 |
+
def _find_last_transition(self, dt, in_utc=False):
|
| 713 |
+
# If there's no list, there are no transitions to find
|
| 714 |
+
if not self._trans_list:
|
| 715 |
+
return None
|
| 716 |
+
|
| 717 |
+
timestamp = _datetime_to_timestamp(dt)
|
| 718 |
+
|
| 719 |
+
# Find where the timestamp fits in the transition list - if the
|
| 720 |
+
# timestamp is a transition time, it's part of the "after" period.
|
| 721 |
+
trans_list = self._trans_list_utc if in_utc else self._trans_list
|
| 722 |
+
idx = bisect.bisect_right(trans_list, timestamp)
|
| 723 |
+
|
| 724 |
+
# We want to know when the previous transition was, so subtract off 1
|
| 725 |
+
return idx - 1
|
| 726 |
+
|
| 727 |
+
def _get_ttinfo(self, idx):
|
| 728 |
+
# For no list or after the last transition, default to _ttinfo_std
|
| 729 |
+
if idx is None or (idx + 1) >= len(self._trans_list):
|
| 730 |
+
return self._ttinfo_std
|
| 731 |
+
|
| 732 |
+
# If there is a list and the time is before it, return _ttinfo_before
|
| 733 |
+
if idx < 0:
|
| 734 |
+
return self._ttinfo_before
|
| 735 |
+
|
| 736 |
+
return self._trans_idx[idx]
|
| 737 |
+
|
| 738 |
+
def _find_ttinfo(self, dt):
|
| 739 |
+
idx = self._resolve_ambiguous_time(dt)
|
| 740 |
+
|
| 741 |
+
return self._get_ttinfo(idx)
|
| 742 |
+
|
| 743 |
+
def fromutc(self, dt):
|
| 744 |
+
"""
|
| 745 |
+
The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`.
|
| 746 |
+
|
| 747 |
+
:param dt:
|
| 748 |
+
A :py:class:`datetime.datetime` object.
|
| 749 |
+
|
| 750 |
+
:raises TypeError:
|
| 751 |
+
Raised if ``dt`` is not a :py:class:`datetime.datetime` object.
|
| 752 |
+
|
| 753 |
+
:raises ValueError:
|
| 754 |
+
Raised if this is called with a ``dt`` which does not have this
|
| 755 |
+
``tzinfo`` attached.
|
| 756 |
+
|
| 757 |
+
:return:
|
| 758 |
+
Returns a :py:class:`datetime.datetime` object representing the
|
| 759 |
+
wall time in ``self``'s time zone.
|
| 760 |
+
"""
|
| 761 |
+
# These isinstance checks are in datetime.tzinfo, so we'll preserve
|
| 762 |
+
# them, even if we don't care about duck typing.
|
| 763 |
+
if not isinstance(dt, datetime.datetime):
|
| 764 |
+
raise TypeError("fromutc() requires a datetime argument")
|
| 765 |
+
|
| 766 |
+
if dt.tzinfo is not self:
|
| 767 |
+
raise ValueError("dt.tzinfo is not self")
|
| 768 |
+
|
| 769 |
+
# First treat UTC as wall time and get the transition we're in.
|
| 770 |
+
idx = self._find_last_transition(dt, in_utc=True)
|
| 771 |
+
tti = self._get_ttinfo(idx)
|
| 772 |
+
|
| 773 |
+
dt_out = dt + datetime.timedelta(seconds=tti.offset)
|
| 774 |
+
|
| 775 |
+
fold = self.is_ambiguous(dt_out, idx=idx)
|
| 776 |
+
|
| 777 |
+
return enfold(dt_out, fold=int(fold))
|
| 778 |
+
|
| 779 |
+
def is_ambiguous(self, dt, idx=None):
|
| 780 |
+
"""
|
| 781 |
+
Whether or not the "wall time" of a given datetime is ambiguous in this
|
| 782 |
+
zone.
|
| 783 |
+
|
| 784 |
+
:param dt:
|
| 785 |
+
A :py:class:`datetime.datetime`, naive or time zone aware.
|
| 786 |
+
|
| 787 |
+
|
| 788 |
+
:return:
|
| 789 |
+
Returns ``True`` if ambiguous, ``False`` otherwise.
|
| 790 |
+
|
| 791 |
+
.. versionadded:: 2.6.0
|
| 792 |
+
"""
|
| 793 |
+
if idx is None:
|
| 794 |
+
idx = self._find_last_transition(dt)
|
| 795 |
+
|
| 796 |
+
# Calculate the difference in offsets from current to previous
|
| 797 |
+
timestamp = _datetime_to_timestamp(dt)
|
| 798 |
+
tti = self._get_ttinfo(idx)
|
| 799 |
+
|
| 800 |
+
if idx is None or idx <= 0:
|
| 801 |
+
return False
|
| 802 |
+
|
| 803 |
+
od = self._get_ttinfo(idx - 1).offset - tti.offset
|
| 804 |
+
tt = self._trans_list[idx] # Transition time
|
| 805 |
+
|
| 806 |
+
return timestamp < tt + od
|
| 807 |
+
|
| 808 |
+
def _resolve_ambiguous_time(self, dt):
|
| 809 |
+
idx = self._find_last_transition(dt)
|
| 810 |
+
|
| 811 |
+
# If we have no transitions, return the index
|
| 812 |
+
_fold = self._fold(dt)
|
| 813 |
+
if idx is None or idx == 0:
|
| 814 |
+
return idx
|
| 815 |
+
|
| 816 |
+
# If it's ambiguous and we're in a fold, shift to a different index.
|
| 817 |
+
idx_offset = int(not _fold and self.is_ambiguous(dt, idx))
|
| 818 |
+
|
| 819 |
+
return idx - idx_offset
|
| 820 |
+
|
| 821 |
+
def utcoffset(self, dt):
|
| 822 |
+
if dt is None:
|
| 823 |
+
return None
|
| 824 |
+
|
| 825 |
+
if not self._ttinfo_std:
|
| 826 |
+
return ZERO
|
| 827 |
+
|
| 828 |
+
return self._find_ttinfo(dt).delta
|
| 829 |
+
|
| 830 |
+
def dst(self, dt):
|
| 831 |
+
if dt is None:
|
| 832 |
+
return None
|
| 833 |
+
|
| 834 |
+
if not self._ttinfo_dst:
|
| 835 |
+
return ZERO
|
| 836 |
+
|
| 837 |
+
tti = self._find_ttinfo(dt)
|
| 838 |
+
|
| 839 |
+
if not tti.isdst:
|
| 840 |
+
return ZERO
|
| 841 |
+
|
| 842 |
+
# The documentation says that utcoffset()-dst() must
|
| 843 |
+
# be constant for every dt.
|
| 844 |
+
return tti.dstoffset
|
| 845 |
+
|
| 846 |
+
@tzname_in_python2
|
| 847 |
+
def tzname(self, dt):
|
| 848 |
+
if not self._ttinfo_std or dt is None:
|
| 849 |
+
return None
|
| 850 |
+
return self._find_ttinfo(dt).abbr
|
| 851 |
+
|
| 852 |
+
def __eq__(self, other):
|
| 853 |
+
if not isinstance(other, tzfile):
|
| 854 |
+
return NotImplemented
|
| 855 |
+
return (self._trans_list == other._trans_list and
|
| 856 |
+
self._trans_idx == other._trans_idx and
|
| 857 |
+
self._ttinfo_list == other._ttinfo_list)
|
| 858 |
+
|
| 859 |
+
__hash__ = None
|
| 860 |
+
|
| 861 |
+
def __ne__(self, other):
|
| 862 |
+
return not (self == other)
|
| 863 |
+
|
| 864 |
+
def __repr__(self):
|
| 865 |
+
return "%s(%s)" % (self.__class__.__name__, repr(self._filename))
|
| 866 |
+
|
| 867 |
+
def __reduce__(self):
|
| 868 |
+
return self.__reduce_ex__(None)
|
| 869 |
+
|
| 870 |
+
def __reduce_ex__(self, protocol):
|
| 871 |
+
return (self.__class__, (None, self._filename), self.__dict__)
|
| 872 |
+
|
| 873 |
+
|
| 874 |
+
class tzrange(tzrangebase):
|
| 875 |
+
"""
|
| 876 |
+
The ``tzrange`` object is a time zone specified by a set of offsets and
|
| 877 |
+
abbreviations, equivalent to the way the ``TZ`` variable can be specified
|
| 878 |
+
in POSIX-like systems, but using Python delta objects to specify DST
|
| 879 |
+
start, end and offsets.
|
| 880 |
+
|
| 881 |
+
:param stdabbr:
|
| 882 |
+
The abbreviation for standard time (e.g. ``'EST'``).
|
| 883 |
+
|
| 884 |
+
:param stdoffset:
|
| 885 |
+
An integer or :class:`datetime.timedelta` object or equivalent
|
| 886 |
+
specifying the base offset from UTC.
|
| 887 |
+
|
| 888 |
+
If unspecified, +00:00 is used.
|
| 889 |
+
|
| 890 |
+
:param dstabbr:
|
| 891 |
+
The abbreviation for DST / "Summer" time (e.g. ``'EDT'``).
|
| 892 |
+
|
| 893 |
+
If specified, with no other DST information, DST is assumed to occur
|
| 894 |
+
and the default behavior or ``dstoffset``, ``start`` and ``end`` is
|
| 895 |
+
used. If unspecified and no other DST information is specified, it
|
| 896 |
+
is assumed that this zone has no DST.
|
| 897 |
+
|
| 898 |
+
If this is unspecified and other DST information is *is* specified,
|
| 899 |
+
DST occurs in the zone but the time zone abbreviation is left
|
| 900 |
+
unchanged.
|
| 901 |
+
|
| 902 |
+
:param dstoffset:
|
| 903 |
+
A an integer or :class:`datetime.timedelta` object or equivalent
|
| 904 |
+
specifying the UTC offset during DST. If unspecified and any other DST
|
| 905 |
+
information is specified, it is assumed to be the STD offset +1 hour.
|
| 906 |
+
|
| 907 |
+
:param start:
|
| 908 |
+
A :class:`relativedelta.relativedelta` object or equivalent specifying
|
| 909 |
+
the time and time of year that daylight savings time starts. To
|
| 910 |
+
specify, for example, that DST starts at 2AM on the 2nd Sunday in
|
| 911 |
+
March, pass:
|
| 912 |
+
|
| 913 |
+
``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))``
|
| 914 |
+
|
| 915 |
+
If unspecified and any other DST information is specified, the default
|
| 916 |
+
value is 2 AM on the first Sunday in April.
|
| 917 |
+
|
| 918 |
+
:param end:
|
| 919 |
+
A :class:`relativedelta.relativedelta` object or equivalent
|
| 920 |
+
representing the time and time of year that daylight savings time
|
| 921 |
+
ends, with the same specification method as in ``start``. One note is
|
| 922 |
+
that this should point to the first time in the *standard* zone, so if
|
| 923 |
+
a transition occurs at 2AM in the DST zone and the clocks are set back
|
| 924 |
+
1 hour to 1AM, set the ``hours`` parameter to +1.
|
| 925 |
+
|
| 926 |
+
|
| 927 |
+
**Examples:**
|
| 928 |
+
|
| 929 |
+
.. testsetup:: tzrange
|
| 930 |
+
|
| 931 |
+
from dateutil.tz import tzrange, tzstr
|
| 932 |
+
|
| 933 |
+
.. doctest:: tzrange
|
| 934 |
+
|
| 935 |
+
>>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT")
|
| 936 |
+
True
|
| 937 |
+
|
| 938 |
+
>>> from dateutil.relativedelta import *
|
| 939 |
+
>>> range1 = tzrange("EST", -18000, "EDT")
|
| 940 |
+
>>> range2 = tzrange("EST", -18000, "EDT", -14400,
|
| 941 |
+
... relativedelta(hours=+2, month=4, day=1,
|
| 942 |
+
... weekday=SU(+1)),
|
| 943 |
+
... relativedelta(hours=+1, month=10, day=31,
|
| 944 |
+
... weekday=SU(-1)))
|
| 945 |
+
>>> tzstr('EST5EDT') == range1 == range2
|
| 946 |
+
True
|
| 947 |
+
|
| 948 |
+
"""
|
| 949 |
+
def __init__(self, stdabbr, stdoffset=None,
|
| 950 |
+
dstabbr=None, dstoffset=None,
|
| 951 |
+
start=None, end=None):
|
| 952 |
+
|
| 953 |
+
global relativedelta
|
| 954 |
+
from dateutil import relativedelta
|
| 955 |
+
|
| 956 |
+
self._std_abbr = stdabbr
|
| 957 |
+
self._dst_abbr = dstabbr
|
| 958 |
+
|
| 959 |
+
try:
|
| 960 |
+
stdoffset = stdoffset.total_seconds()
|
| 961 |
+
except (TypeError, AttributeError):
|
| 962 |
+
pass
|
| 963 |
+
|
| 964 |
+
try:
|
| 965 |
+
dstoffset = dstoffset.total_seconds()
|
| 966 |
+
except (TypeError, AttributeError):
|
| 967 |
+
pass
|
| 968 |
+
|
| 969 |
+
if stdoffset is not None:
|
| 970 |
+
self._std_offset = datetime.timedelta(seconds=stdoffset)
|
| 971 |
+
else:
|
| 972 |
+
self._std_offset = ZERO
|
| 973 |
+
|
| 974 |
+
if dstoffset is not None:
|
| 975 |
+
self._dst_offset = datetime.timedelta(seconds=dstoffset)
|
| 976 |
+
elif dstabbr and stdoffset is not None:
|
| 977 |
+
self._dst_offset = self._std_offset + datetime.timedelta(hours=+1)
|
| 978 |
+
else:
|
| 979 |
+
self._dst_offset = ZERO
|
| 980 |
+
|
| 981 |
+
if dstabbr and start is None:
|
| 982 |
+
self._start_delta = relativedelta.relativedelta(
|
| 983 |
+
hours=+2, month=4, day=1, weekday=relativedelta.SU(+1))
|
| 984 |
+
else:
|
| 985 |
+
self._start_delta = start
|
| 986 |
+
|
| 987 |
+
if dstabbr and end is None:
|
| 988 |
+
self._end_delta = relativedelta.relativedelta(
|
| 989 |
+
hours=+1, month=10, day=31, weekday=relativedelta.SU(-1))
|
| 990 |
+
else:
|
| 991 |
+
self._end_delta = end
|
| 992 |
+
|
| 993 |
+
self._dst_base_offset_ = self._dst_offset - self._std_offset
|
| 994 |
+
self.hasdst = bool(self._start_delta)
|
| 995 |
+
|
| 996 |
+
def transitions(self, year):
|
| 997 |
+
"""
|
| 998 |
+
For a given year, get the DST on and off transition times, expressed
|
| 999 |
+
always on the standard time side. For zones with no transitions, this
|
| 1000 |
+
function returns ``None``.
|
| 1001 |
+
|
| 1002 |
+
:param year:
|
| 1003 |
+
The year whose transitions you would like to query.
|
| 1004 |
+
|
| 1005 |
+
:return:
|
| 1006 |
+
Returns a :class:`tuple` of :class:`datetime.datetime` objects,
|
| 1007 |
+
``(dston, dstoff)`` for zones with an annual DST transition, or
|
| 1008 |
+
``None`` for fixed offset zones.
|
| 1009 |
+
"""
|
| 1010 |
+
if not self.hasdst:
|
| 1011 |
+
return None
|
| 1012 |
+
|
| 1013 |
+
base_year = datetime.datetime(year, 1, 1)
|
| 1014 |
+
|
| 1015 |
+
start = base_year + self._start_delta
|
| 1016 |
+
end = base_year + self._end_delta
|
| 1017 |
+
|
| 1018 |
+
return (start, end)
|
| 1019 |
+
|
| 1020 |
+
def __eq__(self, other):
|
| 1021 |
+
if not isinstance(other, tzrange):
|
| 1022 |
+
return NotImplemented
|
| 1023 |
+
|
| 1024 |
+
return (self._std_abbr == other._std_abbr and
|
| 1025 |
+
self._dst_abbr == other._dst_abbr and
|
| 1026 |
+
self._std_offset == other._std_offset and
|
| 1027 |
+
self._dst_offset == other._dst_offset and
|
| 1028 |
+
self._start_delta == other._start_delta and
|
| 1029 |
+
self._end_delta == other._end_delta)
|
| 1030 |
+
|
| 1031 |
+
@property
|
| 1032 |
+
def _dst_base_offset(self):
|
| 1033 |
+
return self._dst_base_offset_
|
| 1034 |
+
|
| 1035 |
+
|
| 1036 |
+
@six.add_metaclass(_TzStrFactory)
|
| 1037 |
+
class tzstr(tzrange):
|
| 1038 |
+
"""
|
| 1039 |
+
``tzstr`` objects are time zone objects specified by a time-zone string as
|
| 1040 |
+
it would be passed to a ``TZ`` variable on POSIX-style systems (see
|
| 1041 |
+
the `GNU C Library: TZ Variable`_ for more details).
|
| 1042 |
+
|
| 1043 |
+
There is one notable exception, which is that POSIX-style time zones use an
|
| 1044 |
+
inverted offset format, so normally ``GMT+3`` would be parsed as an offset
|
| 1045 |
+
3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an
|
| 1046 |
+
offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX
|
| 1047 |
+
behavior, pass a ``True`` value to ``posix_offset``.
|
| 1048 |
+
|
| 1049 |
+
The :class:`tzrange` object provides the same functionality, but is
|
| 1050 |
+
specified using :class:`relativedelta.relativedelta` objects. rather than
|
| 1051 |
+
strings.
|
| 1052 |
+
|
| 1053 |
+
:param s:
|
| 1054 |
+
A time zone string in ``TZ`` variable format. This can be a
|
| 1055 |
+
:class:`bytes` (2.x: :class:`str`), :class:`str` (2.x:
|
| 1056 |
+
:class:`unicode`) or a stream emitting unicode characters
|
| 1057 |
+
(e.g. :class:`StringIO`).
|
| 1058 |
+
|
| 1059 |
+
:param posix_offset:
|
| 1060 |
+
Optional. If set to ``True``, interpret strings such as ``GMT+3`` or
|
| 1061 |
+
``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the
|
| 1062 |
+
POSIX standard.
|
| 1063 |
+
|
| 1064 |
+
.. caution::
|
| 1065 |
+
|
| 1066 |
+
Prior to version 2.7.0, this function also supported time zones
|
| 1067 |
+
in the format:
|
| 1068 |
+
|
| 1069 |
+
* ``EST5EDT,4,0,6,7200,10,0,26,7200,3600``
|
| 1070 |
+
* ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600``
|
| 1071 |
+
|
| 1072 |
+
This format is non-standard and has been deprecated; this function
|
| 1073 |
+
will raise a :class:`DeprecatedTZFormatWarning` until
|
| 1074 |
+
support is removed in a future version.
|
| 1075 |
+
|
| 1076 |
+
.. _`GNU C Library: TZ Variable`:
|
| 1077 |
+
https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
|
| 1078 |
+
"""
|
| 1079 |
+
def __init__(self, s, posix_offset=False):
|
| 1080 |
+
global parser
|
| 1081 |
+
from dateutil.parser import _parser as parser
|
| 1082 |
+
|
| 1083 |
+
self._s = s
|
| 1084 |
+
|
| 1085 |
+
res = parser._parsetz(s)
|
| 1086 |
+
if res is None or res.any_unused_tokens:
|
| 1087 |
+
raise ValueError("unknown string format")
|
| 1088 |
+
|
| 1089 |
+
# Here we break the compatibility with the TZ variable handling.
|
| 1090 |
+
# GMT-3 actually *means* the timezone -3.
|
| 1091 |
+
if res.stdabbr in ("GMT", "UTC") and not posix_offset:
|
| 1092 |
+
res.stdoffset *= -1
|
| 1093 |
+
|
| 1094 |
+
# We must initialize it first, since _delta() needs
|
| 1095 |
+
# _std_offset and _dst_offset set. Use False in start/end
|
| 1096 |
+
# to avoid building it two times.
|
| 1097 |
+
tzrange.__init__(self, res.stdabbr, res.stdoffset,
|
| 1098 |
+
res.dstabbr, res.dstoffset,
|
| 1099 |
+
start=False, end=False)
|
| 1100 |
+
|
| 1101 |
+
if not res.dstabbr:
|
| 1102 |
+
self._start_delta = None
|
| 1103 |
+
self._end_delta = None
|
| 1104 |
+
else:
|
| 1105 |
+
self._start_delta = self._delta(res.start)
|
| 1106 |
+
if self._start_delta:
|
| 1107 |
+
self._end_delta = self._delta(res.end, isend=1)
|
| 1108 |
+
|
| 1109 |
+
self.hasdst = bool(self._start_delta)
|
| 1110 |
+
|
| 1111 |
+
def _delta(self, x, isend=0):
|
| 1112 |
+
from dateutil import relativedelta
|
| 1113 |
+
kwargs = {}
|
| 1114 |
+
if x.month is not None:
|
| 1115 |
+
kwargs["month"] = x.month
|
| 1116 |
+
if x.weekday is not None:
|
| 1117 |
+
kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week)
|
| 1118 |
+
if x.week > 0:
|
| 1119 |
+
kwargs["day"] = 1
|
| 1120 |
+
else:
|
| 1121 |
+
kwargs["day"] = 31
|
| 1122 |
+
elif x.day:
|
| 1123 |
+
kwargs["day"] = x.day
|
| 1124 |
+
elif x.yday is not None:
|
| 1125 |
+
kwargs["yearday"] = x.yday
|
| 1126 |
+
elif x.jyday is not None:
|
| 1127 |
+
kwargs["nlyearday"] = x.jyday
|
| 1128 |
+
if not kwargs:
|
| 1129 |
+
# Default is to start on first sunday of april, and end
|
| 1130 |
+
# on last sunday of october.
|
| 1131 |
+
if not isend:
|
| 1132 |
+
kwargs["month"] = 4
|
| 1133 |
+
kwargs["day"] = 1
|
| 1134 |
+
kwargs["weekday"] = relativedelta.SU(+1)
|
| 1135 |
+
else:
|
| 1136 |
+
kwargs["month"] = 10
|
| 1137 |
+
kwargs["day"] = 31
|
| 1138 |
+
kwargs["weekday"] = relativedelta.SU(-1)
|
| 1139 |
+
if x.time is not None:
|
| 1140 |
+
kwargs["seconds"] = x.time
|
| 1141 |
+
else:
|
| 1142 |
+
# Default is 2AM.
|
| 1143 |
+
kwargs["seconds"] = 7200
|
| 1144 |
+
if isend:
|
| 1145 |
+
# Convert to standard time, to follow the documented way
|
| 1146 |
+
# of working with the extra hour. See the documentation
|
| 1147 |
+
# of the tzinfo class.
|
| 1148 |
+
delta = self._dst_offset - self._std_offset
|
| 1149 |
+
kwargs["seconds"] -= delta.seconds + delta.days * 86400
|
| 1150 |
+
return relativedelta.relativedelta(**kwargs)
|
| 1151 |
+
|
| 1152 |
+
def __repr__(self):
|
| 1153 |
+
return "%s(%s)" % (self.__class__.__name__, repr(self._s))
|
| 1154 |
+
|
| 1155 |
+
|
| 1156 |
+
class _tzicalvtzcomp(object):
|
| 1157 |
+
def __init__(self, tzoffsetfrom, tzoffsetto, isdst,
|
| 1158 |
+
tzname=None, rrule=None):
|
| 1159 |
+
self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom)
|
| 1160 |
+
self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto)
|
| 1161 |
+
self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom
|
| 1162 |
+
self.isdst = isdst
|
| 1163 |
+
self.tzname = tzname
|
| 1164 |
+
self.rrule = rrule
|
| 1165 |
+
|
| 1166 |
+
|
| 1167 |
+
class _tzicalvtz(_tzinfo):
|
| 1168 |
+
def __init__(self, tzid, comps=[]):
|
| 1169 |
+
super(_tzicalvtz, self).__init__()
|
| 1170 |
+
|
| 1171 |
+
self._tzid = tzid
|
| 1172 |
+
self._comps = comps
|
| 1173 |
+
self._cachedate = []
|
| 1174 |
+
self._cachecomp = []
|
| 1175 |
+
self._cache_lock = _thread.allocate_lock()
|
| 1176 |
+
|
| 1177 |
+
def _find_comp(self, dt):
|
| 1178 |
+
if len(self._comps) == 1:
|
| 1179 |
+
return self._comps[0]
|
| 1180 |
+
|
| 1181 |
+
dt = dt.replace(tzinfo=None)
|
| 1182 |
+
|
| 1183 |
+
try:
|
| 1184 |
+
with self._cache_lock:
|
| 1185 |
+
return self._cachecomp[self._cachedate.index(
|
| 1186 |
+
(dt, self._fold(dt)))]
|
| 1187 |
+
except ValueError:
|
| 1188 |
+
pass
|
| 1189 |
+
|
| 1190 |
+
lastcompdt = None
|
| 1191 |
+
lastcomp = None
|
| 1192 |
+
|
| 1193 |
+
for comp in self._comps:
|
| 1194 |
+
compdt = self._find_compdt(comp, dt)
|
| 1195 |
+
|
| 1196 |
+
if compdt and (not lastcompdt or lastcompdt < compdt):
|
| 1197 |
+
lastcompdt = compdt
|
| 1198 |
+
lastcomp = comp
|
| 1199 |
+
|
| 1200 |
+
if not lastcomp:
|
| 1201 |
+
# RFC says nothing about what to do when a given
|
| 1202 |
+
# time is before the first onset date. We'll look for the
|
| 1203 |
+
# first standard component, or the first component, if
|
| 1204 |
+
# none is found.
|
| 1205 |
+
for comp in self._comps:
|
| 1206 |
+
if not comp.isdst:
|
| 1207 |
+
lastcomp = comp
|
| 1208 |
+
break
|
| 1209 |
+
else:
|
| 1210 |
+
lastcomp = comp[0]
|
| 1211 |
+
|
| 1212 |
+
with self._cache_lock:
|
| 1213 |
+
self._cachedate.insert(0, (dt, self._fold(dt)))
|
| 1214 |
+
self._cachecomp.insert(0, lastcomp)
|
| 1215 |
+
|
| 1216 |
+
if len(self._cachedate) > 10:
|
| 1217 |
+
self._cachedate.pop()
|
| 1218 |
+
self._cachecomp.pop()
|
| 1219 |
+
|
| 1220 |
+
return lastcomp
|
| 1221 |
+
|
| 1222 |
+
def _find_compdt(self, comp, dt):
|
| 1223 |
+
if comp.tzoffsetdiff < ZERO and self._fold(dt):
|
| 1224 |
+
dt -= comp.tzoffsetdiff
|
| 1225 |
+
|
| 1226 |
+
compdt = comp.rrule.before(dt, inc=True)
|
| 1227 |
+
|
| 1228 |
+
return compdt
|
| 1229 |
+
|
| 1230 |
+
def utcoffset(self, dt):
|
| 1231 |
+
if dt is None:
|
| 1232 |
+
return None
|
| 1233 |
+
|
| 1234 |
+
return self._find_comp(dt).tzoffsetto
|
| 1235 |
+
|
| 1236 |
+
def dst(self, dt):
|
| 1237 |
+
comp = self._find_comp(dt)
|
| 1238 |
+
if comp.isdst:
|
| 1239 |
+
return comp.tzoffsetdiff
|
| 1240 |
+
else:
|
| 1241 |
+
return ZERO
|
| 1242 |
+
|
| 1243 |
+
@tzname_in_python2
|
| 1244 |
+
def tzname(self, dt):
|
| 1245 |
+
return self._find_comp(dt).tzname
|
| 1246 |
+
|
| 1247 |
+
def __repr__(self):
|
| 1248 |
+
return "<tzicalvtz %s>" % repr(self._tzid)
|
| 1249 |
+
|
| 1250 |
+
__reduce__ = object.__reduce__
|
| 1251 |
+
|
| 1252 |
+
|
| 1253 |
+
class tzical(object):
|
| 1254 |
+
"""
|
| 1255 |
+
This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure
|
| 1256 |
+
as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects.
|
| 1257 |
+
|
| 1258 |
+
:param `fileobj`:
|
| 1259 |
+
A file or stream in iCalendar format, which should be UTF-8 encoded
|
| 1260 |
+
with CRLF endings.
|
| 1261 |
+
|
| 1262 |
+
.. _`RFC 5545`: https://tools.ietf.org/html/rfc5545
|
| 1263 |
+
"""
|
| 1264 |
+
def __init__(self, fileobj):
|
| 1265 |
+
global rrule
|
| 1266 |
+
from dateutil import rrule
|
| 1267 |
+
|
| 1268 |
+
if isinstance(fileobj, string_types):
|
| 1269 |
+
self._s = fileobj
|
| 1270 |
+
# ical should be encoded in UTF-8 with CRLF
|
| 1271 |
+
fileobj = open(fileobj, 'r')
|
| 1272 |
+
else:
|
| 1273 |
+
self._s = getattr(fileobj, 'name', repr(fileobj))
|
| 1274 |
+
fileobj = _nullcontext(fileobj)
|
| 1275 |
+
|
| 1276 |
+
self._vtz = {}
|
| 1277 |
+
|
| 1278 |
+
with fileobj as fobj:
|
| 1279 |
+
self._parse_rfc(fobj.read())
|
| 1280 |
+
|
| 1281 |
+
def keys(self):
|
| 1282 |
+
"""
|
| 1283 |
+
Retrieves the available time zones as a list.
|
| 1284 |
+
"""
|
| 1285 |
+
return list(self._vtz.keys())
|
| 1286 |
+
|
| 1287 |
+
def get(self, tzid=None):
|
| 1288 |
+
"""
|
| 1289 |
+
Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``.
|
| 1290 |
+
|
| 1291 |
+
:param tzid:
|
| 1292 |
+
If there is exactly one time zone available, omitting ``tzid``
|
| 1293 |
+
or passing :py:const:`None` value returns it. Otherwise a valid
|
| 1294 |
+
key (which can be retrieved from :func:`keys`) is required.
|
| 1295 |
+
|
| 1296 |
+
:raises ValueError:
|
| 1297 |
+
Raised if ``tzid`` is not specified but there are either more
|
| 1298 |
+
or fewer than 1 zone defined.
|
| 1299 |
+
|
| 1300 |
+
:returns:
|
| 1301 |
+
Returns either a :py:class:`datetime.tzinfo` object representing
|
| 1302 |
+
the relevant time zone or :py:const:`None` if the ``tzid`` was
|
| 1303 |
+
not found.
|
| 1304 |
+
"""
|
| 1305 |
+
if tzid is None:
|
| 1306 |
+
if len(self._vtz) == 0:
|
| 1307 |
+
raise ValueError("no timezones defined")
|
| 1308 |
+
elif len(self._vtz) > 1:
|
| 1309 |
+
raise ValueError("more than one timezone available")
|
| 1310 |
+
tzid = next(iter(self._vtz))
|
| 1311 |
+
|
| 1312 |
+
return self._vtz.get(tzid)
|
| 1313 |
+
|
| 1314 |
+
def _parse_offset(self, s):
|
| 1315 |
+
s = s.strip()
|
| 1316 |
+
if not s:
|
| 1317 |
+
raise ValueError("empty offset")
|
| 1318 |
+
if s[0] in ('+', '-'):
|
| 1319 |
+
signal = (-1, +1)[s[0] == '+']
|
| 1320 |
+
s = s[1:]
|
| 1321 |
+
else:
|
| 1322 |
+
signal = +1
|
| 1323 |
+
if len(s) == 4:
|
| 1324 |
+
return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal
|
| 1325 |
+
elif len(s) == 6:
|
| 1326 |
+
return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal
|
| 1327 |
+
else:
|
| 1328 |
+
raise ValueError("invalid offset: " + s)
|
| 1329 |
+
|
| 1330 |
+
def _parse_rfc(self, s):
|
| 1331 |
+
lines = s.splitlines()
|
| 1332 |
+
if not lines:
|
| 1333 |
+
raise ValueError("empty string")
|
| 1334 |
+
|
| 1335 |
+
# Unfold
|
| 1336 |
+
i = 0
|
| 1337 |
+
while i < len(lines):
|
| 1338 |
+
line = lines[i].rstrip()
|
| 1339 |
+
if not line:
|
| 1340 |
+
del lines[i]
|
| 1341 |
+
elif i > 0 and line[0] == " ":
|
| 1342 |
+
lines[i-1] += line[1:]
|
| 1343 |
+
del lines[i]
|
| 1344 |
+
else:
|
| 1345 |
+
i += 1
|
| 1346 |
+
|
| 1347 |
+
tzid = None
|
| 1348 |
+
comps = []
|
| 1349 |
+
invtz = False
|
| 1350 |
+
comptype = None
|
| 1351 |
+
for line in lines:
|
| 1352 |
+
if not line:
|
| 1353 |
+
continue
|
| 1354 |
+
name, value = line.split(':', 1)
|
| 1355 |
+
parms = name.split(';')
|
| 1356 |
+
if not parms:
|
| 1357 |
+
raise ValueError("empty property name")
|
| 1358 |
+
name = parms[0].upper()
|
| 1359 |
+
parms = parms[1:]
|
| 1360 |
+
if invtz:
|
| 1361 |
+
if name == "BEGIN":
|
| 1362 |
+
if value in ("STANDARD", "DAYLIGHT"):
|
| 1363 |
+
# Process component
|
| 1364 |
+
pass
|
| 1365 |
+
else:
|
| 1366 |
+
raise ValueError("unknown component: "+value)
|
| 1367 |
+
comptype = value
|
| 1368 |
+
founddtstart = False
|
| 1369 |
+
tzoffsetfrom = None
|
| 1370 |
+
tzoffsetto = None
|
| 1371 |
+
rrulelines = []
|
| 1372 |
+
tzname = None
|
| 1373 |
+
elif name == "END":
|
| 1374 |
+
if value == "VTIMEZONE":
|
| 1375 |
+
if comptype:
|
| 1376 |
+
raise ValueError("component not closed: "+comptype)
|
| 1377 |
+
if not tzid:
|
| 1378 |
+
raise ValueError("mandatory TZID not found")
|
| 1379 |
+
if not comps:
|
| 1380 |
+
raise ValueError(
|
| 1381 |
+
"at least one component is needed")
|
| 1382 |
+
# Process vtimezone
|
| 1383 |
+
self._vtz[tzid] = _tzicalvtz(tzid, comps)
|
| 1384 |
+
invtz = False
|
| 1385 |
+
elif value == comptype:
|
| 1386 |
+
if not founddtstart:
|
| 1387 |
+
raise ValueError("mandatory DTSTART not found")
|
| 1388 |
+
if tzoffsetfrom is None:
|
| 1389 |
+
raise ValueError(
|
| 1390 |
+
"mandatory TZOFFSETFROM not found")
|
| 1391 |
+
if tzoffsetto is None:
|
| 1392 |
+
raise ValueError(
|
| 1393 |
+
"mandatory TZOFFSETFROM not found")
|
| 1394 |
+
# Process component
|
| 1395 |
+
rr = None
|
| 1396 |
+
if rrulelines:
|
| 1397 |
+
rr = rrule.rrulestr("\n".join(rrulelines),
|
| 1398 |
+
compatible=True,
|
| 1399 |
+
ignoretz=True,
|
| 1400 |
+
cache=True)
|
| 1401 |
+
comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto,
|
| 1402 |
+
(comptype == "DAYLIGHT"),
|
| 1403 |
+
tzname, rr)
|
| 1404 |
+
comps.append(comp)
|
| 1405 |
+
comptype = None
|
| 1406 |
+
else:
|
| 1407 |
+
raise ValueError("invalid component end: "+value)
|
| 1408 |
+
elif comptype:
|
| 1409 |
+
if name == "DTSTART":
|
| 1410 |
+
# DTSTART in VTIMEZONE takes a subset of valid RRULE
|
| 1411 |
+
# values under RFC 5545.
|
| 1412 |
+
for parm in parms:
|
| 1413 |
+
if parm != 'VALUE=DATE-TIME':
|
| 1414 |
+
msg = ('Unsupported DTSTART param in ' +
|
| 1415 |
+
'VTIMEZONE: ' + parm)
|
| 1416 |
+
raise ValueError(msg)
|
| 1417 |
+
rrulelines.append(line)
|
| 1418 |
+
founddtstart = True
|
| 1419 |
+
elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"):
|
| 1420 |
+
rrulelines.append(line)
|
| 1421 |
+
elif name == "TZOFFSETFROM":
|
| 1422 |
+
if parms:
|
| 1423 |
+
raise ValueError(
|
| 1424 |
+
"unsupported %s parm: %s " % (name, parms[0]))
|
| 1425 |
+
tzoffsetfrom = self._parse_offset(value)
|
| 1426 |
+
elif name == "TZOFFSETTO":
|
| 1427 |
+
if parms:
|
| 1428 |
+
raise ValueError(
|
| 1429 |
+
"unsupported TZOFFSETTO parm: "+parms[0])
|
| 1430 |
+
tzoffsetto = self._parse_offset(value)
|
| 1431 |
+
elif name == "TZNAME":
|
| 1432 |
+
if parms:
|
| 1433 |
+
raise ValueError(
|
| 1434 |
+
"unsupported TZNAME parm: "+parms[0])
|
| 1435 |
+
tzname = value
|
| 1436 |
+
elif name == "COMMENT":
|
| 1437 |
+
pass
|
| 1438 |
+
else:
|
| 1439 |
+
raise ValueError("unsupported property: "+name)
|
| 1440 |
+
else:
|
| 1441 |
+
if name == "TZID":
|
| 1442 |
+
if parms:
|
| 1443 |
+
raise ValueError(
|
| 1444 |
+
"unsupported TZID parm: "+parms[0])
|
| 1445 |
+
tzid = value
|
| 1446 |
+
elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"):
|
| 1447 |
+
pass
|
| 1448 |
+
else:
|
| 1449 |
+
raise ValueError("unsupported property: "+name)
|
| 1450 |
+
elif name == "BEGIN" and value == "VTIMEZONE":
|
| 1451 |
+
tzid = None
|
| 1452 |
+
comps = []
|
| 1453 |
+
invtz = True
|
| 1454 |
+
|
| 1455 |
+
def __repr__(self):
|
| 1456 |
+
return "%s(%s)" % (self.__class__.__name__, repr(self._s))
|
| 1457 |
+
|
| 1458 |
+
|
| 1459 |
+
if sys.platform != "win32":
|
| 1460 |
+
TZFILES = ["/etc/localtime", "localtime"]
|
| 1461 |
+
TZPATHS = ["/usr/share/zoneinfo",
|
| 1462 |
+
"/usr/lib/zoneinfo",
|
| 1463 |
+
"/usr/share/lib/zoneinfo",
|
| 1464 |
+
"/etc/zoneinfo"]
|
| 1465 |
+
else:
|
| 1466 |
+
TZFILES = []
|
| 1467 |
+
TZPATHS = []
|
| 1468 |
+
|
| 1469 |
+
|
| 1470 |
+
def __get_gettz():
|
| 1471 |
+
tzlocal_classes = (tzlocal,)
|
| 1472 |
+
if tzwinlocal is not None:
|
| 1473 |
+
tzlocal_classes += (tzwinlocal,)
|
| 1474 |
+
|
| 1475 |
+
class GettzFunc(object):
|
| 1476 |
+
"""
|
| 1477 |
+
Retrieve a time zone object from a string representation
|
| 1478 |
+
|
| 1479 |
+
This function is intended to retrieve the :py:class:`tzinfo` subclass
|
| 1480 |
+
that best represents the time zone that would be used if a POSIX
|
| 1481 |
+
`TZ variable`_ were set to the same value.
|
| 1482 |
+
|
| 1483 |
+
If no argument or an empty string is passed to ``gettz``, local time
|
| 1484 |
+
is returned:
|
| 1485 |
+
|
| 1486 |
+
.. code-block:: python3
|
| 1487 |
+
|
| 1488 |
+
>>> gettz()
|
| 1489 |
+
tzfile('/etc/localtime')
|
| 1490 |
+
|
| 1491 |
+
This function is also the preferred way to map IANA tz database keys
|
| 1492 |
+
to :class:`tzfile` objects:
|
| 1493 |
+
|
| 1494 |
+
.. code-block:: python3
|
| 1495 |
+
|
| 1496 |
+
>>> gettz('Pacific/Kiritimati')
|
| 1497 |
+
tzfile('/usr/share/zoneinfo/Pacific/Kiritimati')
|
| 1498 |
+
|
| 1499 |
+
On Windows, the standard is extended to include the Windows-specific
|
| 1500 |
+
zone names provided by the operating system:
|
| 1501 |
+
|
| 1502 |
+
.. code-block:: python3
|
| 1503 |
+
|
| 1504 |
+
>>> gettz('Egypt Standard Time')
|
| 1505 |
+
tzwin('Egypt Standard Time')
|
| 1506 |
+
|
| 1507 |
+
Passing a GNU ``TZ`` style string time zone specification returns a
|
| 1508 |
+
:class:`tzstr` object:
|
| 1509 |
+
|
| 1510 |
+
.. code-block:: python3
|
| 1511 |
+
|
| 1512 |
+
>>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
|
| 1513 |
+
tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3')
|
| 1514 |
+
|
| 1515 |
+
:param name:
|
| 1516 |
+
A time zone name (IANA, or, on Windows, Windows keys), location of
|
| 1517 |
+
a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone
|
| 1518 |
+
specifier. An empty string, no argument or ``None`` is interpreted
|
| 1519 |
+
as local time.
|
| 1520 |
+
|
| 1521 |
+
:return:
|
| 1522 |
+
Returns an instance of one of ``dateutil``'s :py:class:`tzinfo`
|
| 1523 |
+
subclasses.
|
| 1524 |
+
|
| 1525 |
+
.. versionchanged:: 2.7.0
|
| 1526 |
+
|
| 1527 |
+
After version 2.7.0, any two calls to ``gettz`` using the same
|
| 1528 |
+
input strings will return the same object:
|
| 1529 |
+
|
| 1530 |
+
.. code-block:: python3
|
| 1531 |
+
|
| 1532 |
+
>>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago')
|
| 1533 |
+
True
|
| 1534 |
+
|
| 1535 |
+
In addition to improving performance, this ensures that
|
| 1536 |
+
`"same zone" semantics`_ are used for datetimes in the same zone.
|
| 1537 |
+
|
| 1538 |
+
|
| 1539 |
+
.. _`TZ variable`:
|
| 1540 |
+
https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
|
| 1541 |
+
|
| 1542 |
+
.. _`"same zone" semantics`:
|
| 1543 |
+
https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html
|
| 1544 |
+
"""
|
| 1545 |
+
def __init__(self):
|
| 1546 |
+
|
| 1547 |
+
self.__instances = weakref.WeakValueDictionary()
|
| 1548 |
+
self.__strong_cache_size = 8
|
| 1549 |
+
self.__strong_cache = OrderedDict()
|
| 1550 |
+
self._cache_lock = _thread.allocate_lock()
|
| 1551 |
+
|
| 1552 |
+
def __call__(self, name=None):
|
| 1553 |
+
with self._cache_lock:
|
| 1554 |
+
rv = self.__instances.get(name, None)
|
| 1555 |
+
|
| 1556 |
+
if rv is None:
|
| 1557 |
+
rv = self.nocache(name=name)
|
| 1558 |
+
if not (name is None
|
| 1559 |
+
or isinstance(rv, tzlocal_classes)
|
| 1560 |
+
or rv is None):
|
| 1561 |
+
# tzlocal is slightly more complicated than the other
|
| 1562 |
+
# time zone providers because it depends on environment
|
| 1563 |
+
# at construction time, so don't cache that.
|
| 1564 |
+
#
|
| 1565 |
+
# We also cannot store weak references to None, so we
|
| 1566 |
+
# will also not store that.
|
| 1567 |
+
self.__instances[name] = rv
|
| 1568 |
+
else:
|
| 1569 |
+
# No need for strong caching, return immediately
|
| 1570 |
+
return rv
|
| 1571 |
+
|
| 1572 |
+
self.__strong_cache[name] = self.__strong_cache.pop(name, rv)
|
| 1573 |
+
|
| 1574 |
+
if len(self.__strong_cache) > self.__strong_cache_size:
|
| 1575 |
+
self.__strong_cache.popitem(last=False)
|
| 1576 |
+
|
| 1577 |
+
return rv
|
| 1578 |
+
|
| 1579 |
+
def set_cache_size(self, size):
|
| 1580 |
+
with self._cache_lock:
|
| 1581 |
+
self.__strong_cache_size = size
|
| 1582 |
+
while len(self.__strong_cache) > size:
|
| 1583 |
+
self.__strong_cache.popitem(last=False)
|
| 1584 |
+
|
| 1585 |
+
def cache_clear(self):
|
| 1586 |
+
with self._cache_lock:
|
| 1587 |
+
self.__instances = weakref.WeakValueDictionary()
|
| 1588 |
+
self.__strong_cache.clear()
|
| 1589 |
+
|
| 1590 |
+
@staticmethod
|
| 1591 |
+
def nocache(name=None):
|
| 1592 |
+
"""A non-cached version of gettz"""
|
| 1593 |
+
tz = None
|
| 1594 |
+
if not name:
|
| 1595 |
+
try:
|
| 1596 |
+
name = os.environ["TZ"]
|
| 1597 |
+
except KeyError:
|
| 1598 |
+
pass
|
| 1599 |
+
if name is None or name in ("", ":"):
|
| 1600 |
+
for filepath in TZFILES:
|
| 1601 |
+
if not os.path.isabs(filepath):
|
| 1602 |
+
filename = filepath
|
| 1603 |
+
for path in TZPATHS:
|
| 1604 |
+
filepath = os.path.join(path, filename)
|
| 1605 |
+
if os.path.isfile(filepath):
|
| 1606 |
+
break
|
| 1607 |
+
else:
|
| 1608 |
+
continue
|
| 1609 |
+
if os.path.isfile(filepath):
|
| 1610 |
+
try:
|
| 1611 |
+
tz = tzfile(filepath)
|
| 1612 |
+
break
|
| 1613 |
+
except (IOError, OSError, ValueError):
|
| 1614 |
+
pass
|
| 1615 |
+
else:
|
| 1616 |
+
tz = tzlocal()
|
| 1617 |
+
else:
|
| 1618 |
+
try:
|
| 1619 |
+
if name.startswith(":"):
|
| 1620 |
+
name = name[1:]
|
| 1621 |
+
except TypeError as e:
|
| 1622 |
+
if isinstance(name, bytes):
|
| 1623 |
+
new_msg = "gettz argument should be str, not bytes"
|
| 1624 |
+
six.raise_from(TypeError(new_msg), e)
|
| 1625 |
+
else:
|
| 1626 |
+
raise
|
| 1627 |
+
if os.path.isabs(name):
|
| 1628 |
+
if os.path.isfile(name):
|
| 1629 |
+
tz = tzfile(name)
|
| 1630 |
+
else:
|
| 1631 |
+
tz = None
|
| 1632 |
+
else:
|
| 1633 |
+
for path in TZPATHS:
|
| 1634 |
+
filepath = os.path.join(path, name)
|
| 1635 |
+
if not os.path.isfile(filepath):
|
| 1636 |
+
filepath = filepath.replace(' ', '_')
|
| 1637 |
+
if not os.path.isfile(filepath):
|
| 1638 |
+
continue
|
| 1639 |
+
try:
|
| 1640 |
+
tz = tzfile(filepath)
|
| 1641 |
+
break
|
| 1642 |
+
except (IOError, OSError, ValueError):
|
| 1643 |
+
pass
|
| 1644 |
+
else:
|
| 1645 |
+
tz = None
|
| 1646 |
+
if tzwin is not None:
|
| 1647 |
+
try:
|
| 1648 |
+
tz = tzwin(name)
|
| 1649 |
+
except (WindowsError, UnicodeEncodeError):
|
| 1650 |
+
# UnicodeEncodeError is for Python 2.7 compat
|
| 1651 |
+
tz = None
|
| 1652 |
+
|
| 1653 |
+
if not tz:
|
| 1654 |
+
from dateutil.zoneinfo import get_zonefile_instance
|
| 1655 |
+
tz = get_zonefile_instance().get(name)
|
| 1656 |
+
|
| 1657 |
+
if not tz:
|
| 1658 |
+
for c in name:
|
| 1659 |
+
# name is not a tzstr unless it has at least
|
| 1660 |
+
# one offset. For short values of "name", an
|
| 1661 |
+
# explicit for loop seems to be the fastest way
|
| 1662 |
+
# To determine if a string contains a digit
|
| 1663 |
+
if c in "0123456789":
|
| 1664 |
+
try:
|
| 1665 |
+
tz = tzstr(name)
|
| 1666 |
+
except ValueError:
|
| 1667 |
+
pass
|
| 1668 |
+
break
|
| 1669 |
+
else:
|
| 1670 |
+
if name in ("GMT", "UTC"):
|
| 1671 |
+
tz = UTC
|
| 1672 |
+
elif name in time.tzname:
|
| 1673 |
+
tz = tzlocal()
|
| 1674 |
+
return tz
|
| 1675 |
+
|
| 1676 |
+
return GettzFunc()
|
| 1677 |
+
|
| 1678 |
+
|
| 1679 |
+
gettz = __get_gettz()
|
| 1680 |
+
del __get_gettz
|
| 1681 |
+
|
| 1682 |
+
|
| 1683 |
+
def datetime_exists(dt, tz=None):
|
| 1684 |
+
"""
|
| 1685 |
+
Given a datetime and a time zone, determine whether or not a given datetime
|
| 1686 |
+
would fall in a gap.
|
| 1687 |
+
|
| 1688 |
+
:param dt:
|
| 1689 |
+
A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
|
| 1690 |
+
is provided.)
|
| 1691 |
+
|
| 1692 |
+
:param tz:
|
| 1693 |
+
A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
|
| 1694 |
+
``None`` or not provided, the datetime's own time zone will be used.
|
| 1695 |
+
|
| 1696 |
+
:return:
|
| 1697 |
+
Returns a boolean value whether or not the "wall time" exists in
|
| 1698 |
+
``tz``.
|
| 1699 |
+
|
| 1700 |
+
.. versionadded:: 2.7.0
|
| 1701 |
+
"""
|
| 1702 |
+
if tz is None:
|
| 1703 |
+
if dt.tzinfo is None:
|
| 1704 |
+
raise ValueError('Datetime is naive and no time zone provided.')
|
| 1705 |
+
tz = dt.tzinfo
|
| 1706 |
+
|
| 1707 |
+
dt = dt.replace(tzinfo=None)
|
| 1708 |
+
|
| 1709 |
+
# This is essentially a test of whether or not the datetime can survive
|
| 1710 |
+
# a round trip to UTC.
|
| 1711 |
+
dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz)
|
| 1712 |
+
dt_rt = dt_rt.replace(tzinfo=None)
|
| 1713 |
+
|
| 1714 |
+
return dt == dt_rt
|
| 1715 |
+
|
| 1716 |
+
|
| 1717 |
+
def datetime_ambiguous(dt, tz=None):
|
| 1718 |
+
"""
|
| 1719 |
+
Given a datetime and a time zone, determine whether or not a given datetime
|
| 1720 |
+
is ambiguous (i.e if there are two times differentiated only by their DST
|
| 1721 |
+
status).
|
| 1722 |
+
|
| 1723 |
+
:param dt:
|
| 1724 |
+
A :class:`datetime.datetime` (whose time zone will be ignored if ``tz``
|
| 1725 |
+
is provided.)
|
| 1726 |
+
|
| 1727 |
+
:param tz:
|
| 1728 |
+
A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If
|
| 1729 |
+
``None`` or not provided, the datetime's own time zone will be used.
|
| 1730 |
+
|
| 1731 |
+
:return:
|
| 1732 |
+
Returns a boolean value whether or not the "wall time" is ambiguous in
|
| 1733 |
+
``tz``.
|
| 1734 |
+
|
| 1735 |
+
.. versionadded:: 2.6.0
|
| 1736 |
+
"""
|
| 1737 |
+
if tz is None:
|
| 1738 |
+
if dt.tzinfo is None:
|
| 1739 |
+
raise ValueError('Datetime is naive and no time zone provided.')
|
| 1740 |
+
|
| 1741 |
+
tz = dt.tzinfo
|
| 1742 |
+
|
| 1743 |
+
# If a time zone defines its own "is_ambiguous" function, we'll use that.
|
| 1744 |
+
is_ambiguous_fn = getattr(tz, 'is_ambiguous', None)
|
| 1745 |
+
if is_ambiguous_fn is not None:
|
| 1746 |
+
try:
|
| 1747 |
+
return tz.is_ambiguous(dt)
|
| 1748 |
+
except Exception:
|
| 1749 |
+
pass
|
| 1750 |
+
|
| 1751 |
+
# If it doesn't come out and tell us it's ambiguous, we'll just check if
|
| 1752 |
+
# the fold attribute has any effect on this particular date and time.
|
| 1753 |
+
dt = dt.replace(tzinfo=tz)
|
| 1754 |
+
wall_0 = enfold(dt, fold=0)
|
| 1755 |
+
wall_1 = enfold(dt, fold=1)
|
| 1756 |
+
|
| 1757 |
+
same_offset = wall_0.utcoffset() == wall_1.utcoffset()
|
| 1758 |
+
same_dst = wall_0.dst() == wall_1.dst()
|
| 1759 |
+
|
| 1760 |
+
return not (same_offset and same_dst)
|
| 1761 |
+
|
| 1762 |
+
|
| 1763 |
+
def resolve_imaginary(dt):
|
| 1764 |
+
"""
|
| 1765 |
+
Given a datetime that may be imaginary, return an existing datetime.
|
| 1766 |
+
|
| 1767 |
+
This function assumes that an imaginary datetime represents what the
|
| 1768 |
+
wall time would be in a zone had the offset transition not occurred, so
|
| 1769 |
+
it will always fall forward by the transition's change in offset.
|
| 1770 |
+
|
| 1771 |
+
.. doctest::
|
| 1772 |
+
|
| 1773 |
+
>>> from dateutil import tz
|
| 1774 |
+
>>> from datetime import datetime
|
| 1775 |
+
>>> NYC = tz.gettz('America/New_York')
|
| 1776 |
+
>>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC)))
|
| 1777 |
+
2017-03-12 03:30:00-04:00
|
| 1778 |
+
|
| 1779 |
+
>>> KIR = tz.gettz('Pacific/Kiritimati')
|
| 1780 |
+
>>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR)))
|
| 1781 |
+
1995-01-02 12:30:00+14:00
|
| 1782 |
+
|
| 1783 |
+
As a note, :func:`datetime.astimezone` is guaranteed to produce a valid,
|
| 1784 |
+
existing datetime, so a round-trip to and from UTC is sufficient to get
|
| 1785 |
+
an extant datetime, however, this generally "falls back" to an earlier time
|
| 1786 |
+
rather than falling forward to the STD side (though no guarantees are made
|
| 1787 |
+
about this behavior).
|
| 1788 |
+
|
| 1789 |
+
:param dt:
|
| 1790 |
+
A :class:`datetime.datetime` which may or may not exist.
|
| 1791 |
+
|
| 1792 |
+
:return:
|
| 1793 |
+
Returns an existing :class:`datetime.datetime`. If ``dt`` was not
|
| 1794 |
+
imaginary, the datetime returned is guaranteed to be the same object
|
| 1795 |
+
passed to the function.
|
| 1796 |
+
|
| 1797 |
+
.. versionadded:: 2.7.0
|
| 1798 |
+
"""
|
| 1799 |
+
if dt.tzinfo is not None and not datetime_exists(dt):
|
| 1800 |
+
|
| 1801 |
+
curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset()
|
| 1802 |
+
old_offset = (dt - datetime.timedelta(hours=24)).utcoffset()
|
| 1803 |
+
|
| 1804 |
+
dt += curr_offset - old_offset
|
| 1805 |
+
|
| 1806 |
+
return dt
|
| 1807 |
+
|
| 1808 |
+
|
| 1809 |
+
def _datetime_to_timestamp(dt):
|
| 1810 |
+
"""
|
| 1811 |
+
Convert a :class:`datetime.datetime` object to an epoch timestamp in
|
| 1812 |
+
seconds since January 1, 1970, ignoring the time zone.
|
| 1813 |
+
"""
|
| 1814 |
+
return (dt.replace(tzinfo=None) - EPOCH).total_seconds()
|
| 1815 |
+
|
| 1816 |
+
|
| 1817 |
+
if sys.version_info >= (3, 6):
|
| 1818 |
+
def _get_supported_offset(second_offset):
|
| 1819 |
+
return second_offset
|
| 1820 |
+
else:
|
| 1821 |
+
def _get_supported_offset(second_offset):
|
| 1822 |
+
# For python pre-3.6, round to full-minutes if that's not the case.
|
| 1823 |
+
# Python's datetime doesn't accept sub-minute timezones. Check
|
| 1824 |
+
# http://python.org/sf/1447945 or https://bugs.python.org/issue5288
|
| 1825 |
+
# for some information.
|
| 1826 |
+
old_offset = second_offset
|
| 1827 |
+
calculated_offset = 60 * ((second_offset + 30) // 60)
|
| 1828 |
+
return calculated_offset
|
| 1829 |
+
|
| 1830 |
+
|
| 1831 |
+
try:
|
| 1832 |
+
# Python 3.7 feature
|
| 1833 |
+
from contextlib import nullcontext as _nullcontext
|
| 1834 |
+
except ImportError:
|
| 1835 |
+
class _nullcontext(object):
|
| 1836 |
+
"""
|
| 1837 |
+
Class for wrapping contexts so that they are passed through in a
|
| 1838 |
+
with statement.
|
| 1839 |
+
"""
|
| 1840 |
+
def __init__(self, context):
|
| 1841 |
+
self.context = context
|
| 1842 |
+
|
| 1843 |
+
def __enter__(self):
|
| 1844 |
+
return self.context
|
| 1845 |
+
|
| 1846 |
+
def __exit__(*args, **kwargs):
|
| 1847 |
+
pass
|
| 1848 |
+
|
| 1849 |
+
# vim:ts=4:sw=4:et
|
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
|
dateutil/zoneinfo/__init__.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
import warnings
|
| 3 |
+
import json
|
| 4 |
+
|
| 5 |
+
from tarfile import TarFile
|
| 6 |
+
from pkgutil import get_data
|
| 7 |
+
from io import BytesIO
|
| 8 |
+
|
| 9 |
+
from dateutil.tz import tzfile as _tzfile
|
| 10 |
+
|
| 11 |
+
__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"]
|
| 12 |
+
|
| 13 |
+
ZONEFILENAME = "dateutil-zoneinfo.tar.gz"
|
| 14 |
+
METADATA_FN = 'METADATA'
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class tzfile(_tzfile):
|
| 18 |
+
def __reduce__(self):
|
| 19 |
+
return (gettz, (self._filename,))
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def getzoneinfofile_stream():
|
| 23 |
+
try:
|
| 24 |
+
return BytesIO(get_data(__name__, ZONEFILENAME))
|
| 25 |
+
except IOError as e: # TODO switch to FileNotFoundError?
|
| 26 |
+
warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror))
|
| 27 |
+
return None
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class ZoneInfoFile(object):
|
| 31 |
+
def __init__(self, zonefile_stream=None):
|
| 32 |
+
if zonefile_stream is not None:
|
| 33 |
+
with TarFile.open(fileobj=zonefile_stream) as tf:
|
| 34 |
+
self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name)
|
| 35 |
+
for zf in tf.getmembers()
|
| 36 |
+
if zf.isfile() and zf.name != METADATA_FN}
|
| 37 |
+
# deal with links: They'll point to their parent object. Less
|
| 38 |
+
# waste of memory
|
| 39 |
+
links = {zl.name: self.zones[zl.linkname]
|
| 40 |
+
for zl in tf.getmembers() if
|
| 41 |
+
zl.islnk() or zl.issym()}
|
| 42 |
+
self.zones.update(links)
|
| 43 |
+
try:
|
| 44 |
+
metadata_json = tf.extractfile(tf.getmember(METADATA_FN))
|
| 45 |
+
metadata_str = metadata_json.read().decode('UTF-8')
|
| 46 |
+
self.metadata = json.loads(metadata_str)
|
| 47 |
+
except KeyError:
|
| 48 |
+
# no metadata in tar file
|
| 49 |
+
self.metadata = None
|
| 50 |
+
else:
|
| 51 |
+
self.zones = {}
|
| 52 |
+
self.metadata = None
|
| 53 |
+
|
| 54 |
+
def get(self, name, default=None):
|
| 55 |
+
"""
|
| 56 |
+
Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method
|
| 57 |
+
for retrieving zones from the zone dictionary.
|
| 58 |
+
|
| 59 |
+
:param name:
|
| 60 |
+
The name of the zone to retrieve. (Generally IANA zone names)
|
| 61 |
+
|
| 62 |
+
:param default:
|
| 63 |
+
The value to return in the event of a missing key.
|
| 64 |
+
|
| 65 |
+
.. versionadded:: 2.6.0
|
| 66 |
+
|
| 67 |
+
"""
|
| 68 |
+
return self.zones.get(name, default)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# The current API has gettz as a module function, although in fact it taps into
|
| 72 |
+
# a stateful class. So as a workaround for now, without changing the API, we
|
| 73 |
+
# will create a new "global" class instance the first time a user requests a
|
| 74 |
+
# timezone. Ugly, but adheres to the api.
|
| 75 |
+
#
|
| 76 |
+
# TODO: Remove after deprecation period.
|
| 77 |
+
_CLASS_ZONE_INSTANCE = []
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def get_zonefile_instance(new_instance=False):
|
| 81 |
+
"""
|
| 82 |
+
This is a convenience function which provides a :class:`ZoneInfoFile`
|
| 83 |
+
instance using the data provided by the ``dateutil`` package. By default, it
|
| 84 |
+
caches a single instance of the ZoneInfoFile object and returns that.
|
| 85 |
+
|
| 86 |
+
:param new_instance:
|
| 87 |
+
If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and
|
| 88 |
+
used as the cached instance for the next call. Otherwise, new instances
|
| 89 |
+
are created only as necessary.
|
| 90 |
+
|
| 91 |
+
:return:
|
| 92 |
+
Returns a :class:`ZoneInfoFile` object.
|
| 93 |
+
|
| 94 |
+
.. versionadded:: 2.6
|
| 95 |
+
"""
|
| 96 |
+
if new_instance:
|
| 97 |
+
zif = None
|
| 98 |
+
else:
|
| 99 |
+
zif = getattr(get_zonefile_instance, '_cached_instance', None)
|
| 100 |
+
|
| 101 |
+
if zif is None:
|
| 102 |
+
zif = ZoneInfoFile(getzoneinfofile_stream())
|
| 103 |
+
|
| 104 |
+
get_zonefile_instance._cached_instance = zif
|
| 105 |
+
|
| 106 |
+
return zif
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def gettz(name):
|
| 110 |
+
"""
|
| 111 |
+
This retrieves a time zone from the local zoneinfo tarball that is packaged
|
| 112 |
+
with dateutil.
|
| 113 |
+
|
| 114 |
+
:param name:
|
| 115 |
+
An IANA-style time zone name, as found in the zoneinfo file.
|
| 116 |
+
|
| 117 |
+
:return:
|
| 118 |
+
Returns a :class:`dateutil.tz.tzfile` time zone object.
|
| 119 |
+
|
| 120 |
+
.. warning::
|
| 121 |
+
It is generally inadvisable to use this function, and it is only
|
| 122 |
+
provided for API compatibility with earlier versions. This is *not*
|
| 123 |
+
equivalent to ``dateutil.tz.gettz()``, which selects an appropriate
|
| 124 |
+
time zone based on the inputs, favoring system zoneinfo. This is ONLY
|
| 125 |
+
for accessing the dateutil-specific zoneinfo (which may be out of
|
| 126 |
+
date compared to the system zoneinfo).
|
| 127 |
+
|
| 128 |
+
.. deprecated:: 2.6
|
| 129 |
+
If you need to use a specific zoneinfofile over the system zoneinfo,
|
| 130 |
+
instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call
|
| 131 |
+
:func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead.
|
| 132 |
+
|
| 133 |
+
Use :func:`get_zonefile_instance` to retrieve an instance of the
|
| 134 |
+
dateutil-provided zoneinfo.
|
| 135 |
+
"""
|
| 136 |
+
warnings.warn("zoneinfo.gettz() will be removed in future versions, "
|
| 137 |
+
"to use the dateutil-provided zoneinfo files, instantiate a "
|
| 138 |
+
"ZoneInfoFile object and use ZoneInfoFile.zones.get() "
|
| 139 |
+
"instead. See the documentation for details.",
|
| 140 |
+
DeprecationWarning)
|
| 141 |
+
|
| 142 |
+
if len(_CLASS_ZONE_INSTANCE) == 0:
|
| 143 |
+
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
| 144 |
+
return _CLASS_ZONE_INSTANCE[0].zones.get(name)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def gettz_db_metadata():
|
| 148 |
+
""" Get the zonefile metadata
|
| 149 |
+
|
| 150 |
+
See `zonefile_metadata`_
|
| 151 |
+
|
| 152 |
+
:returns:
|
| 153 |
+
A dictionary with the database metadata
|
| 154 |
+
|
| 155 |
+
.. deprecated:: 2.6
|
| 156 |
+
See deprecation warning in :func:`zoneinfo.gettz`. To get metadata,
|
| 157 |
+
query the attribute ``zoneinfo.ZoneInfoFile.metadata``.
|
| 158 |
+
"""
|
| 159 |
+
warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future "
|
| 160 |
+
"versions, to use the dateutil-provided zoneinfo files, "
|
| 161 |
+
"ZoneInfoFile object and query the 'metadata' attribute "
|
| 162 |
+
"instead. See the documentation for details.",
|
| 163 |
+
DeprecationWarning)
|
| 164 |
+
|
| 165 |
+
if len(_CLASS_ZONE_INSTANCE) == 0:
|
| 166 |
+
_CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream()))
|
| 167 |
+
return _CLASS_ZONE_INSTANCE[0].metadata
|
dateutil/zoneinfo/rebuild.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import tempfile
|
| 4 |
+
import shutil
|
| 5 |
+
import json
|
| 6 |
+
from subprocess import check_call, check_output
|
| 7 |
+
from tarfile import TarFile
|
| 8 |
+
|
| 9 |
+
from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None):
|
| 13 |
+
"""Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar*
|
| 14 |
+
|
| 15 |
+
filename is the timezone tarball from ``ftp.iana.org/tz``.
|
| 16 |
+
|
| 17 |
+
"""
|
| 18 |
+
tmpdir = tempfile.mkdtemp()
|
| 19 |
+
zonedir = os.path.join(tmpdir, "zoneinfo")
|
| 20 |
+
moduledir = os.path.dirname(__file__)
|
| 21 |
+
try:
|
| 22 |
+
with TarFile.open(filename) as tf:
|
| 23 |
+
for name in zonegroups:
|
| 24 |
+
tf.extract(name, tmpdir)
|
| 25 |
+
filepaths = [os.path.join(tmpdir, n) for n in zonegroups]
|
| 26 |
+
|
| 27 |
+
_run_zic(zonedir, filepaths)
|
| 28 |
+
|
| 29 |
+
# write metadata file
|
| 30 |
+
with open(os.path.join(zonedir, METADATA_FN), 'w') as f:
|
| 31 |
+
json.dump(metadata, f, indent=4, sort_keys=True)
|
| 32 |
+
target = os.path.join(moduledir, ZONEFILENAME)
|
| 33 |
+
with TarFile.open(target, "w:%s" % format) as tf:
|
| 34 |
+
for entry in os.listdir(zonedir):
|
| 35 |
+
entrypath = os.path.join(zonedir, entry)
|
| 36 |
+
tf.add(entrypath, entry)
|
| 37 |
+
finally:
|
| 38 |
+
shutil.rmtree(tmpdir)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _run_zic(zonedir, filepaths):
|
| 42 |
+
"""Calls the ``zic`` compiler in a compatible way to get a "fat" binary.
|
| 43 |
+
|
| 44 |
+
Recent versions of ``zic`` default to ``-b slim``, while older versions
|
| 45 |
+
don't even have the ``-b`` option (but default to "fat" binaries). The
|
| 46 |
+
current version of dateutil does not support Version 2+ TZif files, which
|
| 47 |
+
causes problems when used in conjunction with "slim" binaries, so this
|
| 48 |
+
function is used to ensure that we always get a "fat" binary.
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
try:
|
| 52 |
+
help_text = check_output(["zic", "--help"])
|
| 53 |
+
except OSError as e:
|
| 54 |
+
_print_on_nosuchfile(e)
|
| 55 |
+
raise
|
| 56 |
+
|
| 57 |
+
if b"-b " in help_text:
|
| 58 |
+
bloat_args = ["-b", "fat"]
|
| 59 |
+
else:
|
| 60 |
+
bloat_args = []
|
| 61 |
+
|
| 62 |
+
check_call(["zic"] + bloat_args + ["-d", zonedir] + filepaths)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _print_on_nosuchfile(e):
|
| 66 |
+
"""Print helpful troubleshooting message
|
| 67 |
+
|
| 68 |
+
e is an exception raised by subprocess.check_call()
|
| 69 |
+
|
| 70 |
+
"""
|
| 71 |
+
if e.errno == 2:
|
| 72 |
+
logging.error(
|
| 73 |
+
"Could not find zic. Perhaps you need to install "
|
| 74 |
+
"libc-bin or some other package that provides it, "
|
| 75 |
+
"or it's not in your PATH?")
|
pandas/_config/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
pandas._config is considered explicitly upstream of everything else in pandas,
|
| 3 |
+
should have no intra-pandas dependencies.
|
| 4 |
+
|
| 5 |
+
importing `dates` and `display` ensures that keys needed by _libs
|
| 6 |
+
are initialized.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"config",
|
| 11 |
+
"describe_option",
|
| 12 |
+
"detect_console_encoding",
|
| 13 |
+
"get_option",
|
| 14 |
+
"option_context",
|
| 15 |
+
"options",
|
| 16 |
+
"reset_option",
|
| 17 |
+
"set_option",
|
| 18 |
+
]
|
| 19 |
+
from pandas._config import config
|
| 20 |
+
from pandas._config import dates # pyright: ignore[reportUnusedImport] # noqa: F401
|
| 21 |
+
from pandas._config.config import (
|
| 22 |
+
_global_config,
|
| 23 |
+
describe_option,
|
| 24 |
+
get_option,
|
| 25 |
+
option_context,
|
| 26 |
+
options,
|
| 27 |
+
reset_option,
|
| 28 |
+
set_option,
|
| 29 |
+
)
|
| 30 |
+
from pandas._config.display import detect_console_encoding
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def using_string_dtype() -> bool:
|
| 34 |
+
_mode_options = _global_config["future"]
|
| 35 |
+
return _mode_options["infer_string"]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def using_python_scalars() -> bool:
|
| 39 |
+
_mode_options = _global_config["future"]
|
| 40 |
+
return _mode_options["python_scalars"]
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def is_nan_na() -> bool:
|
| 44 |
+
_mode_options = _global_config["future"]
|
| 45 |
+
return not _mode_options["distinguish_nan_and_na"]
|
pandas/_config/config.py
ADDED
|
@@ -0,0 +1,954 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
The config module holds package-wide configurables and provides
|
| 3 |
+
a uniform API for working with them.
|
| 4 |
+
|
| 5 |
+
Overview
|
| 6 |
+
========
|
| 7 |
+
|
| 8 |
+
This module supports the following requirements:
|
| 9 |
+
- options are referenced using keys in dot.notation, e.g. "x.y.option - z".
|
| 10 |
+
- keys are case-insensitive.
|
| 11 |
+
- functions should accept partial/regex keys, when unambiguous.
|
| 12 |
+
- options can be registered by modules at import time.
|
| 13 |
+
- options can be registered at init-time (via core.config_init)
|
| 14 |
+
- options have a default value, and (optionally) a description and
|
| 15 |
+
validation function associated with them.
|
| 16 |
+
- options can be deprecated, in which case referencing them
|
| 17 |
+
should produce a warning.
|
| 18 |
+
- deprecated options can optionally be rerouted to a replacement
|
| 19 |
+
so that accessing a deprecated option reroutes to a differently
|
| 20 |
+
named option.
|
| 21 |
+
- options can be reset to their default value.
|
| 22 |
+
- all option can be reset to their default value at once.
|
| 23 |
+
- all options in a certain sub - namespace can be reset at once.
|
| 24 |
+
- the user can set / get / reset or ask for the description of an option.
|
| 25 |
+
- a developer can register and mark an option as deprecated.
|
| 26 |
+
- you can register a callback to be invoked when the option value
|
| 27 |
+
is set or reset. Changing the stored value is considered misuse, but
|
| 28 |
+
is not verboten.
|
| 29 |
+
|
| 30 |
+
Implementation
|
| 31 |
+
==============
|
| 32 |
+
|
| 33 |
+
- Data is stored using nested dictionaries, and should be accessed
|
| 34 |
+
through the provided API.
|
| 35 |
+
|
| 36 |
+
- "Registered options" and "Deprecated options" have metadata associated
|
| 37 |
+
with them, which are stored in auxiliary dictionaries keyed on the
|
| 38 |
+
fully-qualified key, e.g. "x.y.z.option".
|
| 39 |
+
|
| 40 |
+
- the config_init module is imported by the package's __init__.py file.
|
| 41 |
+
placing any register_option() calls there will ensure those options
|
| 42 |
+
are available as soon as pandas is loaded. If you use register_option
|
| 43 |
+
in a module, it will only be available after that module is imported,
|
| 44 |
+
which you should be aware of.
|
| 45 |
+
|
| 46 |
+
- `config_prefix` is a context_manager (for use with the `with` keyword)
|
| 47 |
+
which can save developers some typing, see the docstring.
|
| 48 |
+
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
from __future__ import annotations
|
| 52 |
+
|
| 53 |
+
from contextlib import contextmanager
|
| 54 |
+
import re
|
| 55 |
+
from typing import (
|
| 56 |
+
TYPE_CHECKING,
|
| 57 |
+
Any,
|
| 58 |
+
NamedTuple,
|
| 59 |
+
cast,
|
| 60 |
+
)
|
| 61 |
+
import warnings
|
| 62 |
+
|
| 63 |
+
from pandas._typing import F
|
| 64 |
+
from pandas.util._exceptions import find_stack_level
|
| 65 |
+
|
| 66 |
+
if TYPE_CHECKING:
|
| 67 |
+
from collections.abc import (
|
| 68 |
+
Callable,
|
| 69 |
+
Generator,
|
| 70 |
+
Sequence,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class DeprecatedOption(NamedTuple):
|
| 75 |
+
key: str
|
| 76 |
+
category: type[Warning]
|
| 77 |
+
msg: str | None
|
| 78 |
+
rkey: str | None
|
| 79 |
+
removal_ver: str | None
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class RegisteredOption(NamedTuple):
|
| 83 |
+
key: str
|
| 84 |
+
defval: Any
|
| 85 |
+
doc: str
|
| 86 |
+
validator: Callable[[object], Any] | None
|
| 87 |
+
cb: Callable[[str], Any] | None
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
# holds deprecated option metadata
|
| 91 |
+
_deprecated_options: dict[str, DeprecatedOption] = {}
|
| 92 |
+
|
| 93 |
+
# holds registered option metadata
|
| 94 |
+
_registered_options: dict[str, RegisteredOption] = {}
|
| 95 |
+
|
| 96 |
+
# holds the current values for registered options
|
| 97 |
+
_global_config: dict[str, Any] = {}
|
| 98 |
+
|
| 99 |
+
# keys which have a special meaning
|
| 100 |
+
_reserved_keys: list[str] = ["all"]
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class OptionError(AttributeError, KeyError):
|
| 104 |
+
"""
|
| 105 |
+
Exception raised for pandas.options.
|
| 106 |
+
|
| 107 |
+
Backwards compatible with KeyError checks.
|
| 108 |
+
|
| 109 |
+
See Also
|
| 110 |
+
--------
|
| 111 |
+
options : Access and modify global pandas settings.
|
| 112 |
+
|
| 113 |
+
Examples
|
| 114 |
+
--------
|
| 115 |
+
>>> pd.options.context
|
| 116 |
+
Traceback (most recent call last):
|
| 117 |
+
OptionError: No such option
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
__module__ = "pandas.errors"
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
#
|
| 124 |
+
# User API
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def _get_single_key(pat: str) -> str:
|
| 128 |
+
keys = _select_options(pat)
|
| 129 |
+
if len(keys) == 0:
|
| 130 |
+
_warn_if_deprecated(pat)
|
| 131 |
+
raise OptionError(f"No such keys(s): {pat!r}")
|
| 132 |
+
if len(keys) > 1:
|
| 133 |
+
raise OptionError("Pattern matched multiple keys")
|
| 134 |
+
key = keys[0]
|
| 135 |
+
|
| 136 |
+
_warn_if_deprecated(key)
|
| 137 |
+
|
| 138 |
+
key = _translate_key(key)
|
| 139 |
+
|
| 140 |
+
return key
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def get_option(pat: str) -> Any:
|
| 144 |
+
"""
|
| 145 |
+
Retrieve the value of the specified option.
|
| 146 |
+
|
| 147 |
+
This method allows users to query the current value of a given option
|
| 148 |
+
in the pandas configuration system. Options control various display,
|
| 149 |
+
performance, and behavior-related settings within pandas.
|
| 150 |
+
|
| 151 |
+
Parameters
|
| 152 |
+
----------
|
| 153 |
+
pat : str
|
| 154 |
+
Regexp which should match a single option.
|
| 155 |
+
|
| 156 |
+
.. warning::
|
| 157 |
+
|
| 158 |
+
Partial matches are supported for convenience, but unless you use the
|
| 159 |
+
full option name (e.g. x.y.z.option_name), your code may break in future
|
| 160 |
+
versions if new options with similar names are introduced.
|
| 161 |
+
|
| 162 |
+
Returns
|
| 163 |
+
-------
|
| 164 |
+
Any
|
| 165 |
+
The value of the option.
|
| 166 |
+
|
| 167 |
+
Raises
|
| 168 |
+
------
|
| 169 |
+
OptionError : if no such option exists
|
| 170 |
+
|
| 171 |
+
See Also
|
| 172 |
+
--------
|
| 173 |
+
set_option : Set the value of the specified option or options.
|
| 174 |
+
reset_option : Reset one or more options to their default value.
|
| 175 |
+
describe_option : Print the description for one or more registered options.
|
| 176 |
+
|
| 177 |
+
Notes
|
| 178 |
+
-----
|
| 179 |
+
For all available options, please view the :ref:`User Guide <options.available>`
|
| 180 |
+
or use ``pandas.describe_option()``.
|
| 181 |
+
|
| 182 |
+
Examples
|
| 183 |
+
--------
|
| 184 |
+
>>> pd.get_option("display.max_columns") # doctest: +SKIP
|
| 185 |
+
4
|
| 186 |
+
"""
|
| 187 |
+
key = _get_single_key(pat)
|
| 188 |
+
|
| 189 |
+
# walk the nested dict
|
| 190 |
+
root, k = _get_root(key)
|
| 191 |
+
return root[k]
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def set_option(*args) -> None:
|
| 195 |
+
"""
|
| 196 |
+
Set the value of the specified option or options.
|
| 197 |
+
|
| 198 |
+
This method allows fine-grained control over the behavior and display settings
|
| 199 |
+
of pandas. Options affect various functionalities such as output formatting,
|
| 200 |
+
display limits, and operational behavior. Settings can be modified at runtime
|
| 201 |
+
without requiring changes to global configurations or environment variables.
|
| 202 |
+
|
| 203 |
+
Parameters
|
| 204 |
+
----------
|
| 205 |
+
*args : str | object | dict
|
| 206 |
+
Arguments provided in pairs, which will be interpreted as (pattern, value),
|
| 207 |
+
or as a single dictionary containing multiple option-value pairs.
|
| 208 |
+
pattern: str
|
| 209 |
+
Regexp which should match a single option
|
| 210 |
+
value: object
|
| 211 |
+
New value of option
|
| 212 |
+
|
| 213 |
+
.. warning::
|
| 214 |
+
|
| 215 |
+
Partial pattern matches are supported for convenience, but unless you
|
| 216 |
+
use the full option name (e.g. x.y.z.option_name), your code may break in
|
| 217 |
+
future versions if new options with similar names are introduced.
|
| 218 |
+
|
| 219 |
+
Returns
|
| 220 |
+
-------
|
| 221 |
+
None
|
| 222 |
+
No return value.
|
| 223 |
+
|
| 224 |
+
Raises
|
| 225 |
+
------
|
| 226 |
+
ValueError if odd numbers of non-keyword arguments are provided
|
| 227 |
+
TypeError if keyword arguments are provided
|
| 228 |
+
OptionError if no such option exists
|
| 229 |
+
|
| 230 |
+
See Also
|
| 231 |
+
--------
|
| 232 |
+
get_option : Retrieve the value of the specified option.
|
| 233 |
+
reset_option : Reset one or more options to their default value.
|
| 234 |
+
describe_option : Print the description for one or more registered options.
|
| 235 |
+
option_context : Context manager to temporarily set options in a ``with``
|
| 236 |
+
statement.
|
| 237 |
+
|
| 238 |
+
Notes
|
| 239 |
+
-----
|
| 240 |
+
For all available options, please view the :ref:`User Guide <options.available>`
|
| 241 |
+
or use ``pandas.describe_option()``.
|
| 242 |
+
|
| 243 |
+
Examples
|
| 244 |
+
--------
|
| 245 |
+
Option-Value Pair Input:
|
| 246 |
+
|
| 247 |
+
>>> pd.set_option("display.max_columns", 4)
|
| 248 |
+
>>> df = pd.DataFrame([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
|
| 249 |
+
>>> df
|
| 250 |
+
0 1 ... 3 4
|
| 251 |
+
0 1 2 ... 4 5
|
| 252 |
+
1 6 7 ... 9 10
|
| 253 |
+
[2 rows x 5 columns]
|
| 254 |
+
>>> pd.reset_option("display.max_columns")
|
| 255 |
+
|
| 256 |
+
Dictionary Input:
|
| 257 |
+
|
| 258 |
+
>>> pd.set_option({"display.max_columns": 4, "display.precision": 1})
|
| 259 |
+
>>> df = pd.DataFrame([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
|
| 260 |
+
>>> df
|
| 261 |
+
0 1 ... 3 4
|
| 262 |
+
0 1 2 ... 4 5
|
| 263 |
+
1 6 7 ... 9 10
|
| 264 |
+
[2 rows x 5 columns]
|
| 265 |
+
>>> pd.reset_option("display.max_columns")
|
| 266 |
+
>>> pd.reset_option("display.precision")
|
| 267 |
+
"""
|
| 268 |
+
# Handle dictionary input
|
| 269 |
+
if len(args) == 1 and isinstance(args[0], dict):
|
| 270 |
+
args = tuple(kv for item in args[0].items() for kv in item)
|
| 271 |
+
|
| 272 |
+
nargs = len(args)
|
| 273 |
+
if not nargs or nargs % 2 != 0:
|
| 274 |
+
raise ValueError("Must provide an even number of non-keyword arguments")
|
| 275 |
+
|
| 276 |
+
for k, v in zip(args[::2], args[1::2], strict=True):
|
| 277 |
+
key = _get_single_key(k)
|
| 278 |
+
|
| 279 |
+
opt = _get_registered_option(key)
|
| 280 |
+
if opt and opt.validator:
|
| 281 |
+
opt.validator(v)
|
| 282 |
+
|
| 283 |
+
# walk the nested dict
|
| 284 |
+
root, k_root = _get_root(key)
|
| 285 |
+
root[k_root] = v
|
| 286 |
+
|
| 287 |
+
if opt.cb:
|
| 288 |
+
opt.cb(key)
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def describe_option(pat: str = "", _print_desc: bool = True) -> str | None:
|
| 292 |
+
"""
|
| 293 |
+
Print the description for one or more registered options.
|
| 294 |
+
|
| 295 |
+
Call with no arguments to get a listing for all registered options.
|
| 296 |
+
|
| 297 |
+
Parameters
|
| 298 |
+
----------
|
| 299 |
+
pat : str, default ""
|
| 300 |
+
String or string regexp pattern.
|
| 301 |
+
Empty string will return all options.
|
| 302 |
+
For regexp strings, all matching keys will have their description displayed.
|
| 303 |
+
_print_desc : bool, default True
|
| 304 |
+
If True (default) the description(s) will be printed to stdout.
|
| 305 |
+
Otherwise, the description(s) will be returned as a string
|
| 306 |
+
(for testing).
|
| 307 |
+
|
| 308 |
+
Returns
|
| 309 |
+
-------
|
| 310 |
+
None
|
| 311 |
+
If ``_print_desc=True``.
|
| 312 |
+
str
|
| 313 |
+
If the description(s) as a string if ``_print_desc=False``.
|
| 314 |
+
|
| 315 |
+
See Also
|
| 316 |
+
--------
|
| 317 |
+
get_option : Retrieve the value of the specified option.
|
| 318 |
+
set_option : Set the value of the specified option or options.
|
| 319 |
+
reset_option : Reset one or more options to their default value.
|
| 320 |
+
|
| 321 |
+
Notes
|
| 322 |
+
-----
|
| 323 |
+
For all available options, please view the
|
| 324 |
+
:ref:`User Guide <options.available>`.
|
| 325 |
+
|
| 326 |
+
Examples
|
| 327 |
+
--------
|
| 328 |
+
>>> pd.describe_option("display.max_columns") # doctest: +SKIP
|
| 329 |
+
display.max_columns : int
|
| 330 |
+
If max_cols is exceeded, switch to truncate view...
|
| 331 |
+
"""
|
| 332 |
+
keys = _select_options(pat)
|
| 333 |
+
if len(keys) == 0:
|
| 334 |
+
raise OptionError(f"No such keys(s) for {pat=}")
|
| 335 |
+
|
| 336 |
+
s = "\n".join([_build_option_description(k) for k in keys])
|
| 337 |
+
|
| 338 |
+
if _print_desc:
|
| 339 |
+
print(s)
|
| 340 |
+
return None
|
| 341 |
+
return s
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
def reset_option(pat: str) -> None:
|
| 345 |
+
"""
|
| 346 |
+
Reset one or more options to their default value.
|
| 347 |
+
|
| 348 |
+
This method resets the specified pandas option(s) back to their default
|
| 349 |
+
values. It allows partial string matching for convenience, but users should
|
| 350 |
+
exercise caution to avoid unintended resets due to changes in option names
|
| 351 |
+
in future versions.
|
| 352 |
+
|
| 353 |
+
Parameters
|
| 354 |
+
----------
|
| 355 |
+
pat : str/regex
|
| 356 |
+
If specified only options matching ``pat*`` will be reset.
|
| 357 |
+
Pass ``"all"`` as argument to reset all options.
|
| 358 |
+
|
| 359 |
+
.. warning::
|
| 360 |
+
|
| 361 |
+
Partial matches are supported for convenience, but unless you
|
| 362 |
+
use the full option name (e.g. x.y.z.option_name), your code may break
|
| 363 |
+
in future versions if new options with similar names are introduced.
|
| 364 |
+
|
| 365 |
+
Returns
|
| 366 |
+
-------
|
| 367 |
+
None
|
| 368 |
+
No return value.
|
| 369 |
+
|
| 370 |
+
See Also
|
| 371 |
+
--------
|
| 372 |
+
get_option : Retrieve the value of the specified option.
|
| 373 |
+
set_option : Set the value of the specified option or options.
|
| 374 |
+
describe_option : Print the description for one or more registered options.
|
| 375 |
+
|
| 376 |
+
Notes
|
| 377 |
+
-----
|
| 378 |
+
For all available options, please view the
|
| 379 |
+
:ref:`User Guide <options.available>`.
|
| 380 |
+
|
| 381 |
+
Examples
|
| 382 |
+
--------
|
| 383 |
+
>>> pd.reset_option("display.max_columns") # doctest: +SKIP
|
| 384 |
+
"""
|
| 385 |
+
keys = _select_options(pat)
|
| 386 |
+
|
| 387 |
+
if len(keys) == 0:
|
| 388 |
+
raise OptionError(f"No such keys(s) for {pat=}")
|
| 389 |
+
|
| 390 |
+
if len(keys) > 1 and len(pat) < 4 and pat != "all":
|
| 391 |
+
raise ValueError(
|
| 392 |
+
"You must specify at least 4 characters when "
|
| 393 |
+
"resetting multiple keys, use the special keyword "
|
| 394 |
+
'"all" to reset all the options to their default value'
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
for k in keys:
|
| 398 |
+
set_option(k, _registered_options[k].defval)
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
def get_default_val(pat: str):
|
| 402 |
+
key = _get_single_key(pat)
|
| 403 |
+
return _get_registered_option(key).defval
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
class DictWrapper:
|
| 407 |
+
"""provide attribute-style access to a nested dict"""
|
| 408 |
+
|
| 409 |
+
d: dict[str, Any]
|
| 410 |
+
|
| 411 |
+
def __init__(self, d: dict[str, Any], prefix: str = "") -> None:
|
| 412 |
+
object.__setattr__(self, "d", d)
|
| 413 |
+
object.__setattr__(self, "prefix", prefix)
|
| 414 |
+
|
| 415 |
+
def __setattr__(self, key: str, val: Any) -> None:
|
| 416 |
+
prefix = object.__getattribute__(self, "prefix")
|
| 417 |
+
if prefix:
|
| 418 |
+
prefix += "."
|
| 419 |
+
prefix += key
|
| 420 |
+
# you can't set new keys
|
| 421 |
+
# can you can't overwrite subtrees
|
| 422 |
+
if key in self.d and not isinstance(self.d[key], dict):
|
| 423 |
+
set_option(prefix, val)
|
| 424 |
+
else:
|
| 425 |
+
raise OptionError("You can only set the value of existing options")
|
| 426 |
+
|
| 427 |
+
def __getattr__(self, key: str):
|
| 428 |
+
prefix = object.__getattribute__(self, "prefix")
|
| 429 |
+
if prefix:
|
| 430 |
+
prefix += "."
|
| 431 |
+
prefix += key
|
| 432 |
+
try:
|
| 433 |
+
v = object.__getattribute__(self, "d")[key]
|
| 434 |
+
except KeyError as err:
|
| 435 |
+
raise OptionError("No such option") from err
|
| 436 |
+
if isinstance(v, dict):
|
| 437 |
+
return DictWrapper(v, prefix)
|
| 438 |
+
else:
|
| 439 |
+
return get_option(prefix)
|
| 440 |
+
|
| 441 |
+
def __dir__(self) -> list[str]:
|
| 442 |
+
return list(self.d.keys())
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
options = DictWrapper(_global_config)
|
| 446 |
+
# DictWrapper defines a custom setattr
|
| 447 |
+
object.__setattr__(options, "__module__", "pandas")
|
| 448 |
+
|
| 449 |
+
#
|
| 450 |
+
# Functions for use by pandas developers, in addition to User - api
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
@contextmanager
|
| 454 |
+
def option_context(*args) -> Generator[None]:
|
| 455 |
+
"""
|
| 456 |
+
Context manager to temporarily set options in a ``with`` statement.
|
| 457 |
+
|
| 458 |
+
This method allows users to set one or more pandas options temporarily
|
| 459 |
+
within a controlled block. The previous options' values are restored
|
| 460 |
+
once the block is exited. This is useful when making temporary adjustments
|
| 461 |
+
to pandas' behavior without affecting the global state.
|
| 462 |
+
|
| 463 |
+
Parameters
|
| 464 |
+
----------
|
| 465 |
+
*args : str | object | dict
|
| 466 |
+
An even amount of arguments provided in pairs which will be
|
| 467 |
+
interpreted as (pattern, value) pairs. Alternatively, a single
|
| 468 |
+
dictionary of {pattern: value} may be provided.
|
| 469 |
+
|
| 470 |
+
Returns
|
| 471 |
+
-------
|
| 472 |
+
None
|
| 473 |
+
No return value.
|
| 474 |
+
|
| 475 |
+
Yields
|
| 476 |
+
------
|
| 477 |
+
None
|
| 478 |
+
No yield value.
|
| 479 |
+
|
| 480 |
+
See Also
|
| 481 |
+
--------
|
| 482 |
+
get_option : Retrieve the value of the specified option.
|
| 483 |
+
set_option : Set the value of the specified option.
|
| 484 |
+
reset_option : Reset one or more options to their default value.
|
| 485 |
+
describe_option : Print the description for one or more registered options.
|
| 486 |
+
|
| 487 |
+
Notes
|
| 488 |
+
-----
|
| 489 |
+
For all available options, please view the :ref:`User Guide <options.available>`
|
| 490 |
+
or use ``pandas.describe_option()``.
|
| 491 |
+
|
| 492 |
+
Examples
|
| 493 |
+
--------
|
| 494 |
+
>>> from pandas import option_context
|
| 495 |
+
>>> with option_context("display.max_rows", 10, "display.max_columns", 5):
|
| 496 |
+
... pass
|
| 497 |
+
>>> with option_context({"display.max_rows": 10, "display.max_columns": 5}):
|
| 498 |
+
... pass
|
| 499 |
+
"""
|
| 500 |
+
if len(args) == 1 and isinstance(args[0], dict):
|
| 501 |
+
args = tuple(kv for item in args[0].items() for kv in item)
|
| 502 |
+
|
| 503 |
+
if len(args) % 2 != 0 or len(args) < 2:
|
| 504 |
+
raise ValueError(
|
| 505 |
+
"Provide an even amount of arguments as "
|
| 506 |
+
"option_context(pat, val, pat, val...)."
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
ops = tuple(zip(args[::2], args[1::2], strict=True))
|
| 510 |
+
undo: tuple[tuple[Any, Any], ...] = ()
|
| 511 |
+
try:
|
| 512 |
+
undo = tuple((pat, get_option(pat)) for pat, val in ops)
|
| 513 |
+
for pat, val in ops:
|
| 514 |
+
set_option(pat, val)
|
| 515 |
+
yield
|
| 516 |
+
finally:
|
| 517 |
+
for pat, val in undo:
|
| 518 |
+
set_option(pat, val)
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def register_option(
|
| 522 |
+
key: str,
|
| 523 |
+
defval: object,
|
| 524 |
+
doc: str = "",
|
| 525 |
+
validator: Callable[[object], Any] | None = None,
|
| 526 |
+
cb: Callable[[str], Any] | None = None,
|
| 527 |
+
) -> None:
|
| 528 |
+
"""
|
| 529 |
+
Register an option in the package-wide pandas config object
|
| 530 |
+
|
| 531 |
+
Parameters
|
| 532 |
+
----------
|
| 533 |
+
key : str
|
| 534 |
+
Fully-qualified key, e.g. "x.y.option - z".
|
| 535 |
+
defval : object
|
| 536 |
+
Default value of the option.
|
| 537 |
+
doc : str
|
| 538 |
+
Description of the option.
|
| 539 |
+
validator : Callable, optional
|
| 540 |
+
Function of a single argument, should raise `ValueError` if
|
| 541 |
+
called with a value which is not a legal value for the option.
|
| 542 |
+
cb
|
| 543 |
+
a function of a single argument "key", which is called
|
| 544 |
+
immediately after an option value is set/reset. key is
|
| 545 |
+
the full name of the option.
|
| 546 |
+
|
| 547 |
+
Raises
|
| 548 |
+
------
|
| 549 |
+
ValueError if `validator` is specified and `defval` is not a valid value.
|
| 550 |
+
|
| 551 |
+
"""
|
| 552 |
+
import keyword
|
| 553 |
+
import tokenize
|
| 554 |
+
|
| 555 |
+
key = key.lower()
|
| 556 |
+
|
| 557 |
+
if key in _registered_options:
|
| 558 |
+
raise OptionError(f"Option '{key}' has already been registered")
|
| 559 |
+
if key in _reserved_keys:
|
| 560 |
+
raise OptionError(f"Option '{key}' is a reserved key")
|
| 561 |
+
|
| 562 |
+
# the default value should be legal
|
| 563 |
+
if validator:
|
| 564 |
+
validator(defval)
|
| 565 |
+
|
| 566 |
+
# walk the nested dict, creating dicts as needed along the path
|
| 567 |
+
path = key.split(".")
|
| 568 |
+
|
| 569 |
+
for k in path:
|
| 570 |
+
if not re.match("^" + tokenize.Name + "$", k):
|
| 571 |
+
raise ValueError(f"{k} is not a valid identifier")
|
| 572 |
+
if keyword.iskeyword(k):
|
| 573 |
+
raise ValueError(f"{k} is a python keyword")
|
| 574 |
+
|
| 575 |
+
cursor = _global_config
|
| 576 |
+
msg = "Path prefix to option '{option}' is already an option"
|
| 577 |
+
|
| 578 |
+
for i, p in enumerate(path[:-1]):
|
| 579 |
+
if not isinstance(cursor, dict):
|
| 580 |
+
raise OptionError(msg.format(option=".".join(path[:i])))
|
| 581 |
+
if p not in cursor:
|
| 582 |
+
cursor[p] = {}
|
| 583 |
+
cursor = cursor[p]
|
| 584 |
+
|
| 585 |
+
if not isinstance(cursor, dict):
|
| 586 |
+
raise OptionError(msg.format(option=".".join(path[:-1])))
|
| 587 |
+
|
| 588 |
+
cursor[path[-1]] = defval # initialize
|
| 589 |
+
|
| 590 |
+
# save the option metadata
|
| 591 |
+
_registered_options[key] = RegisteredOption(
|
| 592 |
+
key=key, defval=defval, doc=doc, validator=validator, cb=cb
|
| 593 |
+
)
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
def deprecate_option(
|
| 597 |
+
key: str,
|
| 598 |
+
category: type[Warning],
|
| 599 |
+
msg: str | None = None,
|
| 600 |
+
rkey: str | None = None,
|
| 601 |
+
removal_ver: str | None = None,
|
| 602 |
+
) -> None:
|
| 603 |
+
"""
|
| 604 |
+
Mark option `key` as deprecated, if code attempts to access this option,
|
| 605 |
+
a warning will be produced, using `msg` if given, or a default message
|
| 606 |
+
if not.
|
| 607 |
+
if `rkey` is given, any access to the key will be re-routed to `rkey`.
|
| 608 |
+
|
| 609 |
+
Neither the existence of `key` nor that if `rkey` is checked. If they
|
| 610 |
+
do not exist, any subsequence access will fail as usual, after the
|
| 611 |
+
deprecation warning is given.
|
| 612 |
+
|
| 613 |
+
Parameters
|
| 614 |
+
----------
|
| 615 |
+
key : str
|
| 616 |
+
Name of the option to be deprecated.
|
| 617 |
+
must be a fully-qualified option name (e.g "x.y.z.rkey").
|
| 618 |
+
category : Warning
|
| 619 |
+
Warning class for the deprecation.
|
| 620 |
+
msg : str, optional
|
| 621 |
+
Warning message to output when the key is referenced.
|
| 622 |
+
if no message is given a default message will be emitted.
|
| 623 |
+
rkey : str, optional
|
| 624 |
+
Name of an option to reroute access to.
|
| 625 |
+
If specified, any referenced `key` will be
|
| 626 |
+
re-routed to `rkey` including set/get/reset.
|
| 627 |
+
rkey must be a fully-qualified option name (e.g "x.y.z.rkey").
|
| 628 |
+
used by the default message if no `msg` is specified.
|
| 629 |
+
removal_ver : str, optional
|
| 630 |
+
Specifies the version in which this option will
|
| 631 |
+
be removed. used by the default message if no `msg` is specified.
|
| 632 |
+
|
| 633 |
+
Raises
|
| 634 |
+
------
|
| 635 |
+
OptionError
|
| 636 |
+
If the specified key has already been deprecated.
|
| 637 |
+
"""
|
| 638 |
+
key = key.lower()
|
| 639 |
+
|
| 640 |
+
if key in _deprecated_options:
|
| 641 |
+
raise OptionError(f"Option '{key}' has already been defined as deprecated.")
|
| 642 |
+
|
| 643 |
+
_deprecated_options[key] = DeprecatedOption(key, category, msg, rkey, removal_ver)
|
| 644 |
+
|
| 645 |
+
|
| 646 |
+
#
|
| 647 |
+
# functions internal to the module
|
| 648 |
+
|
| 649 |
+
|
| 650 |
+
def _select_options(pat: str) -> list[str]:
|
| 651 |
+
"""
|
| 652 |
+
returns a list of keys matching `pat`
|
| 653 |
+
|
| 654 |
+
if pat=="all", returns all registered options
|
| 655 |
+
"""
|
| 656 |
+
# short-circuit for exact key
|
| 657 |
+
if pat in _registered_options:
|
| 658 |
+
return [pat]
|
| 659 |
+
|
| 660 |
+
# else look through all of them
|
| 661 |
+
keys = sorted(_registered_options.keys())
|
| 662 |
+
if pat == "all": # reserved key
|
| 663 |
+
return keys
|
| 664 |
+
|
| 665 |
+
return [k for k in keys if re.search(pat, k, re.I)]
|
| 666 |
+
|
| 667 |
+
|
| 668 |
+
def _get_root(key: str) -> tuple[dict[str, Any], str]:
|
| 669 |
+
path = key.split(".")
|
| 670 |
+
cursor = _global_config
|
| 671 |
+
for p in path[:-1]:
|
| 672 |
+
cursor = cursor[p]
|
| 673 |
+
return cursor, path[-1]
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
def _get_deprecated_option(key: str):
|
| 677 |
+
"""
|
| 678 |
+
Retrieves the metadata for a deprecated option, if `key` is deprecated.
|
| 679 |
+
|
| 680 |
+
Returns
|
| 681 |
+
-------
|
| 682 |
+
DeprecatedOption (namedtuple) if key is deprecated, None otherwise
|
| 683 |
+
"""
|
| 684 |
+
try:
|
| 685 |
+
d = _deprecated_options[key]
|
| 686 |
+
except KeyError:
|
| 687 |
+
return None
|
| 688 |
+
else:
|
| 689 |
+
return d
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
def _get_registered_option(key: str):
|
| 693 |
+
"""
|
| 694 |
+
Retrieves the option metadata if `key` is a registered option.
|
| 695 |
+
|
| 696 |
+
Returns
|
| 697 |
+
-------
|
| 698 |
+
RegisteredOption (namedtuple) if key is deprecated, None otherwise
|
| 699 |
+
"""
|
| 700 |
+
return _registered_options.get(key)
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
def _translate_key(key: str) -> str:
|
| 704 |
+
"""
|
| 705 |
+
if `key` is deprecated and a replacement key defined, will return the
|
| 706 |
+
replacement key, otherwise returns `key` as-is
|
| 707 |
+
"""
|
| 708 |
+
d = _get_deprecated_option(key)
|
| 709 |
+
if d:
|
| 710 |
+
return d.rkey or key
|
| 711 |
+
else:
|
| 712 |
+
return key
|
| 713 |
+
|
| 714 |
+
|
| 715 |
+
def _warn_if_deprecated(key: str) -> bool:
|
| 716 |
+
"""
|
| 717 |
+
Checks if `key` is a deprecated option and if so, prints a warning.
|
| 718 |
+
|
| 719 |
+
Returns
|
| 720 |
+
-------
|
| 721 |
+
bool - True if `key` is deprecated, False otherwise.
|
| 722 |
+
"""
|
| 723 |
+
d = _get_deprecated_option(key)
|
| 724 |
+
if d:
|
| 725 |
+
if d.msg:
|
| 726 |
+
warnings.warn(
|
| 727 |
+
d.msg,
|
| 728 |
+
d.category,
|
| 729 |
+
stacklevel=find_stack_level(),
|
| 730 |
+
)
|
| 731 |
+
else:
|
| 732 |
+
msg = f"'{key}' is deprecated"
|
| 733 |
+
if d.removal_ver:
|
| 734 |
+
msg += f" and will be removed in {d.removal_ver}"
|
| 735 |
+
if d.rkey:
|
| 736 |
+
msg += f", please use '{d.rkey}' instead."
|
| 737 |
+
else:
|
| 738 |
+
msg += ", please refrain from using it."
|
| 739 |
+
|
| 740 |
+
warnings.warn(
|
| 741 |
+
msg,
|
| 742 |
+
d.category,
|
| 743 |
+
stacklevel=find_stack_level(),
|
| 744 |
+
)
|
| 745 |
+
return True
|
| 746 |
+
return False
|
| 747 |
+
|
| 748 |
+
|
| 749 |
+
def _build_option_description(k: str) -> str:
|
| 750 |
+
"""Builds a formatted description of a registered option and prints it"""
|
| 751 |
+
o = _get_registered_option(k)
|
| 752 |
+
d = _get_deprecated_option(k)
|
| 753 |
+
|
| 754 |
+
s = f"{k} "
|
| 755 |
+
|
| 756 |
+
if o.doc:
|
| 757 |
+
s += "\n".join(o.doc.strip().split("\n"))
|
| 758 |
+
else:
|
| 759 |
+
s += "No description available."
|
| 760 |
+
|
| 761 |
+
if o:
|
| 762 |
+
with warnings.catch_warnings():
|
| 763 |
+
warnings.simplefilter("ignore", FutureWarning)
|
| 764 |
+
warnings.simplefilter("ignore", DeprecationWarning)
|
| 765 |
+
s += f"\n [default: {o.defval}] [currently: {get_option(k)}]"
|
| 766 |
+
|
| 767 |
+
if d:
|
| 768 |
+
rkey = d.rkey or ""
|
| 769 |
+
s += "\n (Deprecated"
|
| 770 |
+
s += f", use `{rkey}` instead."
|
| 771 |
+
s += ")"
|
| 772 |
+
|
| 773 |
+
return s
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
# helpers
|
| 777 |
+
|
| 778 |
+
|
| 779 |
+
@contextmanager
|
| 780 |
+
def config_prefix(prefix: str) -> Generator[None]:
|
| 781 |
+
"""
|
| 782 |
+
contextmanager for multiple invocations of API with a common prefix
|
| 783 |
+
|
| 784 |
+
supported API functions: (register / get / set )__option
|
| 785 |
+
|
| 786 |
+
Warning: This is not thread - safe, and won't work properly if you import
|
| 787 |
+
the API functions into your module using the "from x import y" construct.
|
| 788 |
+
|
| 789 |
+
Example
|
| 790 |
+
-------
|
| 791 |
+
import pandas._config.config as cf
|
| 792 |
+
with cf.config_prefix("display.font"):
|
| 793 |
+
cf.register_option("color", "red")
|
| 794 |
+
cf.register_option("size", " 5 pt")
|
| 795 |
+
cf.set_option(size, " 6 pt")
|
| 796 |
+
cf.get_option(size)
|
| 797 |
+
...
|
| 798 |
+
|
| 799 |
+
etc'
|
| 800 |
+
|
| 801 |
+
will register options "display.font.color", "display.font.size", set the
|
| 802 |
+
value of "display.font.size"... and so on.
|
| 803 |
+
"""
|
| 804 |
+
# Note: reset_option relies on set_option, and on key directly
|
| 805 |
+
# it does not fit in to this monkey-patching scheme
|
| 806 |
+
|
| 807 |
+
global register_option, get_option, set_option
|
| 808 |
+
|
| 809 |
+
def wrap(func: F) -> F:
|
| 810 |
+
def inner(key: str, *args, **kwds):
|
| 811 |
+
pkey = f"{prefix}.{key}"
|
| 812 |
+
return func(pkey, *args, **kwds)
|
| 813 |
+
|
| 814 |
+
return cast(F, inner)
|
| 815 |
+
|
| 816 |
+
_register_option = register_option
|
| 817 |
+
_get_option = get_option
|
| 818 |
+
_set_option = set_option
|
| 819 |
+
set_option = wrap(set_option)
|
| 820 |
+
get_option = wrap(get_option)
|
| 821 |
+
register_option = wrap(register_option)
|
| 822 |
+
try:
|
| 823 |
+
yield
|
| 824 |
+
finally:
|
| 825 |
+
set_option = _set_option
|
| 826 |
+
get_option = _get_option
|
| 827 |
+
register_option = _register_option
|
| 828 |
+
|
| 829 |
+
|
| 830 |
+
# These factories and methods are handy for use as the validator
|
| 831 |
+
# arg in register_option
|
| 832 |
+
|
| 833 |
+
|
| 834 |
+
def is_type_factory(_type: type[Any]) -> Callable[[Any], None]:
|
| 835 |
+
"""
|
| 836 |
+
|
| 837 |
+
Parameters
|
| 838 |
+
----------
|
| 839 |
+
`_type` - a type to be compared against (e.g. type(x) == `_type`)
|
| 840 |
+
|
| 841 |
+
Returns
|
| 842 |
+
-------
|
| 843 |
+
validator - a function of a single argument x , which raises
|
| 844 |
+
ValueError if type(x) is not equal to `_type`
|
| 845 |
+
|
| 846 |
+
"""
|
| 847 |
+
|
| 848 |
+
def inner(x) -> None:
|
| 849 |
+
if type(x) != _type:
|
| 850 |
+
raise ValueError(f"Value must have type '{_type}'")
|
| 851 |
+
|
| 852 |
+
return inner
|
| 853 |
+
|
| 854 |
+
|
| 855 |
+
def is_instance_factory(_type: type | tuple[type, ...]) -> Callable[[Any], None]:
|
| 856 |
+
"""
|
| 857 |
+
|
| 858 |
+
Parameters
|
| 859 |
+
----------
|
| 860 |
+
`_type` - the type to be checked against
|
| 861 |
+
|
| 862 |
+
Returns
|
| 863 |
+
-------
|
| 864 |
+
validator - a function of a single argument x , which raises
|
| 865 |
+
ValueError if x is not an instance of `_type`
|
| 866 |
+
|
| 867 |
+
"""
|
| 868 |
+
if isinstance(_type, tuple):
|
| 869 |
+
type_repr = "|".join(map(str, _type))
|
| 870 |
+
else:
|
| 871 |
+
type_repr = f"'{_type}'"
|
| 872 |
+
|
| 873 |
+
def inner(x) -> None:
|
| 874 |
+
if not isinstance(x, _type):
|
| 875 |
+
raise ValueError(f"Value must be an instance of {type_repr}")
|
| 876 |
+
|
| 877 |
+
return inner
|
| 878 |
+
|
| 879 |
+
|
| 880 |
+
def is_one_of_factory(legal_values: Sequence) -> Callable[[Any], None]:
|
| 881 |
+
callables = [c for c in legal_values if callable(c)]
|
| 882 |
+
legal_values = [c for c in legal_values if not callable(c)]
|
| 883 |
+
|
| 884 |
+
def inner(x) -> None:
|
| 885 |
+
if x not in legal_values:
|
| 886 |
+
if not any(c(x) for c in callables):
|
| 887 |
+
uvals = [str(lval) for lval in legal_values]
|
| 888 |
+
pp_values = "|".join(uvals)
|
| 889 |
+
msg = f"Value must be one of {pp_values}"
|
| 890 |
+
if len(callables):
|
| 891 |
+
msg += " or a callable"
|
| 892 |
+
raise ValueError(msg)
|
| 893 |
+
|
| 894 |
+
return inner
|
| 895 |
+
|
| 896 |
+
|
| 897 |
+
def is_nonnegative_int(value: object) -> None:
|
| 898 |
+
"""
|
| 899 |
+
Verify that value is None or a positive int.
|
| 900 |
+
|
| 901 |
+
Parameters
|
| 902 |
+
----------
|
| 903 |
+
value : None or int
|
| 904 |
+
The `value` to be checked.
|
| 905 |
+
|
| 906 |
+
Raises
|
| 907 |
+
------
|
| 908 |
+
ValueError
|
| 909 |
+
When the value is not None or is a negative integer
|
| 910 |
+
"""
|
| 911 |
+
if value is None:
|
| 912 |
+
return
|
| 913 |
+
|
| 914 |
+
elif isinstance(value, int):
|
| 915 |
+
if value >= 0:
|
| 916 |
+
return
|
| 917 |
+
|
| 918 |
+
msg = "Value must be a nonnegative integer or None"
|
| 919 |
+
raise ValueError(msg)
|
| 920 |
+
|
| 921 |
+
|
| 922 |
+
# common type validators, for convenience
|
| 923 |
+
# usage: register_option(... , validator = is_int)
|
| 924 |
+
is_int = is_type_factory(int)
|
| 925 |
+
is_bool = is_type_factory(bool)
|
| 926 |
+
is_float = is_type_factory(float)
|
| 927 |
+
is_str = is_type_factory(str)
|
| 928 |
+
is_text = is_instance_factory((str, bytes))
|
| 929 |
+
|
| 930 |
+
|
| 931 |
+
def is_callable(obj: object) -> bool:
|
| 932 |
+
"""
|
| 933 |
+
|
| 934 |
+
Parameters
|
| 935 |
+
----------
|
| 936 |
+
`obj` - the object to be checked
|
| 937 |
+
|
| 938 |
+
Returns
|
| 939 |
+
-------
|
| 940 |
+
validator - returns True if object is callable
|
| 941 |
+
raises ValueError otherwise.
|
| 942 |
+
|
| 943 |
+
"""
|
| 944 |
+
if not callable(obj):
|
| 945 |
+
raise ValueError("Value must be a callable")
|
| 946 |
+
return True
|
| 947 |
+
|
| 948 |
+
|
| 949 |
+
# import set_module here would cause circular import
|
| 950 |
+
get_option.__module__ = "pandas"
|
| 951 |
+
set_option.__module__ = "pandas"
|
| 952 |
+
describe_option.__module__ = "pandas"
|
| 953 |
+
reset_option.__module__ = "pandas"
|
| 954 |
+
option_context.__module__ = "pandas"
|
pandas/_config/dates.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
config for datetime formatting
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
from pandas._config import config as cf
|
| 8 |
+
|
| 9 |
+
pc_date_dayfirst_doc = """
|
| 10 |
+
: boolean
|
| 11 |
+
When True, prints and parses dates with the day first, eg 20/01/2005
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
pc_date_yearfirst_doc = """
|
| 15 |
+
: boolean
|
| 16 |
+
When True, prints and parses dates with the year first, eg 2005/01/20
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
with cf.config_prefix("display"):
|
| 20 |
+
# Needed upstream of `_libs` because these are used in tslibs.parsing
|
| 21 |
+
cf.register_option(
|
| 22 |
+
"date_dayfirst", False, pc_date_dayfirst_doc, validator=cf.is_bool
|
| 23 |
+
)
|
| 24 |
+
cf.register_option(
|
| 25 |
+
"date_yearfirst", False, pc_date_yearfirst_doc, validator=cf.is_bool
|
| 26 |
+
)
|
pandas/_config/display.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Unopinionated display configuration.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import locale
|
| 8 |
+
import sys
|
| 9 |
+
|
| 10 |
+
from pandas._config import config as cf
|
| 11 |
+
|
| 12 |
+
# -----------------------------------------------------------------------------
|
| 13 |
+
# Global formatting options
|
| 14 |
+
_initial_defencoding: str | None = None
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def detect_console_encoding() -> str:
|
| 18 |
+
"""
|
| 19 |
+
Try to find the most capable encoding supported by the console.
|
| 20 |
+
slightly modified from the way IPython handles the same issue.
|
| 21 |
+
"""
|
| 22 |
+
global _initial_defencoding
|
| 23 |
+
|
| 24 |
+
encoding = None
|
| 25 |
+
try:
|
| 26 |
+
encoding = sys.stdout.encoding or sys.stdin.encoding
|
| 27 |
+
except (AttributeError, OSError):
|
| 28 |
+
pass
|
| 29 |
+
|
| 30 |
+
# try again for something better
|
| 31 |
+
if not encoding or "ascii" in encoding.lower():
|
| 32 |
+
try:
|
| 33 |
+
encoding = locale.getpreferredencoding()
|
| 34 |
+
except locale.Error:
|
| 35 |
+
# can be raised by locale.setlocale(), which is
|
| 36 |
+
# called by getpreferredencoding
|
| 37 |
+
# (on some systems, see stdlib locale docs)
|
| 38 |
+
pass
|
| 39 |
+
|
| 40 |
+
# when all else fails. this will usually be "ascii"
|
| 41 |
+
if not encoding or "ascii" in encoding.lower():
|
| 42 |
+
encoding = sys.getdefaultencoding()
|
| 43 |
+
|
| 44 |
+
# GH#3360, save the reported defencoding at import time
|
| 45 |
+
# MPL backends may change it. Make available for debugging.
|
| 46 |
+
if not _initial_defencoding:
|
| 47 |
+
_initial_defencoding = sys.getdefaultencoding()
|
| 48 |
+
|
| 49 |
+
return encoding
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
pc_encoding_doc = """
|
| 53 |
+
: str/unicode
|
| 54 |
+
Defaults to the detected encoding of the console.
|
| 55 |
+
Specifies the encoding to be used for strings returned by to_string,
|
| 56 |
+
these are generally strings meant to be displayed on the console.
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
with cf.config_prefix("display"):
|
| 60 |
+
cf.register_option(
|
| 61 |
+
"encoding", detect_console_encoding(), pc_encoding_doc, validator=cf.is_text
|
| 62 |
+
)
|
pandas/_config/localization.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Helpers for configuring locale settings.
|
| 3 |
+
|
| 4 |
+
Name `localization` is chosen to avoid overlap with builtin `locale` module.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
from contextlib import contextmanager
|
| 10 |
+
import locale
|
| 11 |
+
import platform
|
| 12 |
+
import re
|
| 13 |
+
import subprocess
|
| 14 |
+
from typing import (
|
| 15 |
+
TYPE_CHECKING,
|
| 16 |
+
cast,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
from pandas._config.config import options
|
| 20 |
+
|
| 21 |
+
if TYPE_CHECKING:
|
| 22 |
+
from collections.abc import Generator
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@contextmanager
|
| 26 |
+
def set_locale(
|
| 27 |
+
new_locale: str | tuple[str, str], lc_var: int = locale.LC_ALL
|
| 28 |
+
) -> Generator[str | tuple[str, str]]:
|
| 29 |
+
"""
|
| 30 |
+
Context manager for temporarily setting a locale.
|
| 31 |
+
|
| 32 |
+
Parameters
|
| 33 |
+
----------
|
| 34 |
+
new_locale : str or tuple
|
| 35 |
+
A string of the form <language_country>.<encoding>. For example to set
|
| 36 |
+
the current locale to US English with a UTF8 encoding, you would pass
|
| 37 |
+
"en_US.UTF-8".
|
| 38 |
+
lc_var : int, default `locale.LC_ALL`
|
| 39 |
+
The category of the locale being set.
|
| 40 |
+
|
| 41 |
+
Notes
|
| 42 |
+
-----
|
| 43 |
+
This is useful when you want to run a particular block of code under a
|
| 44 |
+
particular locale, without globally setting the locale. This probably isn't
|
| 45 |
+
thread-safe.
|
| 46 |
+
"""
|
| 47 |
+
# getlocale is not always compliant with setlocale, use setlocale. GH#46595
|
| 48 |
+
current_locale = locale.setlocale(lc_var)
|
| 49 |
+
|
| 50 |
+
try:
|
| 51 |
+
locale.setlocale(lc_var, new_locale)
|
| 52 |
+
normalized_code, normalized_encoding = locale.getlocale()
|
| 53 |
+
if normalized_code is not None and normalized_encoding is not None:
|
| 54 |
+
yield f"{normalized_code}.{normalized_encoding}"
|
| 55 |
+
else:
|
| 56 |
+
yield new_locale
|
| 57 |
+
finally:
|
| 58 |
+
locale.setlocale(lc_var, current_locale)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def can_set_locale(lc: str, lc_var: int = locale.LC_ALL) -> bool:
|
| 62 |
+
"""
|
| 63 |
+
Check to see if we can set a locale, and subsequently get the locale,
|
| 64 |
+
without raising an Exception.
|
| 65 |
+
|
| 66 |
+
Parameters
|
| 67 |
+
----------
|
| 68 |
+
lc : str
|
| 69 |
+
The locale to attempt to set.
|
| 70 |
+
lc_var : int, default `locale.LC_ALL`
|
| 71 |
+
The category of the locale being set.
|
| 72 |
+
|
| 73 |
+
Returns
|
| 74 |
+
-------
|
| 75 |
+
bool
|
| 76 |
+
Whether the passed locale can be set
|
| 77 |
+
"""
|
| 78 |
+
try:
|
| 79 |
+
with set_locale(lc, lc_var=lc_var):
|
| 80 |
+
pass
|
| 81 |
+
except (ValueError, locale.Error):
|
| 82 |
+
# horrible name for an Exception subclass
|
| 83 |
+
return False
|
| 84 |
+
else:
|
| 85 |
+
return True
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _valid_locales(locales: list[str] | str, normalize: bool) -> list[str]:
|
| 89 |
+
"""
|
| 90 |
+
Return a list of normalized locales that do not throw an ``Exception``
|
| 91 |
+
when set.
|
| 92 |
+
|
| 93 |
+
Parameters
|
| 94 |
+
----------
|
| 95 |
+
locales : str
|
| 96 |
+
A string where each locale is separated by a newline.
|
| 97 |
+
normalize : bool
|
| 98 |
+
Whether to call ``locale.normalize`` on each locale.
|
| 99 |
+
|
| 100 |
+
Returns
|
| 101 |
+
-------
|
| 102 |
+
valid_locales : list
|
| 103 |
+
A list of valid locales.
|
| 104 |
+
"""
|
| 105 |
+
return [
|
| 106 |
+
loc
|
| 107 |
+
for loc in (
|
| 108 |
+
locale.normalize(loc.strip()) if normalize else loc.strip()
|
| 109 |
+
for loc in locales
|
| 110 |
+
)
|
| 111 |
+
if can_set_locale(loc)
|
| 112 |
+
]
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def get_locales(
|
| 116 |
+
prefix: str | None = None,
|
| 117 |
+
normalize: bool = True,
|
| 118 |
+
) -> list[str]:
|
| 119 |
+
"""
|
| 120 |
+
Get all the locales that are available on the system.
|
| 121 |
+
|
| 122 |
+
Parameters
|
| 123 |
+
----------
|
| 124 |
+
prefix : str
|
| 125 |
+
If not ``None`` then return only those locales with the prefix
|
| 126 |
+
provided. For example to get all English language locales (those that
|
| 127 |
+
start with ``"en"``), pass ``prefix="en"``.
|
| 128 |
+
normalize : bool
|
| 129 |
+
Call ``locale.normalize`` on the resulting list of available locales.
|
| 130 |
+
If ``True``, only locales that can be set without throwing an
|
| 131 |
+
``Exception`` are returned.
|
| 132 |
+
|
| 133 |
+
Returns
|
| 134 |
+
-------
|
| 135 |
+
locales : list of strings
|
| 136 |
+
A list of locale strings that can be set with ``locale.setlocale()``.
|
| 137 |
+
For example::
|
| 138 |
+
|
| 139 |
+
locale.setlocale(locale.LC_ALL, locale_string)
|
| 140 |
+
|
| 141 |
+
On error will return an empty list (no locale available, e.g. Windows)
|
| 142 |
+
|
| 143 |
+
"""
|
| 144 |
+
if platform.system() in ("Linux", "Darwin"):
|
| 145 |
+
raw_locales = subprocess.check_output(["locale", "-a"])
|
| 146 |
+
else:
|
| 147 |
+
# Other platforms e.g. windows platforms don't define "locale -a"
|
| 148 |
+
# Note: is_platform_windows causes circular import here
|
| 149 |
+
return []
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
# raw_locales is "\n" separated list of locales
|
| 153 |
+
# it may contain non-decodable parts, so split
|
| 154 |
+
# extract what we can and then rejoin.
|
| 155 |
+
split_raw_locales = raw_locales.split(b"\n")
|
| 156 |
+
out_locales = []
|
| 157 |
+
for x in split_raw_locales:
|
| 158 |
+
try:
|
| 159 |
+
out_locales.append(str(x, encoding=cast(str, options.display.encoding)))
|
| 160 |
+
except UnicodeError:
|
| 161 |
+
# 'locale -a' is used to populated 'raw_locales' and on
|
| 162 |
+
# Redhat 7 Linux (and maybe others) prints locale names
|
| 163 |
+
# using windows-1252 encoding. Bug only triggered by
|
| 164 |
+
# a few special characters and when there is an
|
| 165 |
+
# extensive list of installed locales.
|
| 166 |
+
out_locales.append(str(x, encoding="windows-1252"))
|
| 167 |
+
|
| 168 |
+
except TypeError:
|
| 169 |
+
pass
|
| 170 |
+
|
| 171 |
+
if prefix is None:
|
| 172 |
+
return _valid_locales(out_locales, normalize)
|
| 173 |
+
|
| 174 |
+
pattern = re.compile(f"{prefix}.*")
|
| 175 |
+
found = pattern.findall("\n".join(out_locales))
|
| 176 |
+
return _valid_locales(found, normalize)
|
pandas/_libs/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = [
|
| 2 |
+
"Interval",
|
| 3 |
+
"NaT",
|
| 4 |
+
"NaTType",
|
| 5 |
+
"OutOfBoundsDatetime",
|
| 6 |
+
"Period",
|
| 7 |
+
"Timedelta",
|
| 8 |
+
"Timestamp",
|
| 9 |
+
"iNaT",
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# Below imports needs to happen first to ensure pandas top level
|
| 14 |
+
# module gets monkeypatched with the pandas_datetime_CAPI
|
| 15 |
+
# see pandas_datetime_exec in pd_datetime.c
|
| 16 |
+
import pandas._libs.pandas_parser # isort: skip # type: ignore[reportUnusedImport]
|
| 17 |
+
import pandas._libs.pandas_datetime # noqa: F401 # isort: skip # type: ignore[reportUnusedImport]
|
| 18 |
+
from pandas._libs.interval import Interval
|
| 19 |
+
from pandas._libs.tslibs import (
|
| 20 |
+
NaT,
|
| 21 |
+
NaTType,
|
| 22 |
+
OutOfBoundsDatetime,
|
| 23 |
+
Period,
|
| 24 |
+
Timedelta,
|
| 25 |
+
Timestamp,
|
| 26 |
+
iNaT,
|
| 27 |
+
)
|
pandas/_libs/algos.pyi
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from pandas._typing import npt
|
| 6 |
+
|
| 7 |
+
class Infinity:
|
| 8 |
+
def __eq__(self, other) -> bool: ...
|
| 9 |
+
def __ne__(self, other) -> bool: ...
|
| 10 |
+
def __lt__(self, other) -> bool: ...
|
| 11 |
+
def __le__(self, other) -> bool: ...
|
| 12 |
+
def __gt__(self, other) -> bool: ...
|
| 13 |
+
def __ge__(self, other) -> bool: ...
|
| 14 |
+
|
| 15 |
+
class NegInfinity:
|
| 16 |
+
def __eq__(self, other) -> bool: ...
|
| 17 |
+
def __ne__(self, other) -> bool: ...
|
| 18 |
+
def __lt__(self, other) -> bool: ...
|
| 19 |
+
def __le__(self, other) -> bool: ...
|
| 20 |
+
def __gt__(self, other) -> bool: ...
|
| 21 |
+
def __ge__(self, other) -> bool: ...
|
| 22 |
+
|
| 23 |
+
def unique_deltas(
|
| 24 |
+
arr: np.ndarray, # const int64_t[:]
|
| 25 |
+
) -> np.ndarray: ... # np.ndarray[np.int64, ndim=1]
|
| 26 |
+
def is_lexsorted(list_of_arrays: list[npt.NDArray[np.int64]]) -> bool: ...
|
| 27 |
+
def groupsort_indexer(
|
| 28 |
+
index: np.ndarray, # const int64_t[:]
|
| 29 |
+
ngroups: int,
|
| 30 |
+
) -> tuple[
|
| 31 |
+
np.ndarray, # ndarray[int64_t, ndim=1]
|
| 32 |
+
np.ndarray, # ndarray[int64_t, ndim=1]
|
| 33 |
+
]: ...
|
| 34 |
+
def kth_smallest(
|
| 35 |
+
arr: np.ndarray, # numeric[:]
|
| 36 |
+
k: int,
|
| 37 |
+
) -> Any: ... # numeric
|
| 38 |
+
|
| 39 |
+
# ----------------------------------------------------------------------
|
| 40 |
+
# Pairwise correlation/covariance
|
| 41 |
+
|
| 42 |
+
def nancorr(
|
| 43 |
+
mat: npt.NDArray[np.float64], # const float64_t[:, :]
|
| 44 |
+
cov: bool = ...,
|
| 45 |
+
minp: int | None = ...,
|
| 46 |
+
) -> npt.NDArray[np.float64]: ... # ndarray[float64_t, ndim=2]
|
| 47 |
+
def nancorr_spearman(
|
| 48 |
+
mat: npt.NDArray[np.float64], # ndarray[float64_t, ndim=2]
|
| 49 |
+
minp: int = ...,
|
| 50 |
+
) -> npt.NDArray[np.float64]: ... # ndarray[float64_t, ndim=2]
|
| 51 |
+
|
| 52 |
+
# ----------------------------------------------------------------------
|
| 53 |
+
|
| 54 |
+
def validate_limit(nobs: int | None, limit=...) -> int: ...
|
| 55 |
+
def get_fill_indexer(
|
| 56 |
+
mask: npt.NDArray[np.bool_],
|
| 57 |
+
limit: int | None = None,
|
| 58 |
+
) -> npt.NDArray[np.intp]: ...
|
| 59 |
+
def pad(
|
| 60 |
+
old: np.ndarray, # ndarray[numeric_object_t]
|
| 61 |
+
new: np.ndarray, # ndarray[numeric_object_t]
|
| 62 |
+
limit=...,
|
| 63 |
+
) -> npt.NDArray[np.intp]: ... # np.ndarray[np.intp, ndim=1]
|
| 64 |
+
def pad_inplace(
|
| 65 |
+
values: np.ndarray, # numeric_object_t[:]
|
| 66 |
+
mask: np.ndarray, # uint8_t[:]
|
| 67 |
+
limit=...,
|
| 68 |
+
) -> None: ...
|
| 69 |
+
def pad_2d_inplace(
|
| 70 |
+
values: np.ndarray, # numeric_object_t[:, :]
|
| 71 |
+
mask: np.ndarray, # const uint8_t[:, :]
|
| 72 |
+
limit=...,
|
| 73 |
+
) -> None: ...
|
| 74 |
+
def backfill(
|
| 75 |
+
old: np.ndarray, # ndarray[numeric_object_t]
|
| 76 |
+
new: np.ndarray, # ndarray[numeric_object_t]
|
| 77 |
+
limit=...,
|
| 78 |
+
) -> npt.NDArray[np.intp]: ... # np.ndarray[np.intp, ndim=1]
|
| 79 |
+
def backfill_inplace(
|
| 80 |
+
values: np.ndarray, # numeric_object_t[:]
|
| 81 |
+
mask: np.ndarray, # uint8_t[:]
|
| 82 |
+
limit=...,
|
| 83 |
+
) -> None: ...
|
| 84 |
+
def backfill_2d_inplace(
|
| 85 |
+
values: np.ndarray, # numeric_object_t[:, :]
|
| 86 |
+
mask: np.ndarray, # const uint8_t[:, :]
|
| 87 |
+
limit=...,
|
| 88 |
+
) -> None: ...
|
| 89 |
+
def is_monotonic(
|
| 90 |
+
arr: np.ndarray, # ndarray[numeric_object_t, ndim=1]
|
| 91 |
+
timelike: bool,
|
| 92 |
+
) -> tuple[bool, bool, bool]: ...
|
| 93 |
+
|
| 94 |
+
# ----------------------------------------------------------------------
|
| 95 |
+
# rank_1d, rank_2d
|
| 96 |
+
# ----------------------------------------------------------------------
|
| 97 |
+
|
| 98 |
+
def rank_1d(
|
| 99 |
+
values: np.ndarray, # ndarray[numeric_object_t, ndim=1]
|
| 100 |
+
labels: np.ndarray | None = ..., # const int64_t[:]=None
|
| 101 |
+
is_datetimelike: bool = ...,
|
| 102 |
+
ties_method=...,
|
| 103 |
+
ascending: bool = ...,
|
| 104 |
+
pct: bool = ...,
|
| 105 |
+
na_option=...,
|
| 106 |
+
mask: npt.NDArray[np.bool_] | None = ...,
|
| 107 |
+
) -> np.ndarray: ... # np.ndarray[float64_t, ndim=1]
|
| 108 |
+
def rank_2d(
|
| 109 |
+
in_arr: np.ndarray, # ndarray[numeric_object_t, ndim=2]
|
| 110 |
+
axis: int = ...,
|
| 111 |
+
is_datetimelike: bool = ...,
|
| 112 |
+
ties_method=...,
|
| 113 |
+
ascending: bool = ...,
|
| 114 |
+
na_option=...,
|
| 115 |
+
pct: bool = ...,
|
| 116 |
+
) -> np.ndarray: ... # np.ndarray[float64_t, ndim=1]
|
| 117 |
+
def diff_2d(
|
| 118 |
+
arr: np.ndarray, # ndarray[diff_t, ndim=2]
|
| 119 |
+
out: np.ndarray, # ndarray[out_t, ndim=2]
|
| 120 |
+
periods: int,
|
| 121 |
+
axis: int,
|
| 122 |
+
datetimelike: bool = ...,
|
| 123 |
+
) -> None: ...
|
| 124 |
+
def ensure_platform_int(arr: object) -> npt.NDArray[np.intp]: ...
|
| 125 |
+
def ensure_object(arr: object) -> npt.NDArray[np.object_]: ...
|
| 126 |
+
def ensure_float64(arr: object) -> npt.NDArray[np.float64]: ...
|
| 127 |
+
def ensure_int8(arr: object) -> npt.NDArray[np.int8]: ...
|
| 128 |
+
def ensure_int16(arr: object) -> npt.NDArray[np.int16]: ...
|
| 129 |
+
def ensure_int32(arr: object) -> npt.NDArray[np.int32]: ...
|
| 130 |
+
def ensure_int64(arr: object) -> npt.NDArray[np.int64]: ...
|
| 131 |
+
def ensure_uint64(arr: object) -> npt.NDArray[np.uint64]: ...
|
| 132 |
+
def take_1d_int8_int8(
|
| 133 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 134 |
+
) -> None: ...
|
| 135 |
+
def take_1d_int8_int32(
|
| 136 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 137 |
+
) -> None: ...
|
| 138 |
+
def take_1d_int8_int64(
|
| 139 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 140 |
+
) -> None: ...
|
| 141 |
+
def take_1d_int8_float64(
|
| 142 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 143 |
+
) -> None: ...
|
| 144 |
+
def take_1d_int16_int16(
|
| 145 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 146 |
+
) -> None: ...
|
| 147 |
+
def take_1d_int16_int32(
|
| 148 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 149 |
+
) -> None: ...
|
| 150 |
+
def take_1d_int16_int64(
|
| 151 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 152 |
+
) -> None: ...
|
| 153 |
+
def take_1d_int16_float64(
|
| 154 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 155 |
+
) -> None: ...
|
| 156 |
+
def take_1d_int32_int32(
|
| 157 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 158 |
+
) -> None: ...
|
| 159 |
+
def take_1d_int32_int64(
|
| 160 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 161 |
+
) -> None: ...
|
| 162 |
+
def take_1d_int32_float64(
|
| 163 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 164 |
+
) -> None: ...
|
| 165 |
+
def take_1d_int64_int64(
|
| 166 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 167 |
+
) -> None: ...
|
| 168 |
+
def take_1d_uint16_uint16(
|
| 169 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 170 |
+
) -> None: ...
|
| 171 |
+
def take_1d_uint32_uint32(
|
| 172 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 173 |
+
) -> None: ...
|
| 174 |
+
def take_1d_uint64_uint64(
|
| 175 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 176 |
+
) -> None: ...
|
| 177 |
+
def take_1d_int64_float64(
|
| 178 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 179 |
+
) -> None: ...
|
| 180 |
+
def take_1d_float32_float32(
|
| 181 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 182 |
+
) -> None: ...
|
| 183 |
+
def take_1d_float32_float64(
|
| 184 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 185 |
+
) -> None: ...
|
| 186 |
+
def take_1d_float64_float64(
|
| 187 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 188 |
+
) -> None: ...
|
| 189 |
+
def take_1d_object_object(
|
| 190 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 191 |
+
) -> None: ...
|
| 192 |
+
def take_1d_bool_bool(
|
| 193 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 194 |
+
) -> None: ...
|
| 195 |
+
def take_1d_bool_object(
|
| 196 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 197 |
+
) -> None: ...
|
| 198 |
+
def take_2d_axis0_int8_int8(
|
| 199 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 200 |
+
) -> None: ...
|
| 201 |
+
def take_2d_axis0_int8_int32(
|
| 202 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 203 |
+
) -> None: ...
|
| 204 |
+
def take_2d_axis0_int8_int64(
|
| 205 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 206 |
+
) -> None: ...
|
| 207 |
+
def take_2d_axis0_int8_float64(
|
| 208 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 209 |
+
) -> None: ...
|
| 210 |
+
def take_2d_axis0_int16_int16(
|
| 211 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 212 |
+
) -> None: ...
|
| 213 |
+
def take_2d_axis0_int16_int32(
|
| 214 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 215 |
+
) -> None: ...
|
| 216 |
+
def take_2d_axis0_int16_int64(
|
| 217 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 218 |
+
) -> None: ...
|
| 219 |
+
def take_2d_axis0_int16_float64(
|
| 220 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 221 |
+
) -> None: ...
|
| 222 |
+
def take_2d_axis0_int32_int32(
|
| 223 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 224 |
+
) -> None: ...
|
| 225 |
+
def take_2d_axis0_int32_int64(
|
| 226 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 227 |
+
) -> None: ...
|
| 228 |
+
def take_2d_axis0_int32_float64(
|
| 229 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 230 |
+
) -> None: ...
|
| 231 |
+
def take_2d_axis0_int64_int64(
|
| 232 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 233 |
+
) -> None: ...
|
| 234 |
+
def take_2d_axis0_int64_float64(
|
| 235 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 236 |
+
) -> None: ...
|
| 237 |
+
def take_2d_axis0_uint16_uint16(
|
| 238 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 239 |
+
) -> None: ...
|
| 240 |
+
def take_2d_axis0_uint32_uint32(
|
| 241 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 242 |
+
) -> None: ...
|
| 243 |
+
def take_2d_axis0_uint64_uint64(
|
| 244 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 245 |
+
) -> None: ...
|
| 246 |
+
def take_2d_axis0_float32_float32(
|
| 247 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 248 |
+
) -> None: ...
|
| 249 |
+
def take_2d_axis0_float32_float64(
|
| 250 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 251 |
+
) -> None: ...
|
| 252 |
+
def take_2d_axis0_float64_float64(
|
| 253 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 254 |
+
) -> None: ...
|
| 255 |
+
def take_2d_axis0_object_object(
|
| 256 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 257 |
+
) -> None: ...
|
| 258 |
+
def take_2d_axis0_bool_bool(
|
| 259 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 260 |
+
) -> None: ...
|
| 261 |
+
def take_2d_axis0_bool_object(
|
| 262 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 263 |
+
) -> None: ...
|
| 264 |
+
def take_2d_axis1_int8_int8(
|
| 265 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 266 |
+
) -> None: ...
|
| 267 |
+
def take_2d_axis1_int8_int32(
|
| 268 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 269 |
+
) -> None: ...
|
| 270 |
+
def take_2d_axis1_int8_int64(
|
| 271 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 272 |
+
) -> None: ...
|
| 273 |
+
def take_2d_axis1_int8_float64(
|
| 274 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 275 |
+
) -> None: ...
|
| 276 |
+
def take_2d_axis1_int16_int16(
|
| 277 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 278 |
+
) -> None: ...
|
| 279 |
+
def take_2d_axis1_int16_int32(
|
| 280 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 281 |
+
) -> None: ...
|
| 282 |
+
def take_2d_axis1_int16_int64(
|
| 283 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 284 |
+
) -> None: ...
|
| 285 |
+
def take_2d_axis1_int16_float64(
|
| 286 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 287 |
+
) -> None: ...
|
| 288 |
+
def take_2d_axis1_int32_int32(
|
| 289 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 290 |
+
) -> None: ...
|
| 291 |
+
def take_2d_axis1_int32_int64(
|
| 292 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 293 |
+
) -> None: ...
|
| 294 |
+
def take_2d_axis1_int32_float64(
|
| 295 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 296 |
+
) -> None: ...
|
| 297 |
+
def take_2d_axis1_int64_int64(
|
| 298 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 299 |
+
) -> None: ...
|
| 300 |
+
def take_2d_axis1_uint16_uint16(
|
| 301 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 302 |
+
) -> None: ...
|
| 303 |
+
def take_2d_axis1_uint32_uint32(
|
| 304 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 305 |
+
) -> None: ...
|
| 306 |
+
def take_2d_axis1_uint64_uint64(
|
| 307 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 308 |
+
) -> None: ...
|
| 309 |
+
def take_2d_axis1_int64_float64(
|
| 310 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 311 |
+
) -> None: ...
|
| 312 |
+
def take_2d_axis1_float32_float32(
|
| 313 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 314 |
+
) -> None: ...
|
| 315 |
+
def take_2d_axis1_float32_float64(
|
| 316 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 317 |
+
) -> None: ...
|
| 318 |
+
def take_2d_axis1_float64_float64(
|
| 319 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 320 |
+
) -> None: ...
|
| 321 |
+
def take_2d_axis1_object_object(
|
| 322 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 323 |
+
) -> None: ...
|
| 324 |
+
def take_2d_axis1_bool_bool(
|
| 325 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 326 |
+
) -> None: ...
|
| 327 |
+
def take_2d_axis1_bool_object(
|
| 328 |
+
values: np.ndarray, indexer: npt.NDArray[np.intp], out: np.ndarray, fill_value=...
|
| 329 |
+
) -> None: ...
|
| 330 |
+
def take_2d_multi_int8_int8(
|
| 331 |
+
values: np.ndarray,
|
| 332 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 333 |
+
out: np.ndarray,
|
| 334 |
+
fill_value=...,
|
| 335 |
+
) -> None: ...
|
| 336 |
+
def take_2d_multi_int8_int32(
|
| 337 |
+
values: np.ndarray,
|
| 338 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 339 |
+
out: np.ndarray,
|
| 340 |
+
fill_value=...,
|
| 341 |
+
) -> None: ...
|
| 342 |
+
def take_2d_multi_int8_int64(
|
| 343 |
+
values: np.ndarray,
|
| 344 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 345 |
+
out: np.ndarray,
|
| 346 |
+
fill_value=...,
|
| 347 |
+
) -> None: ...
|
| 348 |
+
def take_2d_multi_int8_float64(
|
| 349 |
+
values: np.ndarray,
|
| 350 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 351 |
+
out: np.ndarray,
|
| 352 |
+
fill_value=...,
|
| 353 |
+
) -> None: ...
|
| 354 |
+
def take_2d_multi_int16_int16(
|
| 355 |
+
values: np.ndarray,
|
| 356 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 357 |
+
out: np.ndarray,
|
| 358 |
+
fill_value=...,
|
| 359 |
+
) -> None: ...
|
| 360 |
+
def take_2d_multi_int16_int32(
|
| 361 |
+
values: np.ndarray,
|
| 362 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 363 |
+
out: np.ndarray,
|
| 364 |
+
fill_value=...,
|
| 365 |
+
) -> None: ...
|
| 366 |
+
def take_2d_multi_int16_int64(
|
| 367 |
+
values: np.ndarray,
|
| 368 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 369 |
+
out: np.ndarray,
|
| 370 |
+
fill_value=...,
|
| 371 |
+
) -> None: ...
|
| 372 |
+
def take_2d_multi_int16_float64(
|
| 373 |
+
values: np.ndarray,
|
| 374 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 375 |
+
out: np.ndarray,
|
| 376 |
+
fill_value=...,
|
| 377 |
+
) -> None: ...
|
| 378 |
+
def take_2d_multi_int32_int32(
|
| 379 |
+
values: np.ndarray,
|
| 380 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 381 |
+
out: np.ndarray,
|
| 382 |
+
fill_value=...,
|
| 383 |
+
) -> None: ...
|
| 384 |
+
def take_2d_multi_int32_int64(
|
| 385 |
+
values: np.ndarray,
|
| 386 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 387 |
+
out: np.ndarray,
|
| 388 |
+
fill_value=...,
|
| 389 |
+
) -> None: ...
|
| 390 |
+
def take_2d_multi_int32_float64(
|
| 391 |
+
values: np.ndarray,
|
| 392 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 393 |
+
out: np.ndarray,
|
| 394 |
+
fill_value=...,
|
| 395 |
+
) -> None: ...
|
| 396 |
+
def take_2d_multi_int64_float64(
|
| 397 |
+
values: np.ndarray,
|
| 398 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 399 |
+
out: np.ndarray,
|
| 400 |
+
fill_value=...,
|
| 401 |
+
) -> None: ...
|
| 402 |
+
def take_2d_multi_float32_float32(
|
| 403 |
+
values: np.ndarray,
|
| 404 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 405 |
+
out: np.ndarray,
|
| 406 |
+
fill_value=...,
|
| 407 |
+
) -> None: ...
|
| 408 |
+
def take_2d_multi_float32_float64(
|
| 409 |
+
values: np.ndarray,
|
| 410 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 411 |
+
out: np.ndarray,
|
| 412 |
+
fill_value=...,
|
| 413 |
+
) -> None: ...
|
| 414 |
+
def take_2d_multi_float64_float64(
|
| 415 |
+
values: np.ndarray,
|
| 416 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 417 |
+
out: np.ndarray,
|
| 418 |
+
fill_value=...,
|
| 419 |
+
) -> None: ...
|
| 420 |
+
def take_2d_multi_object_object(
|
| 421 |
+
values: np.ndarray,
|
| 422 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 423 |
+
out: np.ndarray,
|
| 424 |
+
fill_value=...,
|
| 425 |
+
) -> None: ...
|
| 426 |
+
def take_2d_multi_bool_bool(
|
| 427 |
+
values: np.ndarray,
|
| 428 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 429 |
+
out: np.ndarray,
|
| 430 |
+
fill_value=...,
|
| 431 |
+
) -> None: ...
|
| 432 |
+
def take_2d_multi_bool_object(
|
| 433 |
+
values: np.ndarray,
|
| 434 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 435 |
+
out: np.ndarray,
|
| 436 |
+
fill_value=...,
|
| 437 |
+
) -> None: ...
|
| 438 |
+
def take_2d_multi_int64_int64(
|
| 439 |
+
values: np.ndarray,
|
| 440 |
+
indexer: tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]],
|
| 441 |
+
out: np.ndarray,
|
| 442 |
+
fill_value=...,
|
| 443 |
+
) -> None: ...
|
pandas/_libs/arrays.pyi
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Sequence
|
| 2 |
+
from typing import Self
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
from pandas._typing import (
|
| 7 |
+
AxisInt,
|
| 8 |
+
DtypeObj,
|
| 9 |
+
Shape,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
class NDArrayBacked:
|
| 13 |
+
_dtype: DtypeObj
|
| 14 |
+
_ndarray: np.ndarray
|
| 15 |
+
def __init__(self, values: np.ndarray, dtype: DtypeObj) -> None: ...
|
| 16 |
+
@classmethod
|
| 17 |
+
def _simple_new(cls, values: np.ndarray, dtype: DtypeObj) -> Self: ...
|
| 18 |
+
def _from_backing_data(self, values: np.ndarray) -> Self: ...
|
| 19 |
+
def __setstate__(self, state) -> None: ...
|
| 20 |
+
def __len__(self) -> int: ...
|
| 21 |
+
@property
|
| 22 |
+
def shape(self) -> Shape: ...
|
| 23 |
+
@property
|
| 24 |
+
def ndim(self) -> int: ...
|
| 25 |
+
@property
|
| 26 |
+
def size(self) -> int: ...
|
| 27 |
+
@property
|
| 28 |
+
def nbytes(self) -> int: ...
|
| 29 |
+
def copy(self, order=...) -> Self: ...
|
| 30 |
+
def delete(self, loc, axis=...) -> Self: ...
|
| 31 |
+
def swapaxes(self, axis1, axis2) -> Self: ...
|
| 32 |
+
def repeat(self, repeats: int | Sequence[int], axis: int | None = ...) -> Self: ...
|
| 33 |
+
def reshape(self, *args, **kwargs) -> Self: ...
|
| 34 |
+
def ravel(self, order=...) -> Self: ...
|
| 35 |
+
@property
|
| 36 |
+
def T(self) -> Self: ...
|
| 37 |
+
@classmethod
|
| 38 |
+
def _concat_same_type(
|
| 39 |
+
cls, to_concat: Sequence[Self], axis: AxisInt = ...
|
| 40 |
+
) -> Self: ...
|
pandas/_libs/byteswap.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (49.7 kB). View file
|
|
|
pandas/_libs/byteswap.pyi
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def read_float_with_byteswap(data: bytes, offset: int, byteswap: bool) -> float: ...
|
| 2 |
+
def read_double_with_byteswap(data: bytes, offset: int, byteswap: bool) -> float: ...
|
| 3 |
+
def read_uint16_with_byteswap(data: bytes, offset: int, byteswap: bool) -> int: ...
|
| 4 |
+
def read_uint32_with_byteswap(data: bytes, offset: int, byteswap: bool) -> int: ...
|
| 5 |
+
def read_uint64_with_byteswap(data: bytes, offset: int, byteswap: bool) -> int: ...
|
pandas/_libs/groupby.pyi
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Literal
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from pandas._typing import npt
|
| 6 |
+
|
| 7 |
+
def group_median_float64(
|
| 8 |
+
out: np.ndarray, # ndarray[float64_t, ndim=2]
|
| 9 |
+
counts: npt.NDArray[np.int64],
|
| 10 |
+
values: np.ndarray, # ndarray[float64_t, ndim=2]
|
| 11 |
+
labels: npt.NDArray[np.int64],
|
| 12 |
+
min_count: int = ..., # Py_ssize_t
|
| 13 |
+
mask: np.ndarray | None = ...,
|
| 14 |
+
result_mask: np.ndarray | None = ...,
|
| 15 |
+
is_datetimelike: bool = ..., # bint
|
| 16 |
+
skipna: bool = ...,
|
| 17 |
+
) -> None: ...
|
| 18 |
+
def group_cumprod(
|
| 19 |
+
out: np.ndarray, # float64_t[:, ::1]
|
| 20 |
+
values: np.ndarray, # const float64_t[:, :]
|
| 21 |
+
labels: np.ndarray, # const int64_t[:]
|
| 22 |
+
ngroups: int,
|
| 23 |
+
is_datetimelike: bool,
|
| 24 |
+
skipna: bool = ...,
|
| 25 |
+
mask: np.ndarray | None = ...,
|
| 26 |
+
result_mask: np.ndarray | None = ...,
|
| 27 |
+
) -> None: ...
|
| 28 |
+
def group_cumsum(
|
| 29 |
+
out: np.ndarray, # int64float_t[:, ::1]
|
| 30 |
+
values: np.ndarray, # ndarray[int64float_t, ndim=2]
|
| 31 |
+
labels: np.ndarray, # const int64_t[:]
|
| 32 |
+
ngroups: int,
|
| 33 |
+
is_datetimelike: bool,
|
| 34 |
+
skipna: bool = ...,
|
| 35 |
+
mask: np.ndarray | None = ...,
|
| 36 |
+
result_mask: np.ndarray | None = ...,
|
| 37 |
+
) -> None: ...
|
| 38 |
+
def group_shift_indexer(
|
| 39 |
+
out: np.ndarray, # int64_t[::1]
|
| 40 |
+
labels: np.ndarray, # const int64_t[:]
|
| 41 |
+
ngroups: int,
|
| 42 |
+
periods: int,
|
| 43 |
+
) -> None: ...
|
| 44 |
+
def group_fillna_indexer(
|
| 45 |
+
out: np.ndarray, # ndarray[intp_t]
|
| 46 |
+
labels: np.ndarray, # ndarray[int64_t]
|
| 47 |
+
mask: npt.NDArray[np.uint8],
|
| 48 |
+
limit: int, # int64_t
|
| 49 |
+
compute_ffill: bool,
|
| 50 |
+
ngroups: int,
|
| 51 |
+
) -> None: ...
|
| 52 |
+
def group_any_all(
|
| 53 |
+
out: np.ndarray, # uint8_t[::1]
|
| 54 |
+
values: np.ndarray, # const uint8_t[::1]
|
| 55 |
+
labels: np.ndarray, # const int64_t[:]
|
| 56 |
+
mask: np.ndarray, # const uint8_t[::1]
|
| 57 |
+
val_test: Literal["any", "all"],
|
| 58 |
+
skipna: bool,
|
| 59 |
+
result_mask: np.ndarray | None,
|
| 60 |
+
) -> None: ...
|
| 61 |
+
def group_sum(
|
| 62 |
+
out: np.ndarray, # complexfloatingintuint_t[:, ::1]
|
| 63 |
+
counts: np.ndarray, # int64_t[::1]
|
| 64 |
+
values: np.ndarray, # ndarray[complexfloatingintuint_t, ndim=2]
|
| 65 |
+
labels: np.ndarray, # const intp_t[:]
|
| 66 |
+
mask: np.ndarray | None,
|
| 67 |
+
result_mask: np.ndarray | None = ...,
|
| 68 |
+
min_count: int = ...,
|
| 69 |
+
is_datetimelike: bool = ...,
|
| 70 |
+
initial: object = ...,
|
| 71 |
+
skipna: bool = ...,
|
| 72 |
+
) -> None: ...
|
| 73 |
+
def group_prod(
|
| 74 |
+
out: np.ndarray, # int64float_t[:, ::1]
|
| 75 |
+
counts: np.ndarray, # int64_t[::1]
|
| 76 |
+
values: np.ndarray, # ndarray[int64float_t, ndim=2]
|
| 77 |
+
labels: np.ndarray, # const intp_t[:]
|
| 78 |
+
mask: np.ndarray | None,
|
| 79 |
+
result_mask: np.ndarray | None = ...,
|
| 80 |
+
min_count: int = ...,
|
| 81 |
+
skipna: bool = ...,
|
| 82 |
+
) -> None: ...
|
| 83 |
+
def group_var(
|
| 84 |
+
out: np.ndarray, # floating[:, ::1]
|
| 85 |
+
counts: np.ndarray, # int64_t[::1]
|
| 86 |
+
values: np.ndarray, # ndarray[floating, ndim=2]
|
| 87 |
+
labels: np.ndarray, # const intp_t[:]
|
| 88 |
+
min_count: int = ..., # Py_ssize_t
|
| 89 |
+
ddof: int = ..., # int64_t
|
| 90 |
+
mask: np.ndarray | None = ...,
|
| 91 |
+
result_mask: np.ndarray | None = ...,
|
| 92 |
+
is_datetimelike: bool = ...,
|
| 93 |
+
name: str = ...,
|
| 94 |
+
skipna: bool = ...,
|
| 95 |
+
) -> None: ...
|
| 96 |
+
def group_skew(
|
| 97 |
+
out: np.ndarray, # float64_t[:, ::1]
|
| 98 |
+
counts: np.ndarray, # int64_t[::1]
|
| 99 |
+
values: np.ndarray, # ndarray[float64_T, ndim=2]
|
| 100 |
+
labels: np.ndarray, # const intp_t[::1]
|
| 101 |
+
mask: np.ndarray | None = ...,
|
| 102 |
+
result_mask: np.ndarray | None = ...,
|
| 103 |
+
skipna: bool = ...,
|
| 104 |
+
) -> None: ...
|
| 105 |
+
def group_kurt(
|
| 106 |
+
out: np.ndarray, # float64_t[:, ::1]
|
| 107 |
+
counts: np.ndarray, # int64_t[::1]
|
| 108 |
+
values: np.ndarray, # ndarray[float64_T, ndim=2]
|
| 109 |
+
labels: np.ndarray, # const intp_t[::1]
|
| 110 |
+
mask: np.ndarray | None = ...,
|
| 111 |
+
result_mask: np.ndarray | None = ...,
|
| 112 |
+
skipna: bool = ...,
|
| 113 |
+
) -> None: ...
|
| 114 |
+
def group_mean(
|
| 115 |
+
out: np.ndarray, # floating[:, ::1]
|
| 116 |
+
counts: np.ndarray, # int64_t[::1]
|
| 117 |
+
values: np.ndarray, # ndarray[floating, ndim=2]
|
| 118 |
+
labels: np.ndarray, # const intp_t[:]
|
| 119 |
+
min_count: int = ..., # Py_ssize_t
|
| 120 |
+
is_datetimelike: bool = ..., # bint
|
| 121 |
+
mask: np.ndarray | None = ...,
|
| 122 |
+
result_mask: np.ndarray | None = ...,
|
| 123 |
+
skipna: bool = ...,
|
| 124 |
+
) -> None: ...
|
| 125 |
+
def group_ohlc(
|
| 126 |
+
out: np.ndarray, # floatingintuint_t[:, ::1]
|
| 127 |
+
counts: np.ndarray, # int64_t[::1]
|
| 128 |
+
values: np.ndarray, # ndarray[floatingintuint_t, ndim=2]
|
| 129 |
+
labels: np.ndarray, # const intp_t[:]
|
| 130 |
+
min_count: int = ...,
|
| 131 |
+
mask: np.ndarray | None = ...,
|
| 132 |
+
result_mask: np.ndarray | None = ...,
|
| 133 |
+
) -> None: ...
|
| 134 |
+
def group_quantile(
|
| 135 |
+
out: npt.NDArray[np.float64],
|
| 136 |
+
values: np.ndarray, # ndarray[numeric, ndim=1]
|
| 137 |
+
labels: npt.NDArray[np.intp],
|
| 138 |
+
mask: npt.NDArray[np.uint8],
|
| 139 |
+
qs: npt.NDArray[np.float64], # const
|
| 140 |
+
starts: npt.NDArray[np.int64],
|
| 141 |
+
ends: npt.NDArray[np.int64],
|
| 142 |
+
interpolation: Literal["linear", "lower", "higher", "nearest", "midpoint"],
|
| 143 |
+
result_mask: np.ndarray | None,
|
| 144 |
+
is_datetimelike: bool,
|
| 145 |
+
) -> None: ...
|
| 146 |
+
def group_last(
|
| 147 |
+
out: np.ndarray, # rank_t[:, ::1]
|
| 148 |
+
counts: np.ndarray, # int64_t[::1]
|
| 149 |
+
values: np.ndarray, # ndarray[rank_t, ndim=2]
|
| 150 |
+
labels: np.ndarray, # const int64_t[:]
|
| 151 |
+
mask: npt.NDArray[np.bool_] | None,
|
| 152 |
+
result_mask: npt.NDArray[np.bool_] | None = ...,
|
| 153 |
+
min_count: int = ..., # Py_ssize_t
|
| 154 |
+
is_datetimelike: bool = ...,
|
| 155 |
+
skipna: bool = ...,
|
| 156 |
+
) -> None: ...
|
| 157 |
+
def group_nth(
|
| 158 |
+
out: np.ndarray, # rank_t[:, ::1]
|
| 159 |
+
counts: np.ndarray, # int64_t[::1]
|
| 160 |
+
values: np.ndarray, # ndarray[rank_t, ndim=2]
|
| 161 |
+
labels: np.ndarray, # const int64_t[:]
|
| 162 |
+
mask: npt.NDArray[np.bool_] | None,
|
| 163 |
+
result_mask: npt.NDArray[np.bool_] | None = ...,
|
| 164 |
+
min_count: int = ..., # int64_t
|
| 165 |
+
rank: int = ..., # int64_t
|
| 166 |
+
is_datetimelike: bool = ...,
|
| 167 |
+
skipna: bool = ...,
|
| 168 |
+
) -> None: ...
|
| 169 |
+
def group_rank(
|
| 170 |
+
out: np.ndarray, # float64_t[:, ::1]
|
| 171 |
+
values: np.ndarray, # ndarray[rank_t, ndim=2]
|
| 172 |
+
labels: np.ndarray, # const int64_t[:]
|
| 173 |
+
ngroups: int,
|
| 174 |
+
is_datetimelike: bool,
|
| 175 |
+
ties_method: Literal["average", "min", "max", "first", "dense"] = ...,
|
| 176 |
+
ascending: bool = ...,
|
| 177 |
+
pct: bool = ...,
|
| 178 |
+
na_option: Literal["keep", "top", "bottom"] = ...,
|
| 179 |
+
mask: npt.NDArray[np.bool_] | None = ...,
|
| 180 |
+
) -> None: ...
|
| 181 |
+
def group_max(
|
| 182 |
+
out: np.ndarray, # groupby_t[:, ::1]
|
| 183 |
+
counts: np.ndarray, # int64_t[::1]
|
| 184 |
+
values: np.ndarray, # ndarray[groupby_t, ndim=2]
|
| 185 |
+
labels: np.ndarray, # const int64_t[:]
|
| 186 |
+
min_count: int = ...,
|
| 187 |
+
is_datetimelike: bool = ...,
|
| 188 |
+
mask: np.ndarray | None = ...,
|
| 189 |
+
result_mask: np.ndarray | None = ...,
|
| 190 |
+
skipna: bool = ...,
|
| 191 |
+
) -> None: ...
|
| 192 |
+
def group_min(
|
| 193 |
+
out: np.ndarray, # groupby_t[:, ::1]
|
| 194 |
+
counts: np.ndarray, # int64_t[::1]
|
| 195 |
+
values: np.ndarray, # ndarray[groupby_t, ndim=2]
|
| 196 |
+
labels: np.ndarray, # const int64_t[:]
|
| 197 |
+
min_count: int = ...,
|
| 198 |
+
is_datetimelike: bool = ...,
|
| 199 |
+
mask: np.ndarray | None = ...,
|
| 200 |
+
result_mask: np.ndarray | None = ...,
|
| 201 |
+
skipna: bool = ...,
|
| 202 |
+
) -> None: ...
|
| 203 |
+
def group_idxmin_idxmax(
|
| 204 |
+
out: npt.NDArray[np.intp],
|
| 205 |
+
counts: npt.NDArray[np.int64],
|
| 206 |
+
values: np.ndarray, # ndarray[groupby_t, ndim=2]
|
| 207 |
+
labels: npt.NDArray[np.intp],
|
| 208 |
+
min_count: int = ...,
|
| 209 |
+
is_datetimelike: bool = ...,
|
| 210 |
+
mask: np.ndarray | None = ...,
|
| 211 |
+
name: str = ...,
|
| 212 |
+
skipna: bool = ...,
|
| 213 |
+
result_mask: np.ndarray | None = ...,
|
| 214 |
+
) -> None: ...
|
| 215 |
+
def group_cummin(
|
| 216 |
+
out: np.ndarray, # groupby_t[:, ::1]
|
| 217 |
+
values: np.ndarray, # ndarray[groupby_t, ndim=2]
|
| 218 |
+
labels: np.ndarray, # const int64_t[:]
|
| 219 |
+
ngroups: int,
|
| 220 |
+
is_datetimelike: bool,
|
| 221 |
+
mask: np.ndarray | None = ...,
|
| 222 |
+
result_mask: np.ndarray | None = ...,
|
| 223 |
+
skipna: bool = ...,
|
| 224 |
+
) -> None: ...
|
| 225 |
+
def group_cummax(
|
| 226 |
+
out: np.ndarray, # groupby_t[:, ::1]
|
| 227 |
+
values: np.ndarray, # ndarray[groupby_t, ndim=2]
|
| 228 |
+
labels: np.ndarray, # const int64_t[:]
|
| 229 |
+
ngroups: int,
|
| 230 |
+
is_datetimelike: bool,
|
| 231 |
+
mask: np.ndarray | None = ...,
|
| 232 |
+
result_mask: np.ndarray | None = ...,
|
| 233 |
+
skipna: bool = ...,
|
| 234 |
+
) -> None: ...
|
pandas/_libs/hashing.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (78.6 kB). View file
|
|
|
pandas/_libs/hashing.pyi
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
from pandas._typing import npt
|
| 4 |
+
|
| 5 |
+
def hash_object_array(
|
| 6 |
+
arr: npt.NDArray[np.object_],
|
| 7 |
+
key: str,
|
| 8 |
+
encoding: str = ...,
|
| 9 |
+
) -> npt.NDArray[np.uint64]: ...
|
pandas/_libs/hashtable.pyi
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Hashable
|
| 2 |
+
from typing import (
|
| 3 |
+
Any,
|
| 4 |
+
Literal,
|
| 5 |
+
overload,
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from pandas._typing import npt
|
| 11 |
+
|
| 12 |
+
def unique_label_indices(
|
| 13 |
+
labels: np.ndarray, # const int64_t[:]
|
| 14 |
+
) -> np.ndarray: ...
|
| 15 |
+
|
| 16 |
+
class Factorizer:
|
| 17 |
+
count: int
|
| 18 |
+
uniques: Any
|
| 19 |
+
def __init__(self, size_hint: int, uses_mask: bool = False) -> None: ...
|
| 20 |
+
def get_count(self) -> int: ...
|
| 21 |
+
def factorize(
|
| 22 |
+
self,
|
| 23 |
+
values: np.ndarray,
|
| 24 |
+
na_sentinel=...,
|
| 25 |
+
na_value=...,
|
| 26 |
+
mask=...,
|
| 27 |
+
) -> npt.NDArray[np.intp]: ...
|
| 28 |
+
def hash_inner_join(
|
| 29 |
+
self, values: np.ndarray, mask=...
|
| 30 |
+
) -> tuple[np.ndarray, np.ndarray]: ...
|
| 31 |
+
|
| 32 |
+
class ObjectFactorizer(Factorizer):
|
| 33 |
+
table: PyObjectHashTable
|
| 34 |
+
uniques: ObjectVector
|
| 35 |
+
|
| 36 |
+
class Int64Factorizer(Factorizer):
|
| 37 |
+
table: Int64HashTable
|
| 38 |
+
uniques: Int64Vector
|
| 39 |
+
|
| 40 |
+
class UInt64Factorizer(Factorizer):
|
| 41 |
+
table: UInt64HashTable
|
| 42 |
+
uniques: UInt64Vector
|
| 43 |
+
|
| 44 |
+
class Int32Factorizer(Factorizer):
|
| 45 |
+
table: Int32HashTable
|
| 46 |
+
uniques: Int32Vector
|
| 47 |
+
|
| 48 |
+
class UInt32Factorizer(Factorizer):
|
| 49 |
+
table: UInt32HashTable
|
| 50 |
+
uniques: UInt32Vector
|
| 51 |
+
|
| 52 |
+
class Int16Factorizer(Factorizer):
|
| 53 |
+
table: Int16HashTable
|
| 54 |
+
uniques: Int16Vector
|
| 55 |
+
|
| 56 |
+
class UInt16Factorizer(Factorizer):
|
| 57 |
+
table: UInt16HashTable
|
| 58 |
+
uniques: UInt16Vector
|
| 59 |
+
|
| 60 |
+
class Int8Factorizer(Factorizer):
|
| 61 |
+
table: Int8HashTable
|
| 62 |
+
uniques: Int8Vector
|
| 63 |
+
|
| 64 |
+
class UInt8Factorizer(Factorizer):
|
| 65 |
+
table: UInt8HashTable
|
| 66 |
+
uniques: UInt8Vector
|
| 67 |
+
|
| 68 |
+
class Float64Factorizer(Factorizer):
|
| 69 |
+
table: Float64HashTable
|
| 70 |
+
uniques: Float64Vector
|
| 71 |
+
|
| 72 |
+
class Float32Factorizer(Factorizer):
|
| 73 |
+
table: Float32HashTable
|
| 74 |
+
uniques: Float32Vector
|
| 75 |
+
|
| 76 |
+
class Complex64Factorizer(Factorizer):
|
| 77 |
+
table: Complex64HashTable
|
| 78 |
+
uniques: Complex64Vector
|
| 79 |
+
|
| 80 |
+
class Complex128Factorizer(Factorizer):
|
| 81 |
+
table: Complex128HashTable
|
| 82 |
+
uniques: Complex128Vector
|
| 83 |
+
|
| 84 |
+
class Int64Vector:
|
| 85 |
+
def __init__(self, *args) -> None: ...
|
| 86 |
+
def __len__(self) -> int: ...
|
| 87 |
+
def to_array(self) -> npt.NDArray[np.int64]: ...
|
| 88 |
+
|
| 89 |
+
class Int32Vector:
|
| 90 |
+
def __init__(self, *args) -> None: ...
|
| 91 |
+
def __len__(self) -> int: ...
|
| 92 |
+
def to_array(self) -> npt.NDArray[np.int32]: ...
|
| 93 |
+
|
| 94 |
+
class Int16Vector:
|
| 95 |
+
def __init__(self, *args) -> None: ...
|
| 96 |
+
def __len__(self) -> int: ...
|
| 97 |
+
def to_array(self) -> npt.NDArray[np.int16]: ...
|
| 98 |
+
|
| 99 |
+
class Int8Vector:
|
| 100 |
+
def __init__(self, *args) -> None: ...
|
| 101 |
+
def __len__(self) -> int: ...
|
| 102 |
+
def to_array(self) -> npt.NDArray[np.int8]: ...
|
| 103 |
+
|
| 104 |
+
class UInt64Vector:
|
| 105 |
+
def __init__(self, *args) -> None: ...
|
| 106 |
+
def __len__(self) -> int: ...
|
| 107 |
+
def to_array(self) -> npt.NDArray[np.uint64]: ...
|
| 108 |
+
|
| 109 |
+
class UInt32Vector:
|
| 110 |
+
def __init__(self, *args) -> None: ...
|
| 111 |
+
def __len__(self) -> int: ...
|
| 112 |
+
def to_array(self) -> npt.NDArray[np.uint32]: ...
|
| 113 |
+
|
| 114 |
+
class UInt16Vector:
|
| 115 |
+
def __init__(self, *args) -> None: ...
|
| 116 |
+
def __len__(self) -> int: ...
|
| 117 |
+
def to_array(self) -> npt.NDArray[np.uint16]: ...
|
| 118 |
+
|
| 119 |
+
class UInt8Vector:
|
| 120 |
+
def __init__(self, *args) -> None: ...
|
| 121 |
+
def __len__(self) -> int: ...
|
| 122 |
+
def to_array(self) -> npt.NDArray[np.uint8]: ...
|
| 123 |
+
|
| 124 |
+
class Float64Vector:
|
| 125 |
+
def __init__(self, *args) -> None: ...
|
| 126 |
+
def __len__(self) -> int: ...
|
| 127 |
+
def to_array(self) -> npt.NDArray[np.float64]: ...
|
| 128 |
+
|
| 129 |
+
class Float32Vector:
|
| 130 |
+
def __init__(self, *args) -> None: ...
|
| 131 |
+
def __len__(self) -> int: ...
|
| 132 |
+
def to_array(self) -> npt.NDArray[np.float32]: ...
|
| 133 |
+
|
| 134 |
+
class Complex128Vector:
|
| 135 |
+
def __init__(self, *args) -> None: ...
|
| 136 |
+
def __len__(self) -> int: ...
|
| 137 |
+
def to_array(self) -> npt.NDArray[np.complex128]: ...
|
| 138 |
+
|
| 139 |
+
class Complex64Vector:
|
| 140 |
+
def __init__(self, *args) -> None: ...
|
| 141 |
+
def __len__(self) -> int: ...
|
| 142 |
+
def to_array(self) -> npt.NDArray[np.complex64]: ...
|
| 143 |
+
|
| 144 |
+
class StringVector:
|
| 145 |
+
def __init__(self, *args) -> None: ...
|
| 146 |
+
def __len__(self) -> int: ...
|
| 147 |
+
def to_array(self) -> npt.NDArray[np.object_]: ...
|
| 148 |
+
|
| 149 |
+
class ObjectVector:
|
| 150 |
+
def __init__(self, *args) -> None: ...
|
| 151 |
+
def __len__(self) -> int: ...
|
| 152 |
+
def to_array(self) -> npt.NDArray[np.object_]: ...
|
| 153 |
+
|
| 154 |
+
class HashTable:
|
| 155 |
+
# NB: The base HashTable class does _not_ actually have these methods;
|
| 156 |
+
# we are putting them here for the sake of mypy to avoid
|
| 157 |
+
# reproducing them in each subclass below.
|
| 158 |
+
def __init__(self, size_hint: int = ..., uses_mask: bool = ...) -> None: ...
|
| 159 |
+
def __len__(self) -> int: ...
|
| 160 |
+
def __contains__(self, key: Hashable) -> bool: ...
|
| 161 |
+
def sizeof(self, deep: bool = ...) -> int: ...
|
| 162 |
+
def get_state(self) -> dict[str, int]: ...
|
| 163 |
+
# TODO: `val/key` type is subclass-specific
|
| 164 |
+
def get_item(self, val): ... # TODO: return type?
|
| 165 |
+
def set_item(self, key, val) -> None: ...
|
| 166 |
+
def get_na(self): ... # TODO: return type?
|
| 167 |
+
def set_na(self, val) -> None: ...
|
| 168 |
+
def map_locations(
|
| 169 |
+
self,
|
| 170 |
+
values: np.ndarray, # np.ndarray[subclass-specific]
|
| 171 |
+
mask: npt.NDArray[np.bool_] | None = ...,
|
| 172 |
+
) -> None: ...
|
| 173 |
+
def lookup(
|
| 174 |
+
self,
|
| 175 |
+
values: np.ndarray, # np.ndarray[subclass-specific]
|
| 176 |
+
mask: npt.NDArray[np.bool_] | None = ...,
|
| 177 |
+
) -> npt.NDArray[np.intp]: ...
|
| 178 |
+
def get_labels(
|
| 179 |
+
self,
|
| 180 |
+
values: np.ndarray, # np.ndarray[subclass-specific]
|
| 181 |
+
uniques, # SubclassTypeVector
|
| 182 |
+
count_prior: int = ...,
|
| 183 |
+
na_sentinel: int = ...,
|
| 184 |
+
na_value: object = ...,
|
| 185 |
+
mask=...,
|
| 186 |
+
) -> npt.NDArray[np.intp]: ...
|
| 187 |
+
@overload
|
| 188 |
+
def unique(
|
| 189 |
+
self,
|
| 190 |
+
values: np.ndarray, # np.ndarray[subclass-specific]
|
| 191 |
+
*,
|
| 192 |
+
return_inverse: Literal[False] = ...,
|
| 193 |
+
mask: None = ...,
|
| 194 |
+
) -> np.ndarray: ... # np.ndarray[subclass-specific]
|
| 195 |
+
@overload
|
| 196 |
+
def unique(
|
| 197 |
+
self,
|
| 198 |
+
values: np.ndarray, # np.ndarray[subclass-specific]
|
| 199 |
+
*,
|
| 200 |
+
return_inverse: Literal[True],
|
| 201 |
+
mask: None = ...,
|
| 202 |
+
) -> tuple[np.ndarray, npt.NDArray[np.intp]]: ... # np.ndarray[subclass-specific]
|
| 203 |
+
@overload
|
| 204 |
+
def unique(
|
| 205 |
+
self,
|
| 206 |
+
values: np.ndarray, # np.ndarray[subclass-specific]
|
| 207 |
+
*,
|
| 208 |
+
return_inverse: Literal[False] = ...,
|
| 209 |
+
mask: npt.NDArray[np.bool_],
|
| 210 |
+
) -> tuple[
|
| 211 |
+
np.ndarray,
|
| 212 |
+
npt.NDArray[np.bool_],
|
| 213 |
+
]: ... # np.ndarray[subclass-specific]
|
| 214 |
+
def factorize(
|
| 215 |
+
self,
|
| 216 |
+
values: np.ndarray, # np.ndarray[subclass-specific]
|
| 217 |
+
na_sentinel: int = ...,
|
| 218 |
+
na_value: object = ...,
|
| 219 |
+
mask=...,
|
| 220 |
+
ignore_na: bool = True,
|
| 221 |
+
) -> tuple[np.ndarray, npt.NDArray[np.intp]]: ... # np.ndarray[subclass-specific]
|
| 222 |
+
def hash_inner_join(
|
| 223 |
+
self, values: np.ndarray, mask=...
|
| 224 |
+
) -> tuple[np.ndarray, np.ndarray]: ...
|
| 225 |
+
|
| 226 |
+
class Complex128HashTable(HashTable): ...
|
| 227 |
+
class Complex64HashTable(HashTable): ...
|
| 228 |
+
class Float64HashTable(HashTable): ...
|
| 229 |
+
class Float32HashTable(HashTable): ...
|
| 230 |
+
|
| 231 |
+
class Int64HashTable(HashTable):
|
| 232 |
+
# Only Int64HashTable has get_labels_groupby, map_keys_to_values
|
| 233 |
+
def get_labels_groupby(
|
| 234 |
+
self,
|
| 235 |
+
values: npt.NDArray[np.int64], # const int64_t[:]
|
| 236 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.int64]]: ...
|
| 237 |
+
def map_keys_to_values(
|
| 238 |
+
self,
|
| 239 |
+
keys: npt.NDArray[np.int64],
|
| 240 |
+
values: npt.NDArray[np.int64], # const int64_t[:]
|
| 241 |
+
) -> None: ...
|
| 242 |
+
|
| 243 |
+
class Int32HashTable(HashTable): ...
|
| 244 |
+
class Int16HashTable(HashTable): ...
|
| 245 |
+
class Int8HashTable(HashTable): ...
|
| 246 |
+
class UInt64HashTable(HashTable): ...
|
| 247 |
+
class UInt32HashTable(HashTable): ...
|
| 248 |
+
class UInt16HashTable(HashTable): ...
|
| 249 |
+
class UInt8HashTable(HashTable): ...
|
| 250 |
+
class StringHashTable(HashTable): ...
|
| 251 |
+
class PyObjectHashTable(HashTable): ...
|
| 252 |
+
class IntpHashTable(HashTable): ...
|
| 253 |
+
|
| 254 |
+
def duplicated(
|
| 255 |
+
values: np.ndarray,
|
| 256 |
+
keep: Literal["last", "first", False] = ...,
|
| 257 |
+
mask: npt.NDArray[np.bool_] | None = ...,
|
| 258 |
+
) -> npt.NDArray[np.bool_]: ...
|
| 259 |
+
def mode(
|
| 260 |
+
values: np.ndarray, dropna: bool, mask: npt.NDArray[np.bool_] | None = ...
|
| 261 |
+
) -> np.ndarray: ...
|
| 262 |
+
def value_count(
|
| 263 |
+
values: np.ndarray,
|
| 264 |
+
dropna: bool,
|
| 265 |
+
mask: npt.NDArray[np.bool_] | None = ...,
|
| 266 |
+
) -> tuple[np.ndarray, npt.NDArray[np.int64], int]: ... # np.ndarray[same-as-values]
|
| 267 |
+
|
| 268 |
+
# arr and values should have same dtype
|
| 269 |
+
def ismember(
|
| 270 |
+
arr: np.ndarray,
|
| 271 |
+
values: np.ndarray,
|
| 272 |
+
) -> npt.NDArray[np.bool_]: ...
|
| 273 |
+
def object_hash(obj) -> int: ...
|
| 274 |
+
def objects_are_equal(a, b) -> bool: ...
|
pandas/_libs/index.pyi
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
from pandas._typing import npt
|
| 4 |
+
|
| 5 |
+
from pandas import (
|
| 6 |
+
Index,
|
| 7 |
+
MultiIndex,
|
| 8 |
+
)
|
| 9 |
+
from pandas.core.arrays import ExtensionArray
|
| 10 |
+
|
| 11 |
+
multiindex_nulls_shift: int
|
| 12 |
+
|
| 13 |
+
class IndexEngine:
|
| 14 |
+
over_size_threshold: bool
|
| 15 |
+
def __init__(self, values: np.ndarray) -> None: ...
|
| 16 |
+
def __contains__(self, val: object) -> bool: ...
|
| 17 |
+
|
| 18 |
+
# -> int | slice | np.ndarray[bool]
|
| 19 |
+
def get_loc(self, val: object) -> int | slice | np.ndarray: ...
|
| 20 |
+
def sizeof(self, deep: bool = ...) -> int: ...
|
| 21 |
+
def __sizeof__(self) -> int: ...
|
| 22 |
+
@property
|
| 23 |
+
def is_unique(self) -> bool: ...
|
| 24 |
+
@property
|
| 25 |
+
def is_monotonic_increasing(self) -> bool: ...
|
| 26 |
+
@property
|
| 27 |
+
def is_monotonic_decreasing(self) -> bool: ...
|
| 28 |
+
@property
|
| 29 |
+
def is_mapping_populated(self) -> bool: ...
|
| 30 |
+
def clear_mapping(self): ...
|
| 31 |
+
def get_indexer(self, values: np.ndarray) -> npt.NDArray[np.intp]: ...
|
| 32 |
+
def get_indexer_non_unique(
|
| 33 |
+
self,
|
| 34 |
+
targets: np.ndarray,
|
| 35 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 36 |
+
|
| 37 |
+
class MaskedIndexEngine(IndexEngine):
|
| 38 |
+
def __init__(self, values: object) -> None: ...
|
| 39 |
+
def get_indexer_non_unique(
|
| 40 |
+
self, targets: object
|
| 41 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 42 |
+
|
| 43 |
+
class Float64Engine(IndexEngine): ...
|
| 44 |
+
class Float32Engine(IndexEngine): ...
|
| 45 |
+
class Complex128Engine(IndexEngine): ...
|
| 46 |
+
class Complex64Engine(IndexEngine): ...
|
| 47 |
+
class Int64Engine(IndexEngine): ...
|
| 48 |
+
class Int32Engine(IndexEngine): ...
|
| 49 |
+
class Int16Engine(IndexEngine): ...
|
| 50 |
+
class Int8Engine(IndexEngine): ...
|
| 51 |
+
class UInt64Engine(IndexEngine): ...
|
| 52 |
+
class UInt32Engine(IndexEngine): ...
|
| 53 |
+
class UInt16Engine(IndexEngine): ...
|
| 54 |
+
class UInt8Engine(IndexEngine): ...
|
| 55 |
+
class ObjectEngine(IndexEngine): ...
|
| 56 |
+
class StringEngine(IndexEngine): ...
|
| 57 |
+
class DatetimeEngine(Int64Engine): ...
|
| 58 |
+
class TimedeltaEngine(DatetimeEngine): ...
|
| 59 |
+
class PeriodEngine(Int64Engine): ...
|
| 60 |
+
class BoolEngine(UInt8Engine): ...
|
| 61 |
+
class MaskedFloat64Engine(MaskedIndexEngine): ...
|
| 62 |
+
class MaskedFloat32Engine(MaskedIndexEngine): ...
|
| 63 |
+
class MaskedComplex128Engine(MaskedIndexEngine): ...
|
| 64 |
+
class MaskedComplex64Engine(MaskedIndexEngine): ...
|
| 65 |
+
class MaskedInt64Engine(MaskedIndexEngine): ...
|
| 66 |
+
class MaskedInt32Engine(MaskedIndexEngine): ...
|
| 67 |
+
class MaskedInt16Engine(MaskedIndexEngine): ...
|
| 68 |
+
class MaskedInt8Engine(MaskedIndexEngine): ...
|
| 69 |
+
class MaskedUInt64Engine(MaskedIndexEngine): ...
|
| 70 |
+
class MaskedUInt32Engine(MaskedIndexEngine): ...
|
| 71 |
+
class MaskedUInt16Engine(MaskedIndexEngine): ...
|
| 72 |
+
class MaskedUInt8Engine(MaskedIndexEngine): ...
|
| 73 |
+
class MaskedBoolEngine(MaskedUInt8Engine): ...
|
| 74 |
+
|
| 75 |
+
class StringObjectEngine(ObjectEngine):
|
| 76 |
+
def __init__(self, values: object, na_value) -> None: ...
|
| 77 |
+
|
| 78 |
+
class BaseMultiIndexCodesEngine:
|
| 79 |
+
levels: list[np.ndarray]
|
| 80 |
+
offsets: np.ndarray # np.ndarray[..., ndim=1]
|
| 81 |
+
|
| 82 |
+
def __init__(
|
| 83 |
+
self,
|
| 84 |
+
levels: list[Index], # all entries hashable
|
| 85 |
+
labels: list[np.ndarray], # all entries integer-dtyped
|
| 86 |
+
offsets: np.ndarray, # np.ndarray[..., ndim=1]
|
| 87 |
+
) -> None: ...
|
| 88 |
+
def get_indexer(self, target: npt.NDArray[np.object_]) -> npt.NDArray[np.intp]: ...
|
| 89 |
+
def _extract_level_codes(self, target: MultiIndex) -> np.ndarray: ...
|
| 90 |
+
|
| 91 |
+
class ExtensionEngine:
|
| 92 |
+
def __init__(self, values: ExtensionArray) -> None: ...
|
| 93 |
+
def __contains__(self, val: object) -> bool: ...
|
| 94 |
+
def get_loc(self, val: object) -> int | slice | np.ndarray: ...
|
| 95 |
+
def get_indexer(self, values: np.ndarray) -> npt.NDArray[np.intp]: ...
|
| 96 |
+
def get_indexer_non_unique(
|
| 97 |
+
self,
|
| 98 |
+
targets: np.ndarray,
|
| 99 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 100 |
+
@property
|
| 101 |
+
def is_unique(self) -> bool: ...
|
| 102 |
+
@property
|
| 103 |
+
def is_monotonic_increasing(self) -> bool: ...
|
| 104 |
+
@property
|
| 105 |
+
def is_monotonic_decreasing(self) -> bool: ...
|
| 106 |
+
def sizeof(self, deep: bool = ...) -> int: ...
|
| 107 |
+
def clear_mapping(self): ...
|
pandas/_libs/indexing.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (62.6 kB). View file
|
|
|
pandas/_libs/indexing.pyi
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import (
|
| 2 |
+
Generic,
|
| 3 |
+
TypeVar,
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
from pandas.core.indexing import IndexingMixin
|
| 7 |
+
|
| 8 |
+
_IndexingMixinT = TypeVar("_IndexingMixinT", bound=IndexingMixin)
|
| 9 |
+
|
| 10 |
+
class NDFrameIndexerBase(Generic[_IndexingMixinT]):
|
| 11 |
+
name: str
|
| 12 |
+
# in practice obj is either a DataFrame or a Series
|
| 13 |
+
obj: _IndexingMixinT
|
| 14 |
+
|
| 15 |
+
def __init__(self, name: str, obj: _IndexingMixinT) -> None: ...
|
| 16 |
+
@property
|
| 17 |
+
def ndim(self) -> int: ...
|
pandas/_libs/internals.pyi
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import (
|
| 2 |
+
Iterator,
|
| 3 |
+
Sequence,
|
| 4 |
+
)
|
| 5 |
+
from typing import (
|
| 6 |
+
Self,
|
| 7 |
+
final,
|
| 8 |
+
overload,
|
| 9 |
+
)
|
| 10 |
+
import weakref
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
|
| 14 |
+
from pandas._typing import (
|
| 15 |
+
ArrayLike,
|
| 16 |
+
npt,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
from pandas import Index
|
| 20 |
+
from pandas.core.internals.blocks import Block as B
|
| 21 |
+
|
| 22 |
+
def slice_len(slc: slice, objlen: int = ...) -> int: ...
|
| 23 |
+
def get_concat_blkno_indexers(
|
| 24 |
+
blknos_list: list[npt.NDArray[np.intp]],
|
| 25 |
+
) -> list[tuple[npt.NDArray[np.intp], BlockPlacement]]: ...
|
| 26 |
+
def get_blkno_indexers(
|
| 27 |
+
blknos: np.ndarray, # int64_t[:]
|
| 28 |
+
group: bool = ...,
|
| 29 |
+
) -> list[tuple[int, slice | np.ndarray]]: ...
|
| 30 |
+
def get_blkno_placements(
|
| 31 |
+
blknos: np.ndarray,
|
| 32 |
+
group: bool = ...,
|
| 33 |
+
) -> Iterator[tuple[int, BlockPlacement]]: ...
|
| 34 |
+
def update_blklocs_and_blknos(
|
| 35 |
+
blklocs: npt.NDArray[np.intp],
|
| 36 |
+
blknos: npt.NDArray[np.intp],
|
| 37 |
+
loc: int,
|
| 38 |
+
nblocks: int,
|
| 39 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 40 |
+
@final
|
| 41 |
+
class BlockPlacement:
|
| 42 |
+
def __init__(self, val: int | slice | np.ndarray) -> None: ...
|
| 43 |
+
@property
|
| 44 |
+
def indexer(self) -> np.ndarray | slice: ...
|
| 45 |
+
@property
|
| 46 |
+
def as_array(self) -> np.ndarray: ...
|
| 47 |
+
@property
|
| 48 |
+
def as_slice(self) -> slice: ...
|
| 49 |
+
@property
|
| 50 |
+
def is_slice_like(self) -> bool: ...
|
| 51 |
+
@overload
|
| 52 |
+
def __getitem__(
|
| 53 |
+
self, loc: slice | Sequence[int] | npt.NDArray[np.intp]
|
| 54 |
+
) -> BlockPlacement: ...
|
| 55 |
+
@overload
|
| 56 |
+
def __getitem__(self, loc: int) -> int: ...
|
| 57 |
+
def __iter__(self) -> Iterator[int]: ...
|
| 58 |
+
def __len__(self) -> int: ...
|
| 59 |
+
def delete(self, loc) -> BlockPlacement: ...
|
| 60 |
+
def add(self, other) -> BlockPlacement: ...
|
| 61 |
+
def append(self, others: list[BlockPlacement]) -> BlockPlacement: ...
|
| 62 |
+
def tile_for_unstack(self, factor: int) -> npt.NDArray[np.intp]: ...
|
| 63 |
+
|
| 64 |
+
class Block:
|
| 65 |
+
_mgr_locs: BlockPlacement
|
| 66 |
+
ndim: int
|
| 67 |
+
values: ArrayLike
|
| 68 |
+
refs: BlockValuesRefs
|
| 69 |
+
def __init__(
|
| 70 |
+
self,
|
| 71 |
+
values: ArrayLike,
|
| 72 |
+
placement: BlockPlacement,
|
| 73 |
+
ndim: int,
|
| 74 |
+
refs: BlockValuesRefs | None = ...,
|
| 75 |
+
) -> None: ...
|
| 76 |
+
def slice_block_rows(self, slicer: slice) -> Self: ...
|
| 77 |
+
|
| 78 |
+
class BlockManager:
|
| 79 |
+
blocks: tuple[B, ...]
|
| 80 |
+
axes: list[Index]
|
| 81 |
+
_known_consolidated: bool
|
| 82 |
+
_is_consolidated: bool
|
| 83 |
+
_blknos: np.ndarray
|
| 84 |
+
_blklocs: np.ndarray
|
| 85 |
+
def __init__(
|
| 86 |
+
self, blocks: tuple[B, ...], axes: list[Index], verify_integrity=...
|
| 87 |
+
) -> None: ...
|
| 88 |
+
def get_slice(self, slobj: slice, axis: int = ...) -> Self: ...
|
| 89 |
+
def _rebuild_blknos_and_blklocs(self) -> None: ...
|
| 90 |
+
|
| 91 |
+
class BlockValuesRefs:
|
| 92 |
+
referenced_blocks: list[weakref.ref]
|
| 93 |
+
def __init__(self, blk: Block | None = ...) -> None: ...
|
| 94 |
+
def add_reference(self, blk: Block) -> None: ...
|
| 95 |
+
def add_index_reference(self, index: Index) -> None: ...
|
| 96 |
+
def has_reference(self) -> bool: ...
|
pandas/_libs/interval.pyi
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import (
|
| 2 |
+
Any,
|
| 3 |
+
Generic,
|
| 4 |
+
TypeVar,
|
| 5 |
+
overload,
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import numpy.typing as npt
|
| 10 |
+
|
| 11 |
+
from pandas._typing import (
|
| 12 |
+
IntervalClosedType,
|
| 13 |
+
Timedelta,
|
| 14 |
+
Timestamp,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
VALID_CLOSED: frozenset[str]
|
| 18 |
+
|
| 19 |
+
_OrderableScalarT = TypeVar("_OrderableScalarT", int, float)
|
| 20 |
+
_OrderableTimesT = TypeVar("_OrderableTimesT", Timestamp, Timedelta)
|
| 21 |
+
_OrderableT = TypeVar("_OrderableT", int, float, Timestamp, Timedelta)
|
| 22 |
+
|
| 23 |
+
class _LengthDescriptor:
|
| 24 |
+
@overload
|
| 25 |
+
def __get__(
|
| 26 |
+
self, instance: Interval[_OrderableScalarT], owner: Any
|
| 27 |
+
) -> _OrderableScalarT: ...
|
| 28 |
+
@overload
|
| 29 |
+
def __get__(
|
| 30 |
+
self, instance: Interval[_OrderableTimesT], owner: Any
|
| 31 |
+
) -> Timedelta: ...
|
| 32 |
+
|
| 33 |
+
class _MidDescriptor:
|
| 34 |
+
@overload
|
| 35 |
+
def __get__(self, instance: Interval[_OrderableScalarT], owner: Any) -> float: ...
|
| 36 |
+
@overload
|
| 37 |
+
def __get__(
|
| 38 |
+
self, instance: Interval[_OrderableTimesT], owner: Any
|
| 39 |
+
) -> _OrderableTimesT: ...
|
| 40 |
+
|
| 41 |
+
class IntervalMixin:
|
| 42 |
+
@property
|
| 43 |
+
def closed_left(self) -> bool: ...
|
| 44 |
+
@property
|
| 45 |
+
def closed_right(self) -> bool: ...
|
| 46 |
+
@property
|
| 47 |
+
def open_left(self) -> bool: ...
|
| 48 |
+
@property
|
| 49 |
+
def open_right(self) -> bool: ...
|
| 50 |
+
@property
|
| 51 |
+
def is_empty(self) -> bool: ...
|
| 52 |
+
def _check_closed_matches(self, other: IntervalMixin, name: str = ...) -> None: ...
|
| 53 |
+
|
| 54 |
+
class Interval(IntervalMixin, Generic[_OrderableT]):
|
| 55 |
+
@property
|
| 56 |
+
def left(self: Interval[_OrderableT]) -> _OrderableT: ...
|
| 57 |
+
@property
|
| 58 |
+
def right(self: Interval[_OrderableT]) -> _OrderableT: ...
|
| 59 |
+
@property
|
| 60 |
+
def closed(self) -> IntervalClosedType: ...
|
| 61 |
+
mid: _MidDescriptor
|
| 62 |
+
length: _LengthDescriptor
|
| 63 |
+
def __init__(
|
| 64 |
+
self,
|
| 65 |
+
left: _OrderableT,
|
| 66 |
+
right: _OrderableT,
|
| 67 |
+
closed: IntervalClosedType = ...,
|
| 68 |
+
) -> None: ...
|
| 69 |
+
def __hash__(self) -> int: ...
|
| 70 |
+
@overload
|
| 71 |
+
def __contains__(
|
| 72 |
+
self: Interval[Timedelta], key: Timedelta | Interval[Timedelta]
|
| 73 |
+
) -> bool: ...
|
| 74 |
+
@overload
|
| 75 |
+
def __contains__(
|
| 76 |
+
self: Interval[Timestamp], key: Timestamp | Interval[Timestamp]
|
| 77 |
+
) -> bool: ...
|
| 78 |
+
@overload
|
| 79 |
+
def __contains__(
|
| 80 |
+
self: Interval[_OrderableScalarT],
|
| 81 |
+
key: _OrderableScalarT | Interval[_OrderableScalarT],
|
| 82 |
+
) -> bool: ...
|
| 83 |
+
@overload
|
| 84 |
+
def __add__(
|
| 85 |
+
self: Interval[_OrderableTimesT], y: Timedelta
|
| 86 |
+
) -> Interval[_OrderableTimesT]: ...
|
| 87 |
+
@overload
|
| 88 |
+
def __add__(
|
| 89 |
+
self: Interval[int], y: _OrderableScalarT
|
| 90 |
+
) -> Interval[_OrderableScalarT]: ...
|
| 91 |
+
@overload
|
| 92 |
+
def __add__(self: Interval[float], y: float) -> Interval[float]: ...
|
| 93 |
+
@overload
|
| 94 |
+
def __radd__(
|
| 95 |
+
self: Interval[_OrderableTimesT], y: Timedelta
|
| 96 |
+
) -> Interval[_OrderableTimesT]: ...
|
| 97 |
+
@overload
|
| 98 |
+
def __radd__(
|
| 99 |
+
self: Interval[int], y: _OrderableScalarT
|
| 100 |
+
) -> Interval[_OrderableScalarT]: ...
|
| 101 |
+
@overload
|
| 102 |
+
def __radd__(self: Interval[float], y: float) -> Interval[float]: ...
|
| 103 |
+
@overload
|
| 104 |
+
def __sub__(
|
| 105 |
+
self: Interval[_OrderableTimesT], y: Timedelta
|
| 106 |
+
) -> Interval[_OrderableTimesT]: ...
|
| 107 |
+
@overload
|
| 108 |
+
def __sub__(
|
| 109 |
+
self: Interval[int], y: _OrderableScalarT
|
| 110 |
+
) -> Interval[_OrderableScalarT]: ...
|
| 111 |
+
@overload
|
| 112 |
+
def __sub__(self: Interval[float], y: float) -> Interval[float]: ...
|
| 113 |
+
@overload
|
| 114 |
+
def __rsub__(
|
| 115 |
+
self: Interval[_OrderableTimesT], y: Timedelta
|
| 116 |
+
) -> Interval[_OrderableTimesT]: ...
|
| 117 |
+
@overload
|
| 118 |
+
def __rsub__(
|
| 119 |
+
self: Interval[int], y: _OrderableScalarT
|
| 120 |
+
) -> Interval[_OrderableScalarT]: ...
|
| 121 |
+
@overload
|
| 122 |
+
def __rsub__(self: Interval[float], y: float) -> Interval[float]: ...
|
| 123 |
+
@overload
|
| 124 |
+
def __mul__(
|
| 125 |
+
self: Interval[int], y: _OrderableScalarT
|
| 126 |
+
) -> Interval[_OrderableScalarT]: ...
|
| 127 |
+
@overload
|
| 128 |
+
def __mul__(self: Interval[float], y: float) -> Interval[float]: ...
|
| 129 |
+
@overload
|
| 130 |
+
def __rmul__(
|
| 131 |
+
self: Interval[int], y: _OrderableScalarT
|
| 132 |
+
) -> Interval[_OrderableScalarT]: ...
|
| 133 |
+
@overload
|
| 134 |
+
def __rmul__(self: Interval[float], y: float) -> Interval[float]: ...
|
| 135 |
+
@overload
|
| 136 |
+
def __truediv__(
|
| 137 |
+
self: Interval[int], y: _OrderableScalarT
|
| 138 |
+
) -> Interval[_OrderableScalarT]: ...
|
| 139 |
+
@overload
|
| 140 |
+
def __truediv__(self: Interval[float], y: float) -> Interval[float]: ...
|
| 141 |
+
@overload
|
| 142 |
+
def __floordiv__(
|
| 143 |
+
self: Interval[int], y: _OrderableScalarT
|
| 144 |
+
) -> Interval[_OrderableScalarT]: ...
|
| 145 |
+
@overload
|
| 146 |
+
def __floordiv__(self: Interval[float], y: float) -> Interval[float]: ...
|
| 147 |
+
def overlaps(self: Interval[_OrderableT], other: Interval[_OrderableT]) -> bool: ...
|
| 148 |
+
|
| 149 |
+
def intervals_to_interval_bounds(
|
| 150 |
+
intervals: np.ndarray, validate_closed: bool = ...
|
| 151 |
+
) -> tuple[np.ndarray, np.ndarray, IntervalClosedType]: ...
|
| 152 |
+
|
| 153 |
+
class IntervalTree(IntervalMixin):
|
| 154 |
+
def __init__(
|
| 155 |
+
self,
|
| 156 |
+
left: np.ndarray,
|
| 157 |
+
right: np.ndarray,
|
| 158 |
+
closed: IntervalClosedType = ...,
|
| 159 |
+
leaf_size: int = ...,
|
| 160 |
+
) -> None: ...
|
| 161 |
+
@property
|
| 162 |
+
def mid(self) -> np.ndarray: ...
|
| 163 |
+
@property
|
| 164 |
+
def length(self) -> np.ndarray: ...
|
| 165 |
+
def get_indexer(self, target) -> npt.NDArray[np.intp]: ...
|
| 166 |
+
def get_indexer_non_unique(
|
| 167 |
+
self, target
|
| 168 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 169 |
+
_na_count: int
|
| 170 |
+
@property
|
| 171 |
+
def is_overlapping(self) -> bool: ...
|
| 172 |
+
@property
|
| 173 |
+
def is_monotonic_increasing(self) -> bool: ...
|
| 174 |
+
def clear_mapping(self) -> None: ...
|
pandas/_libs/join.pyi
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
from pandas._typing import npt
|
| 4 |
+
|
| 5 |
+
def inner_join(
|
| 6 |
+
left: np.ndarray, # const intp_t[:]
|
| 7 |
+
right: np.ndarray, # const intp_t[:]
|
| 8 |
+
max_groups: int,
|
| 9 |
+
sort: bool = ...,
|
| 10 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 11 |
+
def left_outer_join(
|
| 12 |
+
left: np.ndarray, # const intp_t[:]
|
| 13 |
+
right: np.ndarray, # const intp_t[:]
|
| 14 |
+
max_groups: int,
|
| 15 |
+
sort: bool = ...,
|
| 16 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 17 |
+
def full_outer_join(
|
| 18 |
+
left: np.ndarray, # const intp_t[:]
|
| 19 |
+
right: np.ndarray, # const intp_t[:]
|
| 20 |
+
max_groups: int,
|
| 21 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 22 |
+
def ffill_indexer(
|
| 23 |
+
indexer: np.ndarray, # const intp_t[:]
|
| 24 |
+
) -> npt.NDArray[np.intp]: ...
|
| 25 |
+
def left_join_indexer_unique(
|
| 26 |
+
left: np.ndarray, # ndarray[join_t]
|
| 27 |
+
right: np.ndarray, # ndarray[join_t]
|
| 28 |
+
) -> npt.NDArray[np.intp]: ...
|
| 29 |
+
def left_join_indexer(
|
| 30 |
+
left: np.ndarray, # ndarray[join_t]
|
| 31 |
+
right: np.ndarray, # ndarray[join_t]
|
| 32 |
+
) -> tuple[
|
| 33 |
+
np.ndarray, # np.ndarray[join_t]
|
| 34 |
+
npt.NDArray[np.intp],
|
| 35 |
+
npt.NDArray[np.intp],
|
| 36 |
+
]: ...
|
| 37 |
+
def inner_join_indexer(
|
| 38 |
+
left: np.ndarray, # ndarray[join_t]
|
| 39 |
+
right: np.ndarray, # ndarray[join_t]
|
| 40 |
+
) -> tuple[
|
| 41 |
+
np.ndarray, # np.ndarray[join_t]
|
| 42 |
+
npt.NDArray[np.intp],
|
| 43 |
+
npt.NDArray[np.intp],
|
| 44 |
+
]: ...
|
| 45 |
+
def outer_join_indexer(
|
| 46 |
+
left: np.ndarray, # ndarray[join_t]
|
| 47 |
+
right: np.ndarray, # ndarray[join_t]
|
| 48 |
+
) -> tuple[
|
| 49 |
+
np.ndarray, # np.ndarray[join_t]
|
| 50 |
+
npt.NDArray[np.intp],
|
| 51 |
+
npt.NDArray[np.intp],
|
| 52 |
+
]: ...
|
| 53 |
+
def asof_join_backward_on_X_by_Y(
|
| 54 |
+
left_values: np.ndarray, # ndarray[numeric_t]
|
| 55 |
+
right_values: np.ndarray, # ndarray[numeric_t]
|
| 56 |
+
left_by_values: np.ndarray, # const int64_t[:]
|
| 57 |
+
right_by_values: np.ndarray, # const int64_t[:]
|
| 58 |
+
allow_exact_matches: bool = ...,
|
| 59 |
+
tolerance: np.number | float | None = ...,
|
| 60 |
+
use_hashtable: bool = ...,
|
| 61 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 62 |
+
def asof_join_forward_on_X_by_Y(
|
| 63 |
+
left_values: np.ndarray, # ndarray[numeric_t]
|
| 64 |
+
right_values: np.ndarray, # ndarray[numeric_t]
|
| 65 |
+
left_by_values: np.ndarray, # const int64_t[:]
|
| 66 |
+
right_by_values: np.ndarray, # const int64_t[:]
|
| 67 |
+
allow_exact_matches: bool = ...,
|
| 68 |
+
tolerance: np.number | float | None = ...,
|
| 69 |
+
use_hashtable: bool = ...,
|
| 70 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
| 71 |
+
def asof_join_nearest_on_X_by_Y(
|
| 72 |
+
left_values: np.ndarray, # ndarray[numeric_t]
|
| 73 |
+
right_values: np.ndarray, # ndarray[numeric_t]
|
| 74 |
+
left_by_values: np.ndarray, # const int64_t[:]
|
| 75 |
+
right_by_values: np.ndarray, # const int64_t[:]
|
| 76 |
+
allow_exact_matches: bool = ...,
|
| 77 |
+
tolerance: np.number | float | None = ...,
|
| 78 |
+
use_hashtable: bool = ...,
|
| 79 |
+
) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]: ...
|
pandas/_libs/json.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (68.7 kB). View file
|
|
|
pandas/_libs/json.pyi
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Callable
|
| 2 |
+
from typing import (
|
| 3 |
+
Any,
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
def ujson_dumps(
|
| 7 |
+
obj: Any,
|
| 8 |
+
ensure_ascii: bool = ...,
|
| 9 |
+
double_precision: int = ...,
|
| 10 |
+
indent: int = ...,
|
| 11 |
+
orient: str = ...,
|
| 12 |
+
date_unit: str = ...,
|
| 13 |
+
iso_dates: bool = ...,
|
| 14 |
+
default_handler: None
|
| 15 |
+
| Callable[[Any], str | float | bool | list | dict | None] = ...,
|
| 16 |
+
) -> str: ...
|
| 17 |
+
def ujson_loads(
|
| 18 |
+
s: str,
|
| 19 |
+
precise_float: bool = ...,
|
| 20 |
+
numpy: bool = ...,
|
| 21 |
+
dtype: None = ...,
|
| 22 |
+
labelled: bool = ...,
|
| 23 |
+
) -> Any: ...
|
pandas/_libs/lib.pyi
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TODO(npdtypes): Many types specified here can be made more specific/accurate;
|
| 2 |
+
# the more specific versions are specified in comments
|
| 3 |
+
from collections.abc import (
|
| 4 |
+
Callable,
|
| 5 |
+
Generator,
|
| 6 |
+
Hashable,
|
| 7 |
+
)
|
| 8 |
+
from decimal import Decimal
|
| 9 |
+
from typing import (
|
| 10 |
+
Any,
|
| 11 |
+
Final,
|
| 12 |
+
Literal,
|
| 13 |
+
TypeAlias,
|
| 14 |
+
TypeGuard,
|
| 15 |
+
overload,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
import numpy as np
|
| 19 |
+
|
| 20 |
+
from pandas._typing import (
|
| 21 |
+
ArrayLike,
|
| 22 |
+
DtypeObj,
|
| 23 |
+
npt,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# placeholder until we can specify np.ndarray[object, ndim=2]
|
| 27 |
+
ndarray_obj_2d = np.ndarray
|
| 28 |
+
|
| 29 |
+
from enum import Enum
|
| 30 |
+
|
| 31 |
+
class _NoDefault(Enum):
|
| 32 |
+
no_default = ...
|
| 33 |
+
|
| 34 |
+
no_default: Final = _NoDefault.no_default
|
| 35 |
+
NoDefault: TypeAlias = Literal[_NoDefault.no_default]
|
| 36 |
+
|
| 37 |
+
i8max: int
|
| 38 |
+
u8max: int
|
| 39 |
+
|
| 40 |
+
def is_np_dtype(dtype: object, kinds: str | None = ...) -> TypeGuard[np.dtype]: ...
|
| 41 |
+
def item_from_zerodim(val: object) -> object: ...
|
| 42 |
+
def infer_dtype(value: object, skipna: bool = ...) -> str: ...
|
| 43 |
+
def is_iterator(obj: object) -> bool: ...
|
| 44 |
+
def is_scalar(val: object) -> bool: ...
|
| 45 |
+
def is_list_like(obj: object, allow_sets: bool = ...) -> bool: ...
|
| 46 |
+
def is_pyarrow_array(obj: object) -> bool: ...
|
| 47 |
+
def is_decimal(obj: object) -> TypeGuard[Decimal]: ...
|
| 48 |
+
def is_complex(obj: object) -> TypeGuard[complex]: ...
|
| 49 |
+
def is_bool(obj: object) -> TypeGuard[bool | np.bool_]: ...
|
| 50 |
+
def is_integer(obj: object) -> TypeGuard[int | np.integer]: ...
|
| 51 |
+
def is_int_or_none(obj) -> bool: ...
|
| 52 |
+
def is_float(obj: object) -> TypeGuard[float]: ...
|
| 53 |
+
def is_interval_array(values: np.ndarray) -> bool: ...
|
| 54 |
+
def is_datetime64_array(values: np.ndarray, skipna: bool = True) -> bool: ...
|
| 55 |
+
def is_timedelta_or_timedelta64_array(
|
| 56 |
+
values: np.ndarray, skipna: bool = True
|
| 57 |
+
) -> bool: ...
|
| 58 |
+
def is_datetime_with_singletz_array(values: np.ndarray) -> bool: ...
|
| 59 |
+
def is_time_array(values: np.ndarray, skipna: bool = ...): ...
|
| 60 |
+
def is_date_array(values: np.ndarray, skipna: bool = ...): ...
|
| 61 |
+
def is_datetime_array(values: np.ndarray, skipna: bool = ...): ...
|
| 62 |
+
def is_string_array(values: np.ndarray, skipna: bool = ...): ...
|
| 63 |
+
def is_float_array(values: np.ndarray, skipna: bool = ...): ...
|
| 64 |
+
def is_integer_array(values: np.ndarray, skipna: bool = ...): ...
|
| 65 |
+
def is_bool_array(values: np.ndarray, skipna: bool = ...): ...
|
| 66 |
+
def fast_multiget(
|
| 67 |
+
mapping: dict,
|
| 68 |
+
keys: np.ndarray, # object[:]
|
| 69 |
+
default=...,
|
| 70 |
+
) -> ArrayLike: ...
|
| 71 |
+
def fast_unique_multiple_list_gen(gen: Generator, sort: bool = ...) -> list: ...
|
| 72 |
+
@overload
|
| 73 |
+
def map_infer(
|
| 74 |
+
arr: np.ndarray,
|
| 75 |
+
f: Callable[[Any], Any],
|
| 76 |
+
*,
|
| 77 |
+
convert: Literal[False],
|
| 78 |
+
ignore_na: bool = ...,
|
| 79 |
+
) -> np.ndarray: ...
|
| 80 |
+
@overload
|
| 81 |
+
def map_infer(
|
| 82 |
+
arr: np.ndarray,
|
| 83 |
+
f: Callable[[Any], Any],
|
| 84 |
+
*,
|
| 85 |
+
convert: bool = ...,
|
| 86 |
+
ignore_na: bool = ...,
|
| 87 |
+
) -> ArrayLike: ...
|
| 88 |
+
@overload
|
| 89 |
+
def maybe_convert_objects(
|
| 90 |
+
objects: npt.NDArray[np.object_],
|
| 91 |
+
*,
|
| 92 |
+
try_float: bool = ...,
|
| 93 |
+
safe: bool = ...,
|
| 94 |
+
convert_numeric: bool = ...,
|
| 95 |
+
convert_non_numeric: Literal[False] = ...,
|
| 96 |
+
convert_to_nullable_dtype: Literal[False] = ...,
|
| 97 |
+
dtype_if_all_nat: DtypeObj | None = ...,
|
| 98 |
+
) -> npt.NDArray[np.object_ | np.number]: ...
|
| 99 |
+
@overload
|
| 100 |
+
def maybe_convert_objects(
|
| 101 |
+
objects: npt.NDArray[np.object_],
|
| 102 |
+
*,
|
| 103 |
+
try_float: bool = ...,
|
| 104 |
+
safe: bool = ...,
|
| 105 |
+
convert_numeric: bool = ...,
|
| 106 |
+
convert_non_numeric: bool = ...,
|
| 107 |
+
convert_to_nullable_dtype: Literal[True] = ...,
|
| 108 |
+
dtype_if_all_nat: DtypeObj | None = ...,
|
| 109 |
+
) -> ArrayLike: ...
|
| 110 |
+
@overload
|
| 111 |
+
def maybe_convert_objects(
|
| 112 |
+
objects: npt.NDArray[np.object_],
|
| 113 |
+
*,
|
| 114 |
+
try_float: bool = ...,
|
| 115 |
+
safe: bool = ...,
|
| 116 |
+
convert_numeric: bool = ...,
|
| 117 |
+
convert_non_numeric: bool = ...,
|
| 118 |
+
convert_to_nullable_dtype: bool = ...,
|
| 119 |
+
dtype_if_all_nat: DtypeObj | None = ...,
|
| 120 |
+
) -> ArrayLike: ...
|
| 121 |
+
@overload
|
| 122 |
+
def maybe_convert_numeric(
|
| 123 |
+
values: npt.NDArray[np.object_],
|
| 124 |
+
na_values: set,
|
| 125 |
+
convert_empty: bool = ...,
|
| 126 |
+
coerce_numeric: bool = ...,
|
| 127 |
+
convert_to_masked_nullable: Literal[False] = ...,
|
| 128 |
+
) -> tuple[np.ndarray, None]: ...
|
| 129 |
+
@overload
|
| 130 |
+
def maybe_convert_numeric(
|
| 131 |
+
values: npt.NDArray[np.object_],
|
| 132 |
+
na_values: set,
|
| 133 |
+
convert_empty: bool = ...,
|
| 134 |
+
coerce_numeric: bool = ...,
|
| 135 |
+
*,
|
| 136 |
+
convert_to_masked_nullable: Literal[True],
|
| 137 |
+
) -> tuple[np.ndarray, np.ndarray]: ...
|
| 138 |
+
|
| 139 |
+
# TODO: restrict `arr`?
|
| 140 |
+
def ensure_string_array(
|
| 141 |
+
arr,
|
| 142 |
+
na_value: object = ...,
|
| 143 |
+
convert_na_value: bool = ...,
|
| 144 |
+
copy: bool = ...,
|
| 145 |
+
skipna: bool = ...,
|
| 146 |
+
) -> npt.NDArray[np.object_]: ...
|
| 147 |
+
def convert_nans_to_NA(
|
| 148 |
+
arr: npt.NDArray[np.object_],
|
| 149 |
+
) -> npt.NDArray[np.object_]: ...
|
| 150 |
+
def fast_zip(ndarrays: list) -> npt.NDArray[np.object_]: ...
|
| 151 |
+
|
| 152 |
+
# TODO: can we be more specific about rows?
|
| 153 |
+
def to_object_array_tuples(rows: object) -> ndarray_obj_2d: ...
|
| 154 |
+
def tuples_to_object_array(
|
| 155 |
+
tuples: npt.NDArray[np.object_],
|
| 156 |
+
) -> ndarray_obj_2d: ...
|
| 157 |
+
|
| 158 |
+
# TODO: can we be more specific about rows?
|
| 159 |
+
def to_object_array(rows: object, min_width: int = ...) -> ndarray_obj_2d: ...
|
| 160 |
+
def dicts_to_array(dicts: list, columns: list) -> ndarray_obj_2d: ...
|
| 161 |
+
def maybe_booleans_to_slice(
|
| 162 |
+
mask: npt.NDArray[np.uint8],
|
| 163 |
+
) -> slice | npt.NDArray[np.uint8]: ...
|
| 164 |
+
def maybe_indices_to_slice(
|
| 165 |
+
indices: npt.NDArray[np.intp],
|
| 166 |
+
max_len: int,
|
| 167 |
+
) -> slice | npt.NDArray[np.intp]: ...
|
| 168 |
+
def is_all_arraylike(obj: list) -> bool: ...
|
| 169 |
+
|
| 170 |
+
# -----------------------------------------------------------------
|
| 171 |
+
# Functions which in reality take memoryviews
|
| 172 |
+
|
| 173 |
+
def memory_usage_of_objects(arr: np.ndarray) -> int: ... # object[:] # np.int64
|
| 174 |
+
@overload
|
| 175 |
+
def map_infer_mask(
|
| 176 |
+
arr: np.ndarray,
|
| 177 |
+
f: Callable[[Any], Any],
|
| 178 |
+
mask: np.ndarray, # const uint8_t[:]
|
| 179 |
+
*,
|
| 180 |
+
convert: Literal[False],
|
| 181 |
+
na_value: Any = ...,
|
| 182 |
+
dtype: np.dtype = ...,
|
| 183 |
+
) -> np.ndarray: ...
|
| 184 |
+
@overload
|
| 185 |
+
def map_infer_mask(
|
| 186 |
+
arr: np.ndarray,
|
| 187 |
+
f: Callable[[Any], Any],
|
| 188 |
+
mask: np.ndarray, # const uint8_t[:]
|
| 189 |
+
*,
|
| 190 |
+
convert: bool = ...,
|
| 191 |
+
na_value: Any = ...,
|
| 192 |
+
dtype: np.dtype = ...,
|
| 193 |
+
) -> ArrayLike: ...
|
| 194 |
+
def indices_fast(
|
| 195 |
+
index: npt.NDArray[np.intp],
|
| 196 |
+
labels: np.ndarray, # const int64_t[:]
|
| 197 |
+
keys: list,
|
| 198 |
+
sorted_labels: list[npt.NDArray[np.int64]],
|
| 199 |
+
) -> dict[Hashable, npt.NDArray[np.intp]]: ...
|
| 200 |
+
def generate_slices(
|
| 201 |
+
labels: np.ndarray,
|
| 202 |
+
ngroups: int, # const intp_t[:]
|
| 203 |
+
) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.int64]]: ...
|
| 204 |
+
def count_level_2d(
|
| 205 |
+
mask: np.ndarray, # ndarray[uint8_t, ndim=2, cast=True],
|
| 206 |
+
labels: np.ndarray, # const intp_t[:]
|
| 207 |
+
max_bin: int,
|
| 208 |
+
) -> np.ndarray: ... # np.ndarray[np.int64, ndim=2]
|
| 209 |
+
def get_level_sorter(
|
| 210 |
+
codes: np.ndarray, # const int64_t[:]
|
| 211 |
+
starts: np.ndarray, # const intp_t[:]
|
| 212 |
+
) -> np.ndarray: ... # np.ndarray[np.intp, ndim=1]
|
| 213 |
+
def generate_bins_dt64(
|
| 214 |
+
values: npt.NDArray[np.int64],
|
| 215 |
+
binner: np.ndarray, # const int64_t[:]
|
| 216 |
+
closed: object = ...,
|
| 217 |
+
hasnans: bool = ...,
|
| 218 |
+
) -> np.ndarray: ... # np.ndarray[np.int64, ndim=1]
|
| 219 |
+
def array_equivalent_object(
|
| 220 |
+
left: npt.NDArray[np.object_],
|
| 221 |
+
right: npt.NDArray[np.object_],
|
| 222 |
+
) -> bool: ...
|
| 223 |
+
def has_infs(arr: np.ndarray) -> bool: ... # const floating[:]
|
| 224 |
+
def has_only_ints_or_nan(arr: np.ndarray) -> bool: ... # const floating[:]
|
| 225 |
+
def get_reverse_indexer(
|
| 226 |
+
indexer: np.ndarray, # const intp_t[:]
|
| 227 |
+
length: int,
|
| 228 |
+
) -> npt.NDArray[np.intp]: ...
|
| 229 |
+
def is_bool_list(obj: list) -> bool: ...
|
| 230 |
+
def dtypes_all_equal(types: list[DtypeObj]) -> bool: ...
|
| 231 |
+
def is_range_indexer(
|
| 232 |
+
left: np.ndarray,
|
| 233 |
+
n: int, # np.ndarray[np.int64, ndim=1]
|
| 234 |
+
) -> bool: ...
|
| 235 |
+
def is_sequence_range(
|
| 236 |
+
sequence: np.ndarray,
|
| 237 |
+
step: int, # np.ndarray[np.int64, ndim=1]
|
| 238 |
+
) -> bool: ...
|
pandas/_libs/missing.pyi
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
from numpy import typing as npt
|
| 3 |
+
|
| 4 |
+
class NAType:
|
| 5 |
+
def __new__(cls, *args, **kwargs): ...
|
| 6 |
+
|
| 7 |
+
NA: NAType
|
| 8 |
+
|
| 9 |
+
def is_matching_na(
|
| 10 |
+
left: object, right: object, nan_matches_none: bool = ...
|
| 11 |
+
) -> bool: ...
|
| 12 |
+
def isposinf_scalar(val: object) -> bool: ...
|
| 13 |
+
def isneginf_scalar(val: object) -> bool: ...
|
| 14 |
+
def checknull(val: object) -> bool: ...
|
| 15 |
+
def isnaobj(arr: np.ndarray) -> npt.NDArray[np.bool_]: ...
|
| 16 |
+
def is_numeric_na(values: np.ndarray) -> npt.NDArray[np.bool_]: ...
|
| 17 |
+
def is_pdna_or_none(values: np.ndarray) -> npt.NDArray[np.bool_]: ...
|
pandas/_libs/ops.pyi
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import (
|
| 2 |
+
Callable,
|
| 3 |
+
Iterable,
|
| 4 |
+
)
|
| 5 |
+
from typing import (
|
| 6 |
+
Any,
|
| 7 |
+
Literal,
|
| 8 |
+
TypeAlias,
|
| 9 |
+
overload,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
|
| 14 |
+
from pandas._typing import npt
|
| 15 |
+
|
| 16 |
+
_BinOp: TypeAlias = Callable[[Any, Any], Any]
|
| 17 |
+
_BoolOp: TypeAlias = Callable[[Any, Any], bool]
|
| 18 |
+
|
| 19 |
+
def scalar_compare(
|
| 20 |
+
values: np.ndarray, # object[:]
|
| 21 |
+
val: object,
|
| 22 |
+
op: _BoolOp, # {operator.eq, operator.ne, ...}
|
| 23 |
+
) -> npt.NDArray[np.bool_]: ...
|
| 24 |
+
def vec_compare(
|
| 25 |
+
left: npt.NDArray[np.object_],
|
| 26 |
+
right: npt.NDArray[np.object_],
|
| 27 |
+
op: _BoolOp, # {operator.eq, operator.ne, ...}
|
| 28 |
+
) -> npt.NDArray[np.bool_]: ...
|
| 29 |
+
def scalar_binop(
|
| 30 |
+
values: np.ndarray, # object[:]
|
| 31 |
+
val: object,
|
| 32 |
+
op: _BinOp, # binary operator
|
| 33 |
+
) -> np.ndarray: ...
|
| 34 |
+
def vec_binop(
|
| 35 |
+
left: np.ndarray, # object[:]
|
| 36 |
+
right: np.ndarray, # object[:]
|
| 37 |
+
op: _BinOp, # binary operator
|
| 38 |
+
) -> np.ndarray: ...
|
| 39 |
+
@overload
|
| 40 |
+
def maybe_convert_bool(
|
| 41 |
+
arr: npt.NDArray[np.object_],
|
| 42 |
+
true_values: Iterable | None = None,
|
| 43 |
+
false_values: Iterable | None = None,
|
| 44 |
+
convert_to_masked_nullable: Literal[False] = ...,
|
| 45 |
+
) -> tuple[np.ndarray, None]: ...
|
| 46 |
+
@overload
|
| 47 |
+
def maybe_convert_bool(
|
| 48 |
+
arr: npt.NDArray[np.object_],
|
| 49 |
+
true_values: Iterable = ...,
|
| 50 |
+
false_values: Iterable = ...,
|
| 51 |
+
*,
|
| 52 |
+
convert_to_masked_nullable: Literal[True],
|
| 53 |
+
) -> tuple[np.ndarray, np.ndarray]: ...
|
pandas/_libs/ops_dispatch.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (57.9 kB). View file
|
|
|
pandas/_libs/ops_dispatch.pyi
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
def maybe_dispatch_ufunc_to_dunder_op(
|
| 4 |
+
self, ufunc: np.ufunc, method: str, *inputs, **kwargs
|
| 5 |
+
): ...
|
pandas/_libs/pandas_datetime.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (39.6 kB). View file
|
|
|
pandas/_libs/pandas_parser.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (43.8 kB). View file
|
|
|
pandas/_libs/parsers.pyi
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Hashable
|
| 2 |
+
from typing import (
|
| 3 |
+
Literal,
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
|
| 8 |
+
from pandas._typing import (
|
| 9 |
+
ArrayLike,
|
| 10 |
+
Dtype,
|
| 11 |
+
npt,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
STR_NA_VALUES: set[str]
|
| 15 |
+
DEFAULT_BUFFER_HEURISTIC: int
|
| 16 |
+
|
| 17 |
+
def sanitize_objects(
|
| 18 |
+
values: npt.NDArray[np.object_],
|
| 19 |
+
na_values: set,
|
| 20 |
+
) -> int: ...
|
| 21 |
+
|
| 22 |
+
class TextReader:
|
| 23 |
+
unnamed_cols: set[str]
|
| 24 |
+
table_width: int # int64_t
|
| 25 |
+
leading_cols: int # int64_t
|
| 26 |
+
header: list[list[int]] # non-negative integers
|
| 27 |
+
def __init__(
|
| 28 |
+
self,
|
| 29 |
+
source,
|
| 30 |
+
delimiter: bytes | str = ..., # single-character only
|
| 31 |
+
header=...,
|
| 32 |
+
header_start: int = ..., # int64_t
|
| 33 |
+
header_end: int = ..., # uint64_t
|
| 34 |
+
index_col=...,
|
| 35 |
+
names=...,
|
| 36 |
+
tokenize_chunksize: int = ..., # int64_t
|
| 37 |
+
delim_whitespace: bool = ...,
|
| 38 |
+
converters=...,
|
| 39 |
+
skipinitialspace: bool = ...,
|
| 40 |
+
escapechar: bytes | str | None = ..., # single-character only
|
| 41 |
+
doublequote: bool = ...,
|
| 42 |
+
quotechar: str | bytes | None = ..., # at most 1 character
|
| 43 |
+
quoting: int = ...,
|
| 44 |
+
lineterminator: bytes | str | None = ..., # at most 1 character
|
| 45 |
+
comment=...,
|
| 46 |
+
decimal: bytes | str = ..., # single-character only
|
| 47 |
+
thousands: bytes | str | None = ..., # single-character only
|
| 48 |
+
dtype: Dtype | dict[Hashable, Dtype] = ...,
|
| 49 |
+
usecols=...,
|
| 50 |
+
error_bad_lines: bool = ...,
|
| 51 |
+
warn_bad_lines: bool = ...,
|
| 52 |
+
na_filter: bool = ...,
|
| 53 |
+
na_values=...,
|
| 54 |
+
na_fvalues=...,
|
| 55 |
+
keep_default_na: bool = ...,
|
| 56 |
+
true_values=...,
|
| 57 |
+
false_values=...,
|
| 58 |
+
allow_leading_cols: bool = ...,
|
| 59 |
+
skiprows=...,
|
| 60 |
+
skipfooter: int = ..., # int64_t
|
| 61 |
+
verbose: bool = ...,
|
| 62 |
+
float_precision: Literal["round_trip", "legacy", "high"] | None = ...,
|
| 63 |
+
skip_blank_lines: bool = ...,
|
| 64 |
+
encoding_errors: bytes | str = ...,
|
| 65 |
+
) -> None: ...
|
| 66 |
+
def set_noconvert(self, i: int) -> None: ...
|
| 67 |
+
def remove_noconvert(self, i: int) -> None: ...
|
| 68 |
+
def close(self) -> None: ...
|
| 69 |
+
def read(self, rows: int | None = ...) -> dict[int, ArrayLike]: ...
|
| 70 |
+
def read_low_memory(self, rows: int | None) -> list[dict[int, ArrayLike]]: ...
|
| 71 |
+
|
| 72 |
+
# _maybe_upcast, na_values are only exposed for testing
|
| 73 |
+
na_values: dict
|
| 74 |
+
|
| 75 |
+
def _maybe_upcast(
|
| 76 |
+
arr, use_dtype_backend: bool = ..., dtype_backend: str = ...
|
| 77 |
+
) -> np.ndarray: ...
|
pandas/_libs/properties.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (79.5 kB). View file
|
|
|
pandas/_libs/properties.pyi
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Sequence
|
| 2 |
+
from typing import (
|
| 3 |
+
overload,
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
from pandas._typing import (
|
| 7 |
+
AnyArrayLike,
|
| 8 |
+
DataFrame,
|
| 9 |
+
Index,
|
| 10 |
+
Series,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
# note: this is a lie to make type checkers happy (they special
|
| 14 |
+
# case property). cache_readonly uses attribute names similar to
|
| 15 |
+
# property (fget) but it does not provide fset and fdel.
|
| 16 |
+
cache_readonly = property
|
| 17 |
+
|
| 18 |
+
class AxisProperty:
|
| 19 |
+
axis: int
|
| 20 |
+
def __init__(self, axis: int = ..., doc: str = ...) -> None: ...
|
| 21 |
+
@overload
|
| 22 |
+
def __get__(self, obj: DataFrame | Series, type) -> Index: ...
|
| 23 |
+
@overload
|
| 24 |
+
def __get__(self, obj: None, type) -> AxisProperty: ...
|
| 25 |
+
def __set__(
|
| 26 |
+
self, obj: DataFrame | Series, value: AnyArrayLike | Sequence
|
| 27 |
+
) -> None: ...
|
pandas/_libs/reshape.pyi
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
from pandas._typing import npt
|
| 4 |
+
|
| 5 |
+
def unstack(
|
| 6 |
+
values: np.ndarray, # reshape_t[:, :]
|
| 7 |
+
mask: np.ndarray, # const uint8_t[:]
|
| 8 |
+
stride: int,
|
| 9 |
+
length: int,
|
| 10 |
+
width: int,
|
| 11 |
+
new_values: np.ndarray, # reshape_t[:, :]
|
| 12 |
+
new_mask: np.ndarray, # uint8_t[:, :]
|
| 13 |
+
) -> None: ...
|
| 14 |
+
def explode(
|
| 15 |
+
values: npt.NDArray[np.object_],
|
| 16 |
+
) -> tuple[npt.NDArray[np.object_], npt.NDArray[np.int64]]: ...
|
pandas/_libs/sas.pyi
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pandas.io.sas.sas7bdat import SAS7BDATReader
|
| 2 |
+
|
| 3 |
+
class Parser:
|
| 4 |
+
def __init__(self, parser: SAS7BDATReader) -> None: ...
|
| 5 |
+
def read(self, nrows: int) -> None: ...
|
| 6 |
+
|
| 7 |
+
def get_subheader_index(signature: bytes) -> int: ...
|
pandas/_libs/sparse.pyi
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Self
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from pandas._typing import (
|
| 6 |
+
TakeIndexer,
|
| 7 |
+
npt,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
class SparseIndex:
|
| 11 |
+
length: int
|
| 12 |
+
npoints: int
|
| 13 |
+
def __init__(self) -> None: ...
|
| 14 |
+
@property
|
| 15 |
+
def ngaps(self) -> int: ...
|
| 16 |
+
@property
|
| 17 |
+
def nbytes(self) -> int: ...
|
| 18 |
+
@property
|
| 19 |
+
def indices(self) -> npt.NDArray[np.int32]: ...
|
| 20 |
+
def equals(self, other) -> bool: ...
|
| 21 |
+
def lookup(self, index: int) -> np.int32: ...
|
| 22 |
+
def lookup_array(self, indexer: npt.NDArray[np.int32]) -> npt.NDArray[np.int32]: ...
|
| 23 |
+
def to_int_index(self) -> IntIndex: ...
|
| 24 |
+
def to_block_index(self) -> BlockIndex: ...
|
| 25 |
+
def intersect(self, y_: SparseIndex) -> Self: ...
|
| 26 |
+
def make_union(self, y_: SparseIndex) -> Self: ...
|
| 27 |
+
|
| 28 |
+
class IntIndex(SparseIndex):
|
| 29 |
+
indices: npt.NDArray[np.int32]
|
| 30 |
+
def __init__(
|
| 31 |
+
self, length: int, indices: TakeIndexer, check_integrity: bool = ...
|
| 32 |
+
) -> None: ...
|
| 33 |
+
|
| 34 |
+
class BlockIndex(SparseIndex):
|
| 35 |
+
nblocks: int
|
| 36 |
+
blocs: np.ndarray
|
| 37 |
+
blengths: np.ndarray
|
| 38 |
+
def __init__(
|
| 39 |
+
self, length: int, blocs: np.ndarray, blengths: np.ndarray
|
| 40 |
+
) -> None: ...
|
| 41 |
+
|
| 42 |
+
# Override to have correct parameters
|
| 43 |
+
def intersect(self, other: SparseIndex) -> Self: ...
|
| 44 |
+
def make_union(self, y: SparseIndex) -> Self: ...
|
| 45 |
+
|
| 46 |
+
def make_mask_object_ndarray(
|
| 47 |
+
arr: npt.NDArray[np.object_], fill_value
|
| 48 |
+
) -> npt.NDArray[np.bool_]: ...
|
| 49 |
+
def get_blocks(
|
| 50 |
+
indices: npt.NDArray[np.int32],
|
| 51 |
+
) -> tuple[npt.NDArray[np.int32], npt.NDArray[np.int32]]: ...
|
pandas/_libs/testing.pyi
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Mapping
|
| 2 |
+
|
| 3 |
+
def assert_dict_equal(a: Mapping, b: Mapping, compare_keys: bool = ...) -> bool: ...
|
| 4 |
+
def assert_almost_equal(
|
| 5 |
+
a,
|
| 6 |
+
b,
|
| 7 |
+
rtol: float = ...,
|
| 8 |
+
atol: float = ...,
|
| 9 |
+
check_dtype: bool = ...,
|
| 10 |
+
obj=...,
|
| 11 |
+
lobj=...,
|
| 12 |
+
robj=...,
|
| 13 |
+
index_values=...,
|
| 14 |
+
) -> bool: ...
|
pandas/_libs/tslib.pyi
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import tzinfo
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
from pandas._typing import npt
|
| 6 |
+
|
| 7 |
+
def format_array_from_datetime(
|
| 8 |
+
values: npt.NDArray[np.int64],
|
| 9 |
+
tz: tzinfo | None = ...,
|
| 10 |
+
format: str | None = ...,
|
| 11 |
+
na_rep: str | float = ...,
|
| 12 |
+
reso: int = ..., # NPY_DATETIMEUNIT
|
| 13 |
+
) -> npt.NDArray[np.object_]: ...
|
| 14 |
+
def first_non_null(values: np.ndarray) -> int: ...
|
| 15 |
+
def array_to_datetime(
|
| 16 |
+
values: npt.NDArray[np.object_],
|
| 17 |
+
errors: str = ...,
|
| 18 |
+
dayfirst: bool = ...,
|
| 19 |
+
yearfirst: bool = ...,
|
| 20 |
+
utc: bool = ...,
|
| 21 |
+
creso: int = ...,
|
| 22 |
+
unit_for_numerics: str | None = ...,
|
| 23 |
+
) -> tuple[np.ndarray, tzinfo | None]: ...
|
| 24 |
+
|
| 25 |
+
# returned ndarray may be object dtype or datetime64[ns]
|
| 26 |
+
|
| 27 |
+
def array_to_datetime_with_tz(
|
| 28 |
+
values: npt.NDArray[np.object_],
|
| 29 |
+
tz: tzinfo,
|
| 30 |
+
dayfirst: bool,
|
| 31 |
+
yearfirst: bool,
|
| 32 |
+
creso: int,
|
| 33 |
+
) -> npt.NDArray[np.int64]: ...
|
pandas/_libs/tslibs/__init__.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = [
|
| 2 |
+
"BaseOffset",
|
| 3 |
+
"Day",
|
| 4 |
+
"IncompatibleFrequency",
|
| 5 |
+
"NaT",
|
| 6 |
+
"NaTType",
|
| 7 |
+
"OutOfBoundsDatetime",
|
| 8 |
+
"OutOfBoundsTimedelta",
|
| 9 |
+
"Period",
|
| 10 |
+
"Resolution",
|
| 11 |
+
"Tick",
|
| 12 |
+
"Timedelta",
|
| 13 |
+
"Timestamp",
|
| 14 |
+
"add_overflowsafe",
|
| 15 |
+
"astype_overflowsafe",
|
| 16 |
+
"delta_to_nanoseconds",
|
| 17 |
+
"dt64arr_to_periodarr",
|
| 18 |
+
"dtypes",
|
| 19 |
+
"get_resolution",
|
| 20 |
+
"get_supported_dtype",
|
| 21 |
+
"get_unit_from_dtype",
|
| 22 |
+
"guess_datetime_format",
|
| 23 |
+
"iNaT",
|
| 24 |
+
"ints_to_pydatetime",
|
| 25 |
+
"ints_to_pytimedelta",
|
| 26 |
+
"is_date_array_normalized",
|
| 27 |
+
"is_supported_dtype",
|
| 28 |
+
"is_unitless",
|
| 29 |
+
"localize_pydatetime",
|
| 30 |
+
"nat_strings",
|
| 31 |
+
"normalize_i8_timestamps",
|
| 32 |
+
"periods_per_day",
|
| 33 |
+
"periods_per_second",
|
| 34 |
+
"to_offset",
|
| 35 |
+
"tz_compare",
|
| 36 |
+
"tz_convert_from_utc",
|
| 37 |
+
"tz_convert_from_utc_single",
|
| 38 |
+
]
|
| 39 |
+
|
| 40 |
+
from pandas._libs.tslibs import dtypes
|
| 41 |
+
from pandas._libs.tslibs.conversion import localize_pydatetime
|
| 42 |
+
from pandas._libs.tslibs.dtypes import (
|
| 43 |
+
Resolution,
|
| 44 |
+
periods_per_day,
|
| 45 |
+
periods_per_second,
|
| 46 |
+
)
|
| 47 |
+
from pandas._libs.tslibs.nattype import (
|
| 48 |
+
NaT,
|
| 49 |
+
NaTType,
|
| 50 |
+
iNaT,
|
| 51 |
+
nat_strings,
|
| 52 |
+
)
|
| 53 |
+
from pandas._libs.tslibs.np_datetime import (
|
| 54 |
+
OutOfBoundsDatetime,
|
| 55 |
+
OutOfBoundsTimedelta,
|
| 56 |
+
add_overflowsafe,
|
| 57 |
+
astype_overflowsafe,
|
| 58 |
+
get_supported_dtype,
|
| 59 |
+
is_supported_dtype,
|
| 60 |
+
is_unitless,
|
| 61 |
+
py_get_unit_from_dtype as get_unit_from_dtype,
|
| 62 |
+
)
|
| 63 |
+
from pandas._libs.tslibs.offsets import (
|
| 64 |
+
BaseOffset,
|
| 65 |
+
Day,
|
| 66 |
+
Tick,
|
| 67 |
+
to_offset,
|
| 68 |
+
)
|
| 69 |
+
from pandas._libs.tslibs.parsing import guess_datetime_format
|
| 70 |
+
from pandas._libs.tslibs.period import (
|
| 71 |
+
IncompatibleFrequency,
|
| 72 |
+
Period,
|
| 73 |
+
)
|
| 74 |
+
from pandas._libs.tslibs.timedeltas import (
|
| 75 |
+
Timedelta,
|
| 76 |
+
delta_to_nanoseconds,
|
| 77 |
+
ints_to_pytimedelta,
|
| 78 |
+
)
|
| 79 |
+
from pandas._libs.tslibs.timestamps import Timestamp
|
| 80 |
+
from pandas._libs.tslibs.timezones import tz_compare
|
| 81 |
+
from pandas._libs.tslibs.tzconversion import tz_convert_from_utc_single
|
| 82 |
+
from pandas._libs.tslibs.vectorized import (
|
| 83 |
+
dt64arr_to_periodarr,
|
| 84 |
+
get_resolution,
|
| 85 |
+
ints_to_pydatetime,
|
| 86 |
+
is_date_array_normalized,
|
| 87 |
+
normalize_i8_timestamps,
|
| 88 |
+
tz_convert_from_utc,
|
| 89 |
+
)
|
pandas/_libs/tslibs/base.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (54.1 kB). View file
|
|
|
pandas/_libs/tslibs/ccalendar.cpython-312-x86_64-linux-gnu.so
ADDED
|
Binary file (90.8 kB). View file
|
|
|