'Python crash on Windows with a datetime close to the epoch
Python 3.7 on Windows 10:
>>> from datetime import datetime
>>> datetime.fromtimestamp(0)
datetime.datetime(1970, 1, 1, 0, 0)
>>> datetime.fromtimestamp(0).timestamp()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument
>>> datetime.fromtimestamp(3600 * 3).timestamp()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument
>>> datetime.fromtimestamp(360000).timestamp()
360000.0
I understand that on Windows, the range for timestamps is limited to 1970 – 2038 due to 32-bit integer size. But it's really strange that it crashes on a timestamp that should still be allowed. FWIW, I am in UTC+2, so if there is some issue with the time zone, I expected the 3600 * 3 (3 hour) timestamp to work. Can I make this work somehow or should I just accept that very low timestamps are not going to work?
Solution 1:[1]
Ok I did some digging in the source code of the Python. The problem lies within the function _PyTime_localtime. This calls the localtime_s function takes 2 arguments time_t t and struct tm *tm. Where t is a time_t object to convert and tm the resulting time structure. When you pass 0 as time_t, which is perfectly valid, the resulting structure has the field tm_hour set to 1 on my machine. Also there is other code for Non-Windows variants, which calls localtime_r in stead.
Now the problem gets moved to the internally used function utc_to_seconds, which takes the time structure (split into arguments like so: int year, int month, int day, int hour, int minute, int second). Now for the year, month and day there is no problem, it gets converted to an ordinal (which is the correct ordinal btw). But then the function has the following last line:
return ((ordinal * 24 + hour) * 60 + minute) * 60 + second;
Where EPOCH is supposed to return 62135683200 there, but due to this extra hour we get 62135686800.
This all comes together in the internal function local_to_seconds
long long t, a, b, u1, u2, t1, t2, lt;
t = utc_to_seconds(year, month, day, hour, minute, second);
/* Our goal is to solve t = local(u) for u. */
lt = local(t);
if (lt == -1)
return -1;
a = lt - t;
u1 = t - a;
t1 = local(u1);
Where t = 62135683200 and lt = 62135686800. We end up with u1 = -3600 which results in the invalid parameter.
So to conclude: The problem is when you call timestamp. I'm not exactly sure what the solution would be to fix it in the C-code, but it definitely looks like a bug I guess.
Solution 2:[2]
I had the same issue trying to squeeze a UNIX timestamp from a datetime object... the crux of the issue was detailed by Neijwiert, but all you need to do is to assign a time zone.
In my case, I was parsing a string and needed to convert to a unix timestamp:
def string_to_unix_time(dt_string):
# get the time zone (EST = UTC?05:00)
tz = timezone(offset=-timedelta(hours=5))
# parse out the string...
date, time = dt_string.strip().split()
month, day, year = date.split('/')
hour, minute, second = time.split(':')
# make datetime object...
dt = datetime(
year=int(year),
month=int(month),
day=int(day),
hour=int(hour),
minute=int(minute),
second=int(second),
tzinfo=tz,
)
return dt.timestamp()
Solution 3:[3]
works nicely even on Windows, if you set the tzinfo, i.e. use aware datetime:
>>> from datetime import datetime, timezone
>>> datetime.fromtimestamp(0, tz=timezone.utc).timestamp()
0
(Windows 10, Python 3.9.12)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Neijwiert |
| Solution 2 | Todd Jacobus |
| Solution 3 |
