Description
Feature or enhancement
In order to make it possible to interoperate with C structs that contain time_t
, add a new type c_time_t
to the ctypes
module that represents a C time_t
.
Pitch
Imagine having a struct in some C library we want to bind using ctypes
:
struct Something {
int foo;
time_t bar;
const char *baz;
};
This is quite cumbersome to do right now, as the size of time_t
depends on lots of factors:
time_t
is special due to the year 2038 problem. Historically on UNIX, time_t
was 32-bit. For 64-bit platforms, time_t
is usually 64-bit, avoiding the problem. In 2020, some changes have landed in Linux 5.6 to enable 64-bit time_t support in 32-bit Linux. glibc also has some documentation on 64-bit time_t. According to PC/pyconfig.h
in CPython, "MS VS2005 changes time_t to a 64-bit type on all platforms".
Also, Lib/test/test_time.py
says (in test_insane_timestamps()
): "It's possible that some platform maps time_t to double" (I don't know which platform has this, but it might make sense to also take care of that).
Based on my research, something like this (likely wrong, incomplete or inaccurate - definitely untested) needs to be done if one wants to have a ctypes
time_t
type right now:
import platform
import ctypes
if platform.system() == 'Windows':
# Assume MSVC(?) - what about mingw/clang?
time_t = ctypes.c_int64
elif ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_int64):
# 64-bit platform of any kind - assume 64-bit time_t(?)
time_t = ctypes.c_int64
else:
# assume some kind of 32-bit platform(?)
time_t = ctypes.c_int32
# ... use time_t here ...
Adding a new c_time_t
type in ctypes
that is the same as one of (depending on how the host system defines it): c_int32
, c_uint32
, c_int64
, c_uint64
would fix this issues, and allow defining the above struct simply as:
class Something(ctypes.Structure):
_fields_ = [
('foo', ctypes.c_int),
('bar', ctypes.c_time_t),
('baz', ctypes.c_char_p),
]
The CPython codebase already defines SIZEOF_TIME_T
grep
'ing for the word time_t
in the current CPython Git head:
% grep '\<time_t\>' **/*.py
Lib/lib2to3/tests/data/infinite_recursion.py:time_t = __darwin_time_t
Lib/lib2to3/tests/data/infinite_recursion.py: ('check_time', time_t),
Lib/lib2to3/tests/data/infinite_recursion.py: ('tv_sec', time_t),
Lib/lib2to3/tests/data/infinite_recursion.py: 'OSUnknownByteOrder', 'BN_MONT_CTX', 'ASN1_NULL', 'time_t',
Lib/test/datetimetester.py: # It's possible that some platform maps time_t to double,
Lib/test/datetimetester.py: # converting a Python int to C time_t can raise a
Lib/test/datetimetester.py: # converting a Python int to C time_t can raise a
Lib/test/datetimetester.py: # It's possible that some platform maps time_t to double,
Lib/test/datetimetester.py: # It's possible that some platform maps time_t to double,
Lib/test/datetimetester.py: # System support for times around the end of 32-bit time_t
Lib/test/test_os.py: # or utime(time_t)
Lib/test/test_time.py: # It's possible that some platform maps time_t to double,
Lib/test/test_time.py: for time_t in (-1, 2**30, 2**33, 2**60):
Lib/test/test_time.py: time.localtime(time_t)
Lib/test/test_time.py: self.skipTest("need 64-bit time_t")
Lib/test/test_time.py: invalid_time_t = time_t
Lib/test/test_time.py: self.skipTest("unable to find an invalid time_t value")
Lib/test/test_time.py: # time_t is a 32-bit or 64-bit signed integer
It seems like some parts of this check for OverflowError
with time.localtime()
to distinguish between 64-bit and 32-bit time_t
.
If nothing else, exposing SIZEOF_TIME_T
(seems like it's either 4 or 8 bytes for most (all?) platforms CPython supports) might help a lot in defining proper structs containing time_t
for interoperability.
Also the Y2038 Wikipedia article describes some platforms that deal with time_t
specially:
- Starting with NetBSD version 6.0 (released in October 2012), the NetBSD operating system uses a 64-bit time_t for both 32-bit and 64-bit architectures.
- OpenBSD since version 5.5, released in May 2014, also uses a 64-bit time_t for both 32-bit and 64-bit architectures.
- Linux originally used a 64-bit time_t for 64-bit architectures only; the pure 32-bit ABI was not changed due to backward compatibility. Starting with version 5.6, 64-bit time_t is supported on 32-bit architectures, too. This was done primarily for the sake of embedded Linux systems.
- FreeBSD uses 64-bit time_t for all 32-bit and 64-bit architectures except 32-bit i386, which uses signed 32-bit time_t instead.
- The x32 ABI for Linux (which defines an environment for programs with 32-bit addresses but running the processor in 64-bit mode) uses a 64-bit time_t.
So supporting time_t properly seems to be a bit more involved than "just" checking the pointer size, especially for the BSDs.
Previous discussion
This came up while trying to create ctypes-based bindings for libgpod
(which defines structs with time_t
) here: gpodder/gpodder#1289