Distinguish between datetime.datetime objects with a timezone and those without
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.8k
- Forks
- 302
- Avg merge
- 23h
- Merged PRs (30d)
- 8
Description
datetime.datetime objects created with timezone information cannot be compared to datetime objects without timezone information. This error comes up
TypeError: can't compare offset-naive and offset-aware datetimes
I believe it is possible for the type system to handle distinguishing between these two objects.
I have a proof of concept at my Company that looks something like this. The idea is to distinguish between these two object types and forbid their corresponding comparisons.
class DatetimeWithTimezone(datetime_lib.datetime):
"""Datetime with timezone."""
@overload
def replace(
self,
year: SupportsIndex = ...,
month: SupportsIndex = ...,
day: SupportsIndex = ...,
hour: SupportsIndex = ...,
minute: SupportsIndex = ...,
second: SupportsIndex = ...,
microsecond: SupportsIndex = ...,
tzinfo: _TzInfo = ...,
*,
fold: int = ...,
) -> Self: ...
@overload
def replace(
self,
year: SupportsIndex = ...,
month: SupportsIndex = ...,
day: SupportsIndex = ...,
hour: SupportsIndex = ...,
minute: SupportsIndex = ...,
second: SupportsIndex = ...,
microsecond: SupportsIndex = ...,
tzinfo: None = ...,
*,
fold: int = ...,
) -> DatetimeWithoutTimezone: ...
@overload
def astimezone(self, tz: _TzInfo) -> Self: ...
@overload
def astimezone(self, tz: None) -> DatetimeWithoutTimezone: ...
def utcoffset(self) -> timedelta: ...
def tzname(self) -> str: ...
def dst(self) -> timedelta: ...
def __le__(self, value: DatetimeWithTimezone, /) -> bool: ... # type: ignore[override]
def __lt__(self, value: DatetimeWithTimezone, /) -> bool: ... # type: ignore[override]
def __ge__(self, value: DatetimeWithTimezone, /) -> bool: ... # type: ignore[override]
def __gt__(self, value: DatetimeWithTimezone, /) -> bool: ... # type: ignore[override]
@overload # type: ignore[override]
def __sub__(self, value: Self, /) -> timedelta: ...
@overload
def __sub__(self, value: timedelta, /) -> Self: ...
class DatetimeWithoutTimezone(datetime_lib.datetime):
"""Datetime without timezone."""
@overload
def replace(
self,
year: SupportsIndex = ...,
month: SupportsIndex = ...,
day: SupportsIndex = ...,
hour: SupportsIndex = ...,
minute: SupportsIndex = ...,
second: SupportsIndex = ...,
microsecond: SupportsIndex = ...,
tzinfo: _TzInfo = ...,
*,
fold: int = ...,
) -> DatetimeWithTimezone: ...
@overload
def replace(
self,
year: SupportsIndex = ...,
month: SupportsIndex = ...,
day: SupportsIndex = ...,
hour: SupportsIndex = ...,
minute: SupportsIndex = ...,
second: SupportsIndex = ...,
microsecond: SupportsIndex = ...,
tzinfo: None = ...,
*,
fold: int = ...,
) -> Self: ...
@overload
def astimezone(self, tz: _TzInfo) -> DatetimeWithoutTimezone: ...
@overload
def astimezone(self, tz: None) -> Self: ...
def utcoffset(self) -> None: ...
def tzname(self) -> None: ...
def dst(self) -> None: ...
def __le__(self, value: DatetimeWithoutTimezone, /) -> bool: ... # type: ignore[override]
def __lt__(self, value: DatetimeWithoutTimezone, /) -> bool: ... # type: ignore[override]
def __ge__(self, value: DatetimeWithoutTimezone, /) -> bool: ... # type: ignore[override]
def __gt__(self, value: DatetimeWithoutTimezone, /) -> bool: ... # type: ignore[override]
@overload # type: ignore[override]
def __sub__(self, value: Self, /) -> timedelta: ...
@overload
def __sub__(self, value: timedelta, /) -> Self: ...
class datetime(datetime_lib.datetime):
@overload
def __new__(
cls,
year: SupportsIndex,
month: SupportsIndex,
day: SupportsIndex,
hour: SupportsIndex = ...,
minute: SupportsIndex = ...,
second: SupportsIndex = ...,
microsecond: SupportsIndex = ...,
tzinfo: _TzInfo = ...,
*,
fold: int = ...,
) -> DatetimeWithTimezone: ...
@overload
def __new__(
cls,
year: SupportsIndex,
month: SupportsIndex,
day: SupportsIndex,
hour: SupportsIndex = ...,
minute: SupportsIndex = ...,
second: SupportsIndex = ...,
microsecond: SupportsIndex = ...,
tzinfo: None = ...,
*,
fold: int = ...,
) -> DatetimeWithoutTimezone: ...
# On <3.12, the name of the first parameter in the pure-Python implementation
# didn't match the name in the C implementation,
# meaning it is only *safe* to pass it as a keyword argument on 3.12+
# Assume are on 3.12+
@overload
@classmethod
def fromtimestamp(
cls, timestamp: float, tz: None = ...
) -> DatetimeWithoutTimezone: ...
@overload
@classmethod
def fromtimestamp(
cls, timestamp: float, tz: _TzInfo
) -> DatetimeWithTimezone: ...
@overload
@classmethod
def now(cls, tz: _TzInfo) -> DatetimeWithTimezone: ...
@overload
@classmethod
def now(cls, tz: None = ...) -> DatetimeWithoutTimezone: ...
@overload
@classmethod
def combine(
cls, date: _Date, time: _Time, tzinfo: None = ...
) -> DatetimeWithoutTimezone: ...
@overload
@classmethod
def combine(
cls, date: _Date, time: _Time, tzinfo: _TzInfo
) -> DatetimeWithTimezone: ...
@classmethod
def strptime(
cls, date_string: str, format: str, /
) -> DatetimeWithTimezone | DatetimeWithoutTimezone: ...
I have some questions before I'm certain this could be possible for everyone
- Is it possible to make this backwards compatible?
- Is this something others are interested in?
- Should this actually be solved in the datetime library directly?
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the proposed DatetimeWithTimezone and DatetimeWithoutTimezone types and the listed datetime constructors and methods. Determine whether the distinction can be added compatibly to Python typing and whether it belongs in the datetime library; completion would require resolving these design questions and defining an agreed scope.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- developer-experience
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100