python / python/typing

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

Offen
#1,962 1 Kommentar 1 Reaktion 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

topic: feature
Vorherrschende Sprache
Python
Sterne
1.8k
Forks
302
Ø Merge
23 Std.
Gemergte PRs (30 T.)
8

Beschreibung

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?

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginnen Sie mit der Prüfung der vorgeschlagenen Typen DatetimeWithTimezone und DatetimeWithoutTimezone sowie der aufgeführten datetime-Konstruktoren und -Methoden. Bestimmen Sie, ob sich die Unterscheidung kompatibel zu Python-Typing hinzufügen lässt und ob sie in die datetime-Bibliothek gehört; für den Abschluss müssten diese Designfragen geklärt und ein abgestimmter Umfang definiert werden.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
developer-experience
Issue-Typ
Feature
Schwierigkeit
5/5
Geschätzter Aufwand
Über eine Woche
Aktivitätsstatus
Veraltet
Klarheit
Muss geklärt werden
Anfängerfreundlichkeit
25/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.