Skip to content

datetime.astimezone(None) does not set fold #103982

Open
@pganssle

Description

@pganssle

At work today someone presented this problem (run with TZ=America/Los_Angeles):

>>> from datetime import datetime, utc
>>> time_utc = datetime(2023, 11, 5, 9, 15, tzinfo=UTC)
>>> (time_local := time_utc.astimezone())
datetime.datetime(2023, 11, 5, 1, 15, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=57600), 'PST'))
>>> print(time_local)
2023-11-05 01:15:00-08:00
>>> time_local_naive = time_local.replace(tzinfo=None)
>>> print(time_local_naive.astimezone())
2023-11-05 01:15:00-07:00

I think this is supposed to be equivalent to:

  1. Create a time in UTC (time_utc)
  2. Convert it to local time with a fixed offset (time_local)
  3. Convert the local time with a fixed offset to a naïve local time.
  4. Convert naïve local time to a local time with a fixed offset.

The problem is that step 3 doesn't work properly because step 2 isn't setting fold=1. This is understandable, since the fixed offset zone doesn't need fold=1, but because time_utc.astimezone(None) creates a fixed-offset aware local time, we're not really providing a good way to go from an aware local time to a naïve local time (one that you can, e.g. do wall-time arithmetic on). It seems like the best way to go from aware → naïve local time is via .timestamp:

>>> (time_local_naive_ts := datetime.fromtimestamp(time_local.timestamp()))
datetime.datetime(2023, 11, 5, 1, 15, fold=1)
>>> print(time_local_naive_ts.astimezone())
2023-11-05 01:15:00-08:00

It's not especially obvious that this is the right way to do it. I think the right way forward here is one of the following options:

  1. Set fold on the result of .astimezone() so that .replace(tzinfo=None) gives you a "naïve local time".
  2. Add some mechanism to get a naïve local time from an aware datetime (in retrospect, it seems like maybe the current behavior should be the result of calling .astimezone with some LOCAL sentinel object, and .astimezone() should do what I'm asking for here, but it seems that ship has sailed). I guess this could either be a special sentinel or a new method like .asnaive() or .aslocal() or something.
  3. Both 1 and 2.

I'm leaning towards starting with 1 if there's no fundamental reason we can't do it that way.

@abalkin Any thoughts on this?

(Tangentially related: #83861)

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibPython modules in the Lib dirtype-bugAn unexpected behavior, bug, or error

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions