Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Distinguish between datetime.datetime objects with a timezone and those without

Open
#1,962 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Needs clarification
Activity status
Stale
Tech stack
python

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.

Description

topic: feature

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

  1. Is it possible to make this backwards compatible?
  2. Is this something others are interested in?
  3. Should this actually be solved in the datetime library directly?
Dominant language
Python
Stars
1.8k
Forks
302
Avg merge
23h
Merged PRs (30d)
8

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from python/typing

All issues in python/typing

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.