diff --git a/arrow/api.py b/arrow/api.py index d8ed24b9..8d6e0509 100644 --- a/arrow/api.py +++ b/arrow/api.py @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/arrow/arrow.py b/arrow/arrow.py index 1ede107f..6db278c9 100644 --- a/arrow/arrow.py +++ b/arrow/arrow.py @@ -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 @@ -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: @@ -148,6 +149,8 @@ class Arrow: } _datetime: dt_datetime + default_tz: TZ_EXPR + default_tz_used: bool def __init__( self, @@ -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) @@ -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 ` 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 ` that will be used on naive datetimes. + Defaults to local timezone. Usage:: @@ -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) @@ -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), ) @@ -235,7 +252,7 @@ def utcnow(cls) -> "Arrow": dt.minute, dt.second, dt.microsecond, - dt.tzinfo, + tzinfo=dt.tzinfo, fold=getattr(dt, "fold", 0), ) @@ -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 ` 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 ` 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) @@ -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), ) @@ -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 ` object from a ``datetime`` and optional replacement timezone. :param dt: the ``datetime`` :param tzinfo: (optional) A :ref:`timezone expression `. Defaults to ``dt``'s - timezone, or UTC if naive. + timezone, or ``default_tz`` if naive. + :param default_tz: (optional) a :ref:`timezone expression ` that will be used on naive datetimes. + Defaults to UTC. Usage:: @@ -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, @@ -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 ` 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 `. Defaults to UTC. + :param tzinfo: (optional) A :ref:`timezone expression `. + :param default_tz: (optional) a :ref:`timezone expression ` 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( @@ -384,7 +435,7 @@ def strptime( dt.minute, dt.second, dt.microsecond, - tzinfo, + tzinfo=tzinfo, fold=getattr(dt, "fold", 0), ) @@ -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), ) @@ -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), ) diff --git a/arrow/constants.py b/arrow/constants.py index 53d163b9..fb1ecedd 100644 --- a/arrow/constants.py +++ b/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 @@ -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 diff --git a/arrow/factory.py b/arrow/factory.py index aad4af8b..ba04a272 100644 --- a/arrow/factory.py +++ b/arrow/factory.py @@ -41,6 +41,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 @@ -62,6 +63,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 @@ -74,6 +76,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 @@ -86,6 +89,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 @@ -95,10 +99,11 @@ def get(self, *args: Any, **kwargs: Any) -> Arrow: :param locale: (optional) a ``str`` specifying a locale for the parser. Defaults to 'en-us'. :param tzinfo: (optional) a :ref:`timezone expression ` or tzinfo object. Replaces the timezone unless using an input form that is explicitly UTC or specifies - the timezone in a positional argument. Defaults to UTC. + the timezone in a positional argument. :param normalize_whitespace: (optional) a ``bool`` specifying whether or not to normalize redundant whitespace (spaces, tabs, and newlines) in a datetime string before parsing. - Defaults to false. + Defaults to false.tr`` specifying a locale for the parser. Defaults to 'en-us'. + :param default_tz: (optional) a :ref:`timezone expression ` that will be used on naive datetimes. Usage:: @@ -196,6 +201,7 @@ def get(self, *args: Any, **kwargs: Any) -> Arrow: arg_count = len(args) locale = kwargs.pop("locale", DEFAULT_LOCALE) tz = kwargs.get("tzinfo", None) + default_tz = kwargs.get("default_tz", None) normalize_whitespace = kwargs.pop("normalize_whitespace", False) # if kwargs given, send to constructor unless only tzinfo provided @@ -203,47 +209,45 @@ def get(self, *args: Any, **kwargs: Any) -> Arrow: arg_count = 3 # tzinfo kwarg is not provided - if len(kwargs) == 1 and tz is None: + if len(kwargs) == 1 and tz is None and default_tz is None: arg_count = 3 - # () -> now, @ tzinfo or utc + # () -> now, @ tzinfo or default_tz or utc if arg_count == 0: - if isinstance(tz, str): - tz = parser.TzinfoParser.parse(tz) - return self.type.now(tzinfo=tz) - - if isinstance(tz, dt_tzinfo): - return self.type.now(tzinfo=tz) - - return self.type.utcnow() + if default_tz is None: + default_tz = dateutil_tz.tzutc() + return self.type.now(default_tz=default_tz, tzinfo=tz) if arg_count == 1: arg = args[0] - if isinstance(arg, Decimal): - arg = float(arg) # (None) -> raises an exception if arg is None: raise TypeError("Cannot parse argument of type None.") + if isinstance(arg, Decimal): + arg = float(arg) + # try (int, float) -> from timestamp @ tzinfo - elif not isinstance(arg, str) and is_timestamp(arg): - if tz is None: + if not isinstance(arg, str) and is_timestamp(arg): + if default_tz is None: # set to UTC by default - tz = dateutil_tz.tzutc() - return self.type.fromtimestamp(arg, tzinfo=tz) + default_tz = dateutil_tz.tzutc() + return self.type.fromtimestamp(arg, default_tz=default_tz, tzinfo=tz) # (Arrow) -> from the object's datetime @ tzinfo elif isinstance(arg, Arrow): - return self.type.fromdatetime(arg.datetime, tzinfo=tz) + return self.type.fromdatetime( + arg.datetime, default_tz=default_tz, tzinfo=tz + ) # (datetime) -> from datetime @ tzinfo elif isinstance(arg, datetime): - return self.type.fromdatetime(arg, tzinfo=tz) + return self.type.fromdatetime(arg, default_tz=default_tz, tzinfo=tz) # (date) -> from date @ tzinfo elif isinstance(arg, date): - return self.type.fromdate(arg, tzinfo=tz) + return self.type.fromdate(arg, default_tz=default_tz, tzinfo=tz) # (tzinfo) -> now @ tzinfo elif isinstance(arg, dt_tzinfo): @@ -252,7 +256,7 @@ def get(self, *args: Any, **kwargs: Any) -> Arrow: # (str) -> parse @ tzinfo elif isinstance(arg, str): dt = parser.DateTimeParser(locale).parse_iso(arg, normalize_whitespace) - return self.type.fromdatetime(dt, tzinfo=tz) + return self.type.fromdatetime(dt, default_tz=default_tz, tzinfo=tz) # (struct_time) -> from struct_time elif isinstance(arg, struct_time): @@ -261,7 +265,7 @@ def get(self, *args: Any, **kwargs: Any) -> Arrow: # (iso calendar) -> convert then from date @ tzinfo elif isinstance(arg, tuple) and len(arg) == 3: d = iso_to_gregorian(*arg) - return self.type.fromdate(d, tzinfo=tz) + return self.type.fromdate(d, default_tz=default_tz, tzinfo=tz) else: raise TypeError(f"Cannot parse single argument of type {type(arg)!r}.") @@ -274,7 +278,9 @@ def get(self, *args: Any, **kwargs: Any) -> Arrow: # (datetime, tzinfo/str) -> fromdatetime @ tzinfo if isinstance(arg_2, (dt_tzinfo, str)): - return self.type.fromdatetime(arg_1, tzinfo=arg_2) + return self.type.fromdatetime( + arg_1, default_tz=default_tz, tzinfo=arg_2 + ) else: raise TypeError( f"Cannot parse two arguments of types 'datetime', {type(arg_2)!r}." @@ -284,7 +290,9 @@ def get(self, *args: Any, **kwargs: Any) -> Arrow: # (date, tzinfo/str) -> fromdate @ tzinfo if isinstance(arg_2, (dt_tzinfo, str)): - return self.type.fromdate(arg_1, tzinfo=arg_2) + return self.type.fromdate( + arg_1, default_tz=default_tz, tzinfo=arg_2 + ) else: raise TypeError( f"Cannot parse two arguments of types 'date', {type(arg_2)!r}." @@ -295,7 +303,7 @@ def get(self, *args: Any, **kwargs: Any) -> Arrow: dt = parser.DateTimeParser(locale).parse( args[0], args[1], normalize_whitespace ) - return self.type.fromdatetime(dt, tzinfo=tz) + return self.type.fromdatetime(dt, default_tz=default_tz, tzinfo=tz) else: raise TypeError( @@ -340,9 +348,4 @@ def now(self, tz: Optional[TZ_EXPR] = None) -> Arrow: """ - if tz is None: - tz = dateutil_tz.tzlocal() - elif not isinstance(tz, dt_tzinfo): - tz = parser.TzinfoParser.parse(tz) - - return self.type.now(tz) + return self.type.now(default_tz=dateutil_tz.tzlocal(), tzinfo=tz) diff --git a/arrow/util.py b/arrow/util.py index f3eaa21c..084bc780 100644 --- a/arrow/util.py +++ b/arrow/util.py @@ -1,10 +1,13 @@ """Helpful functions used internally within arrow.""" import datetime -from typing import Any, Optional, cast +from datetime import datetime as dt_datetime +from datetime import tzinfo as dt_tzinfo +from typing import Any, Optional, Tuple, Union, cast from dateutil.rrule import WEEKLY, rrule +from arrow import parser from arrow.constants import ( MAX_ORDINAL, MAX_TIMESTAMP, @@ -115,3 +118,39 @@ def validate_bounds(bounds: str) -> None: __all__ = ["next_weekday", "is_timestamp", "validate_ordinal", "iso_to_gregorian"] + + +def get_tzinfo_default_used( + default_tz: Union[dt_tzinfo, str], + dt: Optional[dt_datetime] = None, + tzinfo: Optional[Union[dt_tzinfo, str]] = None, +) -> Tuple[dt_tzinfo, bool]: + """Get the tzinfo to use, and indicate if it was based on ``default_tz``. + + :param default_tz: A :ref:`timezone expression ` that will be used on naive datetimes. + :param dt: (optional) a ``datetime`` object. + :param tzinfo: (optional) a ``tzinfo`` object. + """ + + default_tz_used = False + + if tzinfo is None: + if dt and dt.tzinfo: + tzinfo = dt.tzinfo + + else: + tzinfo = default_tz + default_tz_used = True + + # detect that tzinfo is a pytz object (issue #626) + if ( + isinstance(tzinfo, dt_tzinfo) + and 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) + + return tzinfo, default_tz_used diff --git a/docs/index.rst b/docs/index.rst index d4f9ec2a..f8be2cdf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -60,6 +60,16 @@ Parse from a string: >>> arrow.get('2013-05-05 12:30:45', 'YYYY-MM-DD HH:mm:ss') +It is possible to override the default UTC timezone for a naive datetime +(any timezone-aware datetime will keep it's original timezone): + +.. code-block:: python + + >>> arrow.get('2013-05-05 12:30:45', 'YYYY-MM-DD HH:mm:ss', default_tz='US/Pacific') + + >>> arrow.get('2013-05-05 12:30:45Z', 'YYYY-MM-DD HH:mm:ssZ', default_tz='US/Pacific') + + Search a date in a string: .. code-block:: python diff --git a/tests/test_arrow.py b/tests/test_arrow.py index 38d5000a..cfd1cf1a 100644 --- a/tests/test_arrow.py +++ b/tests/test_arrow.py @@ -81,6 +81,58 @@ def test_init_with_fold(self): assert before == after assert before.utcoffset() != after.utcoffset() + def test_init_default_tz(self): + + result = arrow.Arrow( + 2013, 2, 2, 12, 30, 45, 999999, default_tz=tz.gettz("Europe/Oslo") + ) + self.expected = datetime( + 2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Oslo") + ) + assert result._datetime == self.expected + assert_datetime_equality(result._datetime, self.expected, 1) + assert result.default_tz_used + assert result.default_tz == tz.gettz("Europe/Oslo") + + def test_init_default_tz_string(self): + + result = arrow.Arrow(2013, 2, 2, 12, 30, 45, 999999, default_tz="Europe/Oslo") + self.expected = datetime( + 2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.gettz("Europe/Oslo") + ) + assert result._datetime == self.expected + assert_datetime_equality(result._datetime, self.expected, 1) + assert result.default_tz_used + assert result.default_tz == "Europe/Oslo" + + def test_init_default_tz_none(self): + + result = arrow.Arrow(2013, 2, 2, 12, 30, 45, 999999) + self.expected = datetime(2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.tzutc()) + assert result._datetime == self.expected + assert_datetime_equality(result._datetime, self.expected, 1) + assert result.default_tz_used is True + assert result.default_tz == tz.tzutc() + + def test_init_default_tz_existing_tzinfo(self): + + result = arrow.Arrow( + 2013, + 2, + 2, + 12, + 30, + 45, + 999999, + tzinfo=tz.tzutc(), + default_tz=tz.gettz("Europe/Oslo"), + ) + self.expected = datetime(2013, 2, 2, 12, 30, 45, 999999, tzinfo=tz.tzutc()) + assert result._datetime == self.expected + assert_datetime_equality(result._datetime, self.expected, 1) + assert result.default_tz_used is False + assert result.default_tz == tz.gettz("Europe/Oslo") + class TestTestArrowFactory: def test_now(self): @@ -169,6 +221,9 @@ def test_fromdate(self): assert result._datetime == datetime(2013, 2, 3, tzinfo=tz.gettz("US/Pacific")) + result = arrow.Arrow.fromdate(dt, default_tz=tz.gettz("US/Pacific")) + assert result._datetime == datetime(2013, 2, 3, tzinfo=tz.gettz("US/Pacific")) + def test_strptime(self): formatted = datetime(2013, 2, 3, 12, 30, 45).strftime("%Y-%m-%d %H:%M:%S") @@ -288,6 +343,19 @@ def test_tzinfo(self): assert self.arrow.tzinfo == tz.tzutc() + def test_default_tz(self): + + assert self.arrow.default_tz == tz.tzutc() + assert self.arrow.default_tz_used is True + + result = arrow.Arrow(2013, 1, 1, tzinfo=tz.gettz("Europe/Oslo")) + assert result.default_tz == tz.tzutc() + assert result.default_tz_used is False + + result = arrow.Arrow(2013, 1, 1, default_tz=tz.gettz("Europe/Oslo")) + assert result.default_tz == tz.gettz("Europe/Oslo") + assert result.default_tz_used + def test_naive(self): assert self.arrow.naive == self.arrow._datetime.replace(tzinfo=None) diff --git a/tests/test_factory.py b/tests/test_factory.py index f368126c..c66e9478 100644 --- a/tests/test_factory.py +++ b/tests/test_factory.py @@ -90,6 +90,27 @@ def test_one_arg_timestamp_with_tzinfo(self): self.factory.get(timestamp, tzinfo=timezone), timestamp_dt ) + def test_one_arg_timestamp_with_default_tz(self): + + timestamp = time.time() + timestamp_dt = datetime.fromtimestamp(timestamp, tz=tz.tzutc()).astimezone( + tz.gettz("US/Pacific") + ) + timezone = tz.gettz("US/Pacific") + + assert_datetime_equality( + self.factory.get(timestamp, default_tz=timezone), timestamp_dt + ) + + def test_one_arg_str(self): + + result = self.factory.get("1990-01-01 12:30:45") + self.expected = datetime(1990, 1, 1, 12, 30, 45).replace(tzinfo=tz.tzutc()) + + assert_datetime_equality(result, self.expected) + assert result.default_tz == tz.tzutc() + assert result.default_tz_used + def test_one_arg_arrow(self): arw = self.factory.utcnow() @@ -156,6 +177,34 @@ def test_kwarg_tzinfo_string(self): with pytest.raises(ParserError): self.factory.get(tzinfo="US/PacificInvalidTzinfo") + def test_kwarg_default_tz(self): + + self.expected = datetime.now().astimezone(tz.gettz("Europe/Oslo")) + + assert_datetime_equality( + self.factory.get(default_tz=tz.gettz("Europe/Oslo")), self.expected + ) + + def test_kwarg_default_tz_string(self): + + self.expected = datetime.now().astimezone(tz.gettz("Europe/Oslo")) + + assert_datetime_equality( + self.factory.get(default_tz="Europe/Oslo"), self.expected + ) + + with pytest.raises(ParserError): + self.factory.get(default_tz="Europe/OsloInvalidTzinfo") + + def test_kwarg_default_tz_string_existing_tzinfo(self): + + self.expected = datetime(1990, 1, 1, 12, 30, 45).replace(tzinfo=tz.tzutc()) + + assert_datetime_equality( + self.factory.get("1990-01-01 12:30:45+00:00", default_tz="Europe/Oslo"), + self.expected, + ) + def test_kwarg_normalize_whitespace(self): result = self.factory.get( "Jun 1 2005 1:33PM", @@ -206,6 +255,19 @@ def test_one_arg_date_tzinfo_kwarg(self): assert result.date() == expected.date() assert result.tzinfo == expected.tzinfo + def test_one_arg_date_default_tz_kwarg(self): + + da = date(2021, 4, 29) + + result = self.factory.get(da, default_tz="America/Chicago") + + expected = Arrow(2021, 4, 29, tzinfo=tz.gettz("America/Chicago")) + + assert result.date() == expected.date() + assert result.tzinfo == expected.tzinfo + assert result.default_tz == "America/Chicago" + assert result.default_tz_used is True + def test_one_arg_iso_calendar_tzinfo_kwarg(self): result = self.factory.get((2004, 1, 7), tzinfo="America/Chicago")