"""
Types for narrowing on the builtin datetime types.
These types can be used without installing any extra dependencies, however, to parse
strings, python-dateutil must be installed or a
:py:class:`phantom.errors.MissingDependency` error will be raised when calling parse.
You can install python-dateutil by using the ``[dateutil]`` or ``[all]`` extras.
"""
from __future__ import annotations
import datetime
from . import Phantom
from . import _hypothesis
from .bounds import parse_str
from .errors import MissingDependency
from .predicates.datetime import is_tz_aware
from .predicates.datetime import is_tz_naive
from .schema import Schema
try:
import dateutil.parser
parse_datetime_str = dateutil.parser.parse
DateutilParseError = dateutil.parser.ParserError
except ImportError as e:
exception = e
def parse_datetime_str(
*_: object,
**__: object,
) -> datetime.datetime:
raise MissingDependency(
"python-dateutil needs to be installed to use this type for parsing. It "
"can be installed with the phantom-types[dateutil] extra."
) from exception
class DateutilParseError(Exception): # type: ignore[no-redef]
...
__all__ = ("TZAware", "TZNaive")
def parse_datetime(value: object) -> datetime.datetime:
if isinstance(value, datetime.datetime):
return value
str_value = parse_str(value)
try:
return parse_datetime_str(str_value)
except DateutilParseError as exc:
raise TypeError("Could not parse datetime from given string") from exc
[docs]class TZAware(datetime.datetime, Phantom, predicate=is_tz_aware):
"""
A type for helping ensure that ``datetime`` objects are always timezone aware.
>>> isinstance(datetime.datetime.now(), TZAware)
False
>>> isinstance(datetime.datetime.now(tz=datetime.timezone.utc), TZAware)
True
"""
# A property of being aware is (dt.tzinfo != None), so we can safely narrow this
# attribute to not include None.
tzinfo: datetime.tzinfo
[docs] @classmethod
def parse(cls, instance: object) -> TZAware:
return super().parse(parse_datetime(instance))
@classmethod
def __schema__(cls) -> Schema:
return {
**super().__schema__(), # type: ignore[misc]
"description": "A date-time with timezone data.",
}
@classmethod
def __register_strategy__(cls) -> _hypothesis.SearchStrategy:
from hypothesis.strategies import datetimes
from hypothesis.strategies import timezones
return datetimes(timezones=timezones())
[docs]class TZNaive(datetime.datetime, Phantom, predicate=is_tz_naive):
"""
>>> isinstance(datetime.datetime.now(), TZNaive)
True
>>> isinstance(datetime.datetime.now(tz=datetime.timezone.utc), TZNaive)
False
"""
[docs] @classmethod
def parse(cls, instance: object) -> TZNaive:
return super().parse(parse_datetime(instance))
@classmethod
def __schema__(cls) -> Schema:
return {
**super().__schema__(), # type: ignore[misc]
"description": "A date-time without timezone data.",
"format": "date-time-naive",
}
@classmethod
def __register_strategy__(cls) -> _hypothesis.SearchStrategy:
from hypothesis.strategies import datetimes
from hypothesis.strategies import none
return datetimes(timezones=none())