Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repos:
rev: v2.1.1
hooks:
- id: pycln
args: [--all]
args: [ --all ]

- repo: https://github.com/psf/black
rev: 22.6.0
Expand All @@ -26,7 +26,7 @@ repos:
rev: 5.10.1
hooks:
- id: isort
args: [--add-import, from __future__ import annotations]
args: [ --add-import, from __future__ import annotations, --lines-after-imports, "-1" ]

- repo: https://github.com/pycqa/flake8
rev: 5.0.4
Expand Down Expand Up @@ -67,3 +67,5 @@ repos:
exclude: ^build\.py$
additional_dependencies:
- pytest>=7.1.2
- types-backports
- types-python-dateutil
118 changes: 93 additions & 25 deletions pendulum/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import datetime as _datetime

from typing import Optional
from typing import Union
from typing import cast

from pendulum.__version__ import __version__
from pendulum.constants import DAYS_PER_WEEK
Expand Down Expand Up @@ -40,9 +40,6 @@
from pendulum.parser import parse
from pendulum.period import Period
from pendulum.time import Time
from pendulum.tz import POST_TRANSITION
from pendulum.tz import PRE_TRANSITION
from pendulum.tz import TRANSITION_ERROR
from pendulum.tz import UTC
from pendulum.tz import local_timezone
from pendulum.tz import set_local_timezone
Expand All @@ -52,7 +49,6 @@
from pendulum.tz.timezone import FixedTimezone
from pendulum.tz.timezone import Timezone


_TEST_NOW: DateTime | None = None
_LOCALE = "en"
_WEEK_STARTS_AT = MONDAY
Expand All @@ -61,7 +57,10 @@
_formatter = Formatter()


def _safe_timezone(obj: str | float | _datetime.tzinfo | Timezone | None) -> Timezone:
def _safe_timezone(
obj: str | float | _datetime.tzinfo | Timezone | FixedTimezone | None,
dt: _datetime.datetime | None = None,
) -> Timezone | FixedTimezone:
"""
Creates a timezone instance
from a string, Timezone, TimezoneInfo or integer offset.
Expand All @@ -75,19 +74,24 @@ def _safe_timezone(obj: str | float | _datetime.tzinfo | Timezone | None) -> Tim
if isinstance(obj, (int, float)):
obj = int(obj * 60 * 60)
elif isinstance(obj, _datetime.tzinfo):
# zoneinfo
if hasattr(obj, "key"):
obj = obj.key # type: ignore
# pytz
if hasattr(obj, "localize"):
obj = obj.zone
elif hasattr(obj, "localize"):
obj = obj.zone # type: ignore
elif obj.tzname(None) == "UTC":
return UTC
else:
offset = obj.utcoffset(None)
offset = obj.utcoffset(dt)

if offset is None:
offset = _datetime.timedelta(0)

obj = int(offset.total_seconds())

obj = cast(Union[str, int], obj)

return timezone(obj)


Expand All @@ -100,8 +104,8 @@ def datetime(
minute: int = 0,
second: int = 0,
microsecond: int = 0,
tz: str | float | Timezone | None = UTC,
fold: int | None = 1,
tz: str | float | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
fold: int = 1,
raise_on_unknown_times: bool = False,
) -> DateTime:
"""
Expand Down Expand Up @@ -146,7 +150,7 @@ def naive(
minute: int = 0,
second: int = 0,
microsecond: int = 0,
fold: int | None = 1,
fold: int = 1,
) -> DateTime:
"""
Return a naive DateTime.
Expand All @@ -168,7 +172,10 @@ def time(hour: int, minute: int = 0, second: int = 0, microsecond: int = 0) -> T
return Time(hour, minute, second, microsecond)


def instance(dt: _datetime.datetime, tz: str | Timezone | None = UTC) -> DateTime:
def instance(
dt: _datetime.datetime,
tz: str | Timezone | FixedTimezone | _datetime.tzinfo | None = UTC,
) -> DateTime:
"""
Create a DateTime instance from a datetime one.
"""
Expand All @@ -180,19 +187,18 @@ def instance(dt: _datetime.datetime, tz: str | Timezone | None = UTC) -> DateTim

tz = dt.tzinfo or tz

# Checking for pytz/tzinfo
if isinstance(tz, _datetime.tzinfo) and not isinstance(tz, Timezone):
# pytz
if hasattr(tz, "localize") and tz.zone:
tz = tz.zone
else:
# We have no sure way to figure out
# the timezone name, we fallback
# on a fixed offset
tz = tz.utcoffset(dt).total_seconds() / 3600
if tz is not None:
tz = _safe_timezone(tz, dt=dt)

return datetime(
dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.microsecond, tz=tz
dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second,
dt.microsecond,
tz=cast(Union[str, int, Timezone, FixedTimezone, None], tz),
)


Expand Down Expand Up @@ -228,12 +234,13 @@ def from_format(
string: str,
fmt: str,
tz: str | Timezone = UTC,
locale: str | None = None, # noqa
locale: str | None = None,
) -> DateTime:
"""
Creates a DateTime instance from a specific format.
"""
parts = _formatter.parse(string, fmt, now(), locale=locale)

if parts["tz"] is None:
parts["tz"] = tz

Expand Down Expand Up @@ -288,3 +295,64 @@ def period(start: DateTime, end: DateTime, absolute: bool = False) -> Period:
Create a Period instance.
"""
return Period(start, end, absolute=absolute)


__all__ = [
"__version__",
"DAYS_PER_WEEK",
"FRIDAY",
"HOURS_PER_DAY",
"MINUTES_PER_HOUR",
"MONDAY",
"MONTHS_PER_YEAR",
"SATURDAY",
"SECONDS_PER_DAY",
"SECONDS_PER_HOUR",
"SECONDS_PER_MINUTE",
"SUNDAY",
"THURSDAY",
"TUESDAY",
"WEDNESDAY",
"WEEKS_PER_YEAR",
"YEARS_PER_CENTURY",
"YEARS_PER_DECADE",
"Date",
"DateTime",
"Duration",
"Formatter",
"date",
"datetime",
"duration",
"format_diff",
"from_format",
"from_timestamp",
"get_locale",
"get_test_now",
"has_test_now",
"instance",
"local",
"locale",
"naive",
"now",
"period",
"set_locale",
"set_test_now",
"test",
"week_ends_at",
"week_starts_at",
"parse",
"Period",
"Time",
"UTC",
"local_timezone",
"set_local_timezone",
"test_local_timezone",
"time",
"timezone",
"timezones",
"today",
"tomorrow",
"FixedTimezone",
"Timezone",
"yesterday",
]
31 changes: 31 additions & 0 deletions pendulum/_extensions/_helpers.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

from collections import namedtuple
from datetime import date
from datetime import datetime

def days_in_year(year: int) -> int: ...
def is_leap(year: int) -> bool: ...
def is_long_year(year: int) -> bool: ...
def local_time(
unix_time: int, utc_offset: int, microseconds: int
) -> tuple[int, int, int, int, int, int, int]: ...

class PreciseDiff(
namedtuple(
"PreciseDiff",
"years months days " "hours minutes seconds microseconds " "total_days",
)
):
years: int
months: int
days: int
hours: int
minutes: int
seconds: int
microseconds: int
total_days: int

def precise_diff(d1: datetime | date, d2: datetime | date) -> PreciseDiff: ...
def timestamp(dt: datetime) -> int: ...
def week_day(year: int, month: int, day: int) -> int: ...
Loading