Getting type/size of `time_t` using ctypes
Question:
I’m accessing a C struct which contains some time_t
fields using python ctypes module.
Given its non completely portable nature, I cannot define these fields statically as of c_int
or c_long
type.
How can I define them to make my code portable?
Example C struct definition:
#import <sys/types.h>
#import <time.h>
typedef struct my_struct {
time_t timestap;
uint16_t code;
};
Respective python ctypes structure:
from ctypes import *
c_time = ? # What do I have to put here?
class MyStruct(Structure):
_fields_ = [
('timestamp', c_time),
('code', c_int16),
]
Answers:
Your best bet is by introspecting the system your script runs on and making a best bet on which integral type to use. Something like,
if sys.platform == 'win32':
time_t = ctypes.c_uint64
# ...
The bottom line is that time_t
is not defined in the standard. It’s up to the OS and compiler. So your definition of time_t
in your Python script depends on the DLL/so you are interacting with.
Starting with Python 3.12 (still in development at the time of writing this, planned for release in October 2023), ctypes.c_time_t
can be used for this, in your example:
# This requires Python 3.12, which adds ctypes.c_time_t
from ctypes import *
class MyStruct(Structure):
_fields_ = [
('timestamp', c_time_t),
('code', c_int16),
]
I had a use case for this last year and the patch was accepted.
If you don’t have Python 3.12 yet, something like this could do the trick, although it’s not covering all cases (from the PR/issue above):
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 ...
This should be fine for Windows (MSVC >= 2005), macOS (x86_64, aarch64) and Linux (arm32, aarch64, x86, x86_64), but might need tweaking for the BSDs on some CPUs.
Once Python 3.12 is released and ctypes.c_time_t
is a thing, you can just use that.
For example, this is how gPodder’s libgpod_ctypes.py
tries to use ctypes.c_time_t
, but falls back to some simple heuristics on older systems:
# ctypes.c_time_t will be available in Python 3.12 onwards
# See also: https://github.com/python/cpython/pull/92870
if hasattr(ctypes, 'c_time_t'):
time_t = ctypes.c_time_t
else:
# See also: https://github.com/python/cpython/issues/92869
if ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_int64):
time_t = ctypes.c_int64
else:
# On 32-bit systems, time_t is historically 32-bit, but due to Y2K38
# there have been efforts to establish 64-bit time_t on 32-bit Linux:
# https://linux.slashdot.org/story/20/02/15/0247201/linux-is-ready-for-the-end-of-time
# https://www.gnu.org/software/libc/manual/html_node/64_002dbit-time-symbol-handling.html
time_t = ctypes.c_int32
I’m accessing a C struct which contains some time_t
fields using python ctypes module.
Given its non completely portable nature, I cannot define these fields statically as of c_int
or c_long
type.
How can I define them to make my code portable?
Example C struct definition:
#import <sys/types.h>
#import <time.h>
typedef struct my_struct {
time_t timestap;
uint16_t code;
};
Respective python ctypes structure:
from ctypes import *
c_time = ? # What do I have to put here?
class MyStruct(Structure):
_fields_ = [
('timestamp', c_time),
('code', c_int16),
]
Your best bet is by introspecting the system your script runs on and making a best bet on which integral type to use. Something like,
if sys.platform == 'win32':
time_t = ctypes.c_uint64
# ...
The bottom line is that time_t
is not defined in the standard. It’s up to the OS and compiler. So your definition of time_t
in your Python script depends on the DLL/so you are interacting with.
Starting with Python 3.12 (still in development at the time of writing this, planned for release in October 2023), ctypes.c_time_t
can be used for this, in your example:
# This requires Python 3.12, which adds ctypes.c_time_t
from ctypes import *
class MyStruct(Structure):
_fields_ = [
('timestamp', c_time_t),
('code', c_int16),
]
I had a use case for this last year and the patch was accepted.
If you don’t have Python 3.12 yet, something like this could do the trick, although it’s not covering all cases (from the PR/issue above):
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 ...
This should be fine for Windows (MSVC >= 2005), macOS (x86_64, aarch64) and Linux (arm32, aarch64, x86, x86_64), but might need tweaking for the BSDs on some CPUs.
Once Python 3.12 is released and ctypes.c_time_t
is a thing, you can just use that.
For example, this is how gPodder’s libgpod_ctypes.py
tries to use ctypes.c_time_t
, but falls back to some simple heuristics on older systems:
# ctypes.c_time_t will be available in Python 3.12 onwards
# See also: https://github.com/python/cpython/pull/92870
if hasattr(ctypes, 'c_time_t'):
time_t = ctypes.c_time_t
else:
# See also: https://github.com/python/cpython/issues/92869
if ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_int64):
time_t = ctypes.c_int64
else:
# On 32-bit systems, time_t is historically 32-bit, but due to Y2K38
# there have been efforts to establish 64-bit time_t on 32-bit Linux:
# https://linux.slashdot.org/story/20/02/15/0247201/linux-is-ready-for-the-end-of-time
# https://www.gnu.org/software/libc/manual/html_node/64_002dbit-time-symbol-handling.html
time_t = ctypes.c_int32