This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: datetime: min date (0001-01-01 00:00:00) can't be converted to local timestamp
Type: behavior Stage: test needed
Components: Extension Modules Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: belopolsky Nosy List: Dave Johansen, andrei.avk, belopolsky, chris.jerdonek, delete_me, vstinner
Priority: normal Keywords:

Created on 2017-08-15 17:26 by Dave Johansen, last changed 2022-04-11 14:58 by admin.

Messages (13)
msg300303 - (view) Author: Dave Johansen (Dave Johansen) Date: 2017-08-15 17:26
This worked in Python 3.6.0 and before:
```
from datetime import datetime
d = datetime(1, 1, 1, 0, 0, 0)
d.timestamp()
```

The error output is:
```
ValueError: year 0 is out of range
```

But it used to return `-62135658000.0`.

Appears to be related to https://bugs.python.org/issue29921
msg300338 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017-08-16 08:58
This error is a side effect of the implementation of the PEP 495. In your timezone, datetime(1, 1, 1, 0, 0, 0).timestamp() creates an internal timestamp with year=0. The problem is that the internal function ymd_to_ord() doesn't support year=0:

    /* This is incorrect if year <= 0; we really want the floor
     * here.  But so long as MINYEAR is 1, the smallest year this
     * can see is 1.
     */
    assert (year >= 1);

> This worked in Python 3.6.0 and before: (...)

The question is if the result was correct before?
msg300339 - (view) Author: STINNER Victor (vstinner) * (Python committer) Date: 2017-08-16 09:01
Note: My change was the commit 5b804b2fb0eaa2bacb4afcfe4cfa85b31475e87f which prevented a crash, bpo-29100.
msg300389 - (view) Author: Dave Johansen (Dave Johansen) Date: 2017-08-16 21:13
That's a valid `datetime` (i.e. within the min and max values) and `tzinfo` is `None` so I think it's completely reasonable to assume that `timestamp()` will return the correct value.
msg300390 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2017-08-16 21:44
The question is whether -62135658000.0 is the "correct" or even meaningful value:

>>> datetime.utcfromtimestamp(-62135658000.0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: year is out of range

(I ran the above in Python 2.7 to avoid any 3.x datetime innovations.)

I don't see much of a value in allowing datetime.timestamp() to produce values that correspond to out of bounds datetimes in UTC.  In most cases this will only result in error later on in the program, or worse an error passing undetected.

What is the use case for datetime.min.timestamp()?  I suspect OP encountered this issue in an overly aggressive edge case testing and not in a real user scenario.
msg300391 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2017-08-16 21:57
On the second thought, a reasonable design can use datetime.min/max as placeholders for unknown times far in the past/future compensating for the lack datetime ±inf.  In this use case, it may be annoying to see errors from timestamp() instead of some ridiculously large values.

I am -0 on making this change.  If anyone is motivated to produce a patch - I'll review it, but I am not going to write it myself.

I am marking this "tests needed" because we need a test complete with setting an appropriate timezone that demonstrates this issue.
msg300392 - (view) Author: Dave Johansen (Dave Johansen) Date: 2017-08-16 22:02
The use case was parsing user input of ISO 8601 date strings and converting them to UNIX epochs.  The input "0001-01-01T00:00:00" is valid, parses to a valid `datetime` and it seems like a reasonable expectation that all of the functions should work on a valid `datetime` (especially when they worked in earlier versions of Python).
msg300393 - (view) Author: Dave Johansen (Dave Johansen) Date: 2017-08-16 22:30
Ok, so I understand the issue now. `timestamp()` for naive datetime instances applies the local timezone offset ( https://docs.python.org/3.6/library/datetime.html#datetime.datetime.timestamp ). This is surprising because naive datetime instances usually are just that and don't make assumptions the timezone, but I guess that the behavior is documented and I was just unaware of it. It still seems like this shouldn't give an error (especially when the timezone of the local machine is UTC), but I guess that it is what it is.
msg300394 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2017-08-16 22:35
It your use case, the input "0001-01-01T00:00:00" was in what timezone?

I suspect what you want is 

>>> datetime.min.replace(tzinfo=timezone.utc).timestamp()
-62135596800.0

And not -62135658000.0.  If you work with naive datetimes and just want to roundtrip between datetimes and numbers, you should not use a timezone-dependent conversion - attach UTC timezone before calling .timestamp() as shown above.  You probably don't want your program to produce different numbers for the same "0001-01-01T00:00:00" input depending on where it is run.
msg300395 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2017-08-16 22:47
BTW, I was originally against introducing .timestamp() method and this issue illustrates why it is problematic: people use it without understanding what it does.  If you want the distance between datetime.min and datetime(1970,1,1) in seconds - compute it explicitly:

>>> (datetime.min - datetime(1970,1,1)) / timedelta(0, 1)
-62135596800.0

If you don't want to loose precision is roundtriping between datetimes and numbers - use microseconds:

>>> (datetime.min - datetime(1970,1,1)) // datetime.resolution
-62135596800000000

It is perfectly fine to work with naive datetimes and ignore the timezone issues when all your timestamps are in one timezones, but if you choose to ignore timezones - don't use .timestamp().
msg300396 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2017-08-16 23:00
> It still seems like this shouldn't give an error (especially when the timezone of the local machine is UTC)

This is the part where I agree with you.  I suspect the error in the UTC case is an artifact of PEP 495 fold calculations that require probing times ±24 hours around the time being converted.  For the times before timezones were invented (late 1800s?) we can safely assume that fold=0 always.  To be safe, I would set the fold cut-off at the year 1000.
msg385937 - (view) Author: delete me (delete_me) Date: 2021-01-29 20:56
I encountered this issue in Python 3.8. I consider it to be a bug because it's an instance of a class-defined constant (datetime.min) not working with a method of that class (timestamp) when all other values that the class could take work with the method AND the constant works with all of the class' other methods. And it has practical implications: As belopolsky said above, "a reasonable design can use datetime.min/max as placeholders for unknown times far in the past/future compensating for the lack [of] datetime ±inf."

Since datetime.min lies so close to the edges of datetime's value-space, perhaps it should be made offset-aware and placed in UTC so that it stops breaking timestamp() when the user is in the wrong timezone. Alternatively, there could be a note in the documentation for datetime.timestamp() about this edge case. Assuming similarly bizarre behavior happens at datetime.max for one or more datetime methods (and for consistency's sake), it should probably be put in UTC as well.
msg397159 - (view) Author: Andrei Kulakov (andrei.avk) * (Python triager) Date: 2021-07-08 18:00
Perhaps min and max should be moved 24 hours up and down respectively? This would make their names more accurately reflect valid range of the class and also allow them to be used as effective -inf +inf.
History
Date User Action Args
2022-04-11 14:58:50adminsetgithub: 75395
2021-07-08 18:00:58andrei.avksetnosy: + andrei.avk
messages: + msg397159
2021-02-01 01:18:02chris.jerdoneksetnosy: + chris.jerdonek
2021-01-29 20:56:28delete_mesetnosy: + delete_me

messages: + msg385937
versions: + Python 3.8, - Python 3.6
2017-08-16 23:00:16belopolskysetmessages: + msg300396
2017-08-16 22:47:55belopolskysetmessages: + msg300395
2017-08-16 22:35:51belopolskysetmessages: + msg300394
2017-08-16 22:30:21Dave Johansensetmessages: + msg300393
2017-08-16 22:02:30Dave Johansensetmessages: + msg300392
2017-08-16 21:57:10belopolskysetmessages: + msg300391
components: + Extension Modules
stage: test needed
2017-08-16 21:44:01belopolskysetmessages: + msg300390
2017-08-16 21:13:12Dave Johansensetmessages: + msg300389
2017-08-16 09:01:34vstinnersetmessages: + msg300339
2017-08-16 09:01:02vstinnersettitle: min date can't be converted to timestamp -> datetime: min date (0001-01-01 00:00:00) can't be converted to local timestamp
2017-08-16 08:58:59vstinnersetassignee: vstinner -> belopolsky

messages: + msg300338
nosy: + belopolsky
2017-08-16 04:43:40rhettingersetassignee: vstinner

nosy: + vstinner
2017-08-15 17:26:32Dave Johansencreate