Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(1090/default_tz): Added possibility to override default UTC - #1… #1122

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions arrow/api.py
Expand Up @@ -26,6 +26,7 @@ def get(
locale: str = DEFAULT_LOCALE,
tzinfo: Optional[TZ_EXPR] = None,
normalize_whitespace: bool = False,
default_tz: Optional[TZ_EXPR] = None,
) -> Arrow:
... # pragma: no cover

Expand All @@ -36,6 +37,7 @@ def get(
locale: str = DEFAULT_LOCALE,
tzinfo: Optional[TZ_EXPR] = None,
normalize_whitespace: bool = False,
default_tz: Optional[TZ_EXPR] = None,
) -> Arrow:
... # pragma: no cover

Expand All @@ -57,6 +59,7 @@ def get(
locale: str = DEFAULT_LOCALE,
tzinfo: Optional[TZ_EXPR] = None,
normalize_whitespace: bool = False,
default_tz: Optional[TZ_EXPR] = None,
) -> Arrow:
... # pragma: no cover

Expand All @@ -69,6 +72,7 @@ def get(
locale: str = DEFAULT_LOCALE,
tzinfo: Optional[TZ_EXPR] = None,
normalize_whitespace: bool = False,
default_tz: Optional[TZ_EXPR] = None,
) -> Arrow:
... # pragma: no cover

Expand All @@ -81,6 +85,7 @@ def get(
locale: str = DEFAULT_LOCALE,
tzinfo: Optional[TZ_EXPR] = None,
normalize_whitespace: bool = False,
default_tz: Optional[TZ_EXPR] = None,
) -> Arrow:
... # pragma: no cover

Expand Down
135 changes: 93 additions & 42 deletions arrow/arrow.py
Expand Up @@ -33,7 +33,7 @@
from dateutil.relativedelta import relativedelta

from arrow import formatter, locales, parser, util
from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES
from arrow.constants import DEFAULT_LOCALE, DEFAULT_TZ, DEHUMANIZE_LOCALES
from arrow.locales import TimeFrameLiteral

if sys.version_info < (3, 8): # pragma: no cover
Expand Down Expand Up @@ -93,7 +93,8 @@ class Arrow:
:param minute: (optional) the minute, Defaults to 0.
:param second: (optional) the second, Defaults to 0.
:param microsecond: (optional) the microsecond. Defaults to 0.
:param tzinfo: (optional) A timezone expression. Defaults to UTC.
:param default_tz: A timezone expression that will be used on naive datetimes. Defaults to UTC.
:param tzinfo: (optional) A timezone expression.
:param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0.

.. _tz-expr:
Expand Down Expand Up @@ -148,6 +149,8 @@ class Arrow:
}

_datetime: dt_datetime
default_tz: TZ_EXPR
default_tz_used: bool

def __init__(
self,
Expand All @@ -159,20 +162,22 @@ def __init__(
second: int = 0,
microsecond: int = 0,
tzinfo: Optional[TZ_EXPR] = None,
default_tz: TZ_EXPR = DEFAULT_TZ,
default_tz_used: bool = False,
**kwargs: Any,
) -> None:
if tzinfo is None:
tzinfo = dateutil_tz.tzutc()
# detect that tzinfo is a pytz object (issue #626)
elif (
isinstance(tzinfo, dt_tzinfo)
and hasattr(tzinfo, "localize")
self.default_tz = default_tz
self.default_tz_used = default_tz_used
# If tzinfo is not a datetime.tzinfo object parse tzinfo
# Also detects if tzinfo is a pytz object (issue #626)
if not isinstance(tzinfo, dt_tzinfo) or (
hasattr(tzinfo, "localize")
and hasattr(tzinfo, "zone")
and tzinfo.zone # type: ignore[attr-defined]
):
tzinfo = parser.TzinfoParser.parse(tzinfo.zone) # type: ignore[attr-defined]
elif isinstance(tzinfo, str):
tzinfo = parser.TzinfoParser.parse(tzinfo)
tzinfo, self.default_tz_used = util.get_tzinfo_default_used(
default_tz=self.default_tz, tzinfo=tzinfo
)

fold = kwargs.get("fold", 0)

Expand All @@ -183,11 +188,17 @@ def __init__(
# factories: single object, both original and from datetime.

@classmethod
def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow":
def now(
cls,
tzinfo: Optional[TZ_EXPR] = None,
default_tz: Optional[TZ_EXPR] = None,
) -> "Arrow":
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given
timezone.

:param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
:param tzinfo: (optional) a ``tzinfo`` object.
:param default_tz: (optional) a :ref:`timezone expression <tz-expr>` that will be used on naive datetimes.
Defaults to local timezone.

Usage::

Expand All @@ -196,8 +207,12 @@ def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow":

"""

if tzinfo is None:
tzinfo = dateutil_tz.tzlocal()
if default_tz is None:
default_tz = dateutil_tz.tzlocal()

tzinfo, default_tz_used = util.get_tzinfo_default_used(
default_tz=default_tz, tzinfo=tzinfo
)

dt = dt_datetime.now(tzinfo)

Expand All @@ -209,7 +224,9 @@ def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow":
dt.minute,
dt.second,
dt.microsecond,
dt.tzinfo,
tzinfo=dt.tzinfo,
default_tz=default_tz,
default_tz_used=default_tz_used,
fold=getattr(dt, "fold", 0),
)

Expand All @@ -235,7 +252,7 @@ def utcnow(cls) -> "Arrow":
dt.minute,
dt.second,
dt.microsecond,
dt.tzinfo,
tzinfo=dt.tzinfo,
fold=getattr(dt, "fold", 0),
)

Expand All @@ -244,23 +261,27 @@ def fromtimestamp(
cls,
timestamp: Union[int, float, str],
tzinfo: Optional[TZ_EXPR] = None,
default_tz: Optional[TZ_EXPR] = None,
) -> "Arrow":
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, converted to
the given timezone.

:param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
:param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.

:param tzinfo: (optional) a ``tzinfo`` object.
:param default_tz: (optional) a :ref:`timezone expression <tz-expr>` that will be used on naive datetimes.
Defaults to local timezone.
"""

if tzinfo is None:
tzinfo = dateutil_tz.tzlocal()
elif isinstance(tzinfo, str):
tzinfo = parser.TzinfoParser.parse(tzinfo)

if not util.is_timestamp(timestamp):
raise ValueError(f"The provided timestamp {timestamp!r} is invalid.")

if default_tz is None:
default_tz = dateutil_tz.tzlocal()

tzinfo, default_tz_used = util.get_tzinfo_default_used(
default_tz=default_tz, tzinfo=tzinfo
)

timestamp = util.normalize_timestamp(float(timestamp))
dt = dt_datetime.fromtimestamp(timestamp, tzinfo)

Expand All @@ -272,7 +293,9 @@ def fromtimestamp(
dt.minute,
dt.second,
dt.microsecond,
dt.tzinfo,
tzinfo=dt.tzinfo,
default_tz=default_tz,
default_tz_used=default_tz_used,
fold=getattr(dt, "fold", 0),
)

Expand All @@ -298,18 +321,25 @@ def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow":
dt.minute,
dt.second,
dt.microsecond,
dateutil_tz.tzutc(),
tzinfo=dateutil_tz.tzutc(),
fold=getattr(dt, "fold", 0),
)

@classmethod
def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
def fromdatetime(
cls,
dt: dt_datetime,
tzinfo: Optional[TZ_EXPR] = None,
default_tz: Optional[TZ_EXPR] = None,
) -> "Arrow":
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and
optional replacement timezone.

:param dt: the ``datetime``
:param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to ``dt``'s
timezone, or UTC if naive.
timezone, or ``default_tz`` if naive.
:param default_tz: (optional) a :ref:`timezone expression <tz-expr>` that will be used on naive datetimes.
Defaults to UTC.

Usage::

Expand All @@ -320,11 +350,12 @@ def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arr

"""

if tzinfo is None:
if dt.tzinfo is None:
tzinfo = dateutil_tz.tzutc()
else:
tzinfo = dt.tzinfo
if default_tz is None:
default_tz = dateutil_tz.tzutc()

tzinfo, default_tz_used = util.get_tzinfo_default_used(
default_tz=default_tz, dt=dt, tzinfo=tzinfo
)

return cls(
dt.year,
Expand All @@ -334,24 +365,44 @@ def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arr
dt.minute,
dt.second,
dt.microsecond,
tzinfo,
tzinfo=tzinfo,
default_tz=default_tz,
default_tz_used=default_tz_used,
fold=getattr(dt, "fold", 0),
)

@classmethod
def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow":
def fromdate(
cls,
date: date,
tzinfo: Optional[TZ_EXPR] = None,
default_tz: Optional[TZ_EXPR] = None,
) -> "Arrow":
"""Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional
replacement timezone. All time values are set to 0.

:param date: the ``date``
:param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to UTC.
:param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`.
:param default_tz: (optional) a :ref:`timezone expression <tz-expr>` that will be used on naive datetimes.
Defaults to UTC.

"""

if tzinfo is None:
tzinfo = dateutil_tz.tzutc()
if default_tz is None:
default_tz = dateutil_tz.tzutc()

return cls(date.year, date.month, date.day, tzinfo=tzinfo)
tzinfo, default_tz_used = util.get_tzinfo_default_used(
default_tz=default_tz, tzinfo=tzinfo
)

return cls(
date.year,
date.month,
date.day,
tzinfo=tzinfo,
default_tz=default_tz,
default_tz_used=default_tz_used,
)

@classmethod
def strptime(
Expand Down Expand Up @@ -384,7 +435,7 @@ def strptime(
dt.minute,
dt.second,
dt.microsecond,
tzinfo,
tzinfo=tzinfo,
fold=getattr(dt, "fold", 0),
)

Expand Down Expand Up @@ -412,7 +463,7 @@ def fromordinal(cls, ordinal: int) -> "Arrow":
dt.minute,
dt.second,
dt.microsecond,
dt.tzinfo,
tzinfo=dt.tzinfo,
fold=getattr(dt, "fold", 0),
)

Expand Down Expand Up @@ -1086,7 +1137,7 @@ def to(self, tz: TZ_EXPR) -> "Arrow":
dt.minute,
dt.second,
dt.microsecond,
dt.tzinfo,
tzinfo=dt.tzinfo,
fold=getattr(dt, "fold", 0),
)

Expand Down
5 changes: 4 additions & 1 deletion arrow/constants.py
@@ -1,7 +1,9 @@
"""Constants used internally in arrow."""

import sys
from datetime import datetime
from datetime import datetime, tzinfo

from dateutil import tz

if sys.version_info < (3, 8): # pragma: no cover
from typing_extensions import Final
Expand Down Expand Up @@ -35,6 +37,7 @@
MAX_ORDINAL: Final[int] = datetime.max.toordinal()
MIN_ORDINAL: Final[int] = 1

DEFAULT_TZ: tzinfo = tz.tzutc()
DEFAULT_LOCALE: Final[str] = "en-us"

# Supported dehumanize locales
Expand Down