OK, I've pondered this some more, and in the end I think it is a reasonable proposal.

Note that there are many subtleties in various other methods already; e.g. the distinction between timetuple() and utctimetuple(), or between fromtimestamp() and utcfromtimestamp(), is not as straightforward as their names might suggest. So I think it's better not to add more utc*() functions.

If I had to do the design over I might have made a deeper distinction between aware and naive datetimes (e.g. aware being a subclass of naive) but such a redesign is not on the table.

Another (mostly unrelated) observation about datetime: even an aware datetime does not really represent an instant in time, unless its timezone has a fixed offset. For example, suppose I create a datetime representing noon, June 3rd, US/Eastern time in 2020. My best guess is that this will correspond to the instant 16:00 in UTC on that date. But I can't really tell. Maybe by then the campaign against DST has finally succeeded, and noon on that date in US/Eastern is actually 17:00 UTC. But if I store this date in a database and read it back in the year 2020, I want it to be read back as noon US/Eastern (June 3rd 2020), not as "whatever time in US/Eastern corresponding to 16:00 UTC on that date". Now you may wonder why I prefer it this way, and I'll just let you guess -- but I need this for my use case. If your use case needs to record the instant, you should use UTC or some other fixed offset zone. (And yes, I fundamentally consider pytz's behavior here a bug, and datetime's behavior a feature.)

On Fri, Aug 28, 2015 at 4:29 PM, Alexander Belopolsky <alexander.belopolsky@gmail.com> wrote:
While PEP 495 is primarily about improving the tzinfo interface, it proposes one feature that will be visible to datetime users who don't write their own tzinfo implementations or use any from a third party.   This feature is described in the section "Conversion from Naive to Aware" [1] and will enable users to call astimezone() method on naive instances to convert them to aware.

The PEP proposes that naive datetimes should be presumed by astimezone() to be in system local timezone similarly to the way they are treated in the timestamp() method now.

This feature has been criticized because "Treating a naive datetime as being a local time is as senseless as treating it as a UTC time - it's arbitrary." [2]

The proposed changes to astimezone() will make it discriminate between the two arbitrary choices.   Converting a naive datetime to say UTC timezone assuming it is local will become

>>> dt.astimezone(timezone.utc)

but the same conversion assuming dt is already in UTC is done by

>>> dt.replace(tzinfo=timezone.utc)

The asymmetry is more visible in when the task is to imbue a naive datetime with an appropriate local fixed offset timezone:

>>> dt.astimezone()  # assuming dt is local


>>> dt.replace(tzinfo=timezone.utc).astimezone()  # assuming dt is UTC

As an alternative to an implicit local timezone, we can provide an explicit timezone.local implementation and tell users to write

>>> dt.replace(tzinfo=timezone.local).astimezone()  # assuming dt is local

making working with implicitly local and implicitly UTC naive instances equally inconvenient.

Furthermore, implicit local time zone logic is already implemented with respect to the target timezone: to convert to a local (fixed offset) timezone astimezone() is called without arguments, but any other timezone needs to be provided explicitly, including timezone.utc.

IMO, if nondiscrimination between between UTC and local is important, I would rather add utcastimezone() and utctimestamp() methods that will work like their namesakes but assume that the instances they are invoked on are in UTC.

Please discuss.

[1]: https://www.python.org/dev/peps/pep-0495/#conversion-from-naive-to-aware
[2]: https://mail.python.org/pipermail/datetime-sig/2015-August/000464.html

Datetime-SIG mailing list
The PSF Code of Conduct applies to this mailing list: https://www.python.org/psf/codeofconduct/

--Guido van Rossum (python.org/~guido)