Skip to content

Commit 6fe20b3

Browse files
committed
Issue #14127: Add st_{cma}time_ns fields to os.stat() result object.
1 parent dd5aa36 commit 6fe20b3

6 files changed

Lines changed: 96 additions & 33 deletions

File tree

Doc/library/os.rst

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,8 +2011,8 @@ Files and Directories
20112011
Perform the equivalent of a :c:func:`stat` system call on the given path.
20122012
(This function follows symlinks; to stat a symlink use :func:`lstat`.)
20132013

2014-
The return value is an object whose attributes correspond to the members
2015-
of the :c:type:`stat` structure, namely:
2014+
The return value is an object whose attributes correspond roughly
2015+
to the members of the :c:type:`stat` structure, namely:
20162016

20172017
* :attr:`st_mode` - protection bits,
20182018
* :attr:`st_ino` - inode number,
@@ -2021,10 +2021,18 @@ Files and Directories
20212021
* :attr:`st_uid` - user id of owner,
20222022
* :attr:`st_gid` - group id of owner,
20232023
* :attr:`st_size` - size of file, in bytes,
2024-
* :attr:`st_atime` - time of most recent access,
2025-
* :attr:`st_mtime` - time of most recent content modification,
2026-
* :attr:`st_ctime` - platform dependent; time of most recent metadata change on
2027-
Unix, or the time of creation on Windows)
2024+
* :attr:`st_atime` - time of most recent access expressed in seconds,
2025+
* :attr:`st_mtime` - time of most recent content modification
2026+
expressed in seconds,
2027+
* :attr:`st_ctime` - platform dependent; time of most recent metadata
2028+
change on Unix, or the time of creation on Windows, expressed in seconds
2029+
* :attr:`st_atime_ns` - time of most recent access
2030+
expressed in nanoseconds as an integer,
2031+
* :attr:`st_mtime_ns` - time of most recent content modification
2032+
expressed in nanoseconds as an integer,
2033+
* :attr:`st_ctime_ns` - platform dependent; time of most recent metadata
2034+
change on Unix, or the time of creation on Windows,
2035+
expressed in nanoseconds as an integer
20282036

20292037
On some Unix systems (such as Linux), the following attributes may also be
20302038
available:
@@ -2054,6 +2062,14 @@ Files and Directories
20542062
or FAT32 file systems, :attr:`st_mtime` has 2-second resolution, and
20552063
:attr:`st_atime` has only 1-day resolution. See your operating system
20562064
documentation for details.
2065+
Similarly, although :attr:`st_atime_ns`, :attr:`st_mtime_ns`,
2066+
and :attr:`st_ctime_ns` are always expressed in nanoseconds, many
2067+
systems do not provide nanosecond precision. On systems that do
2068+
provide nanosecond precision, the floating-point object used to
2069+
store :attr:`st_atime`, :attr:`st_mtime`, and :attr:`st_ctime`
2070+
cannot preserve all of it, and as such will be slightly inexact.
2071+
If you need the exact timestamps you should always use
2072+
:attr:`st_atime_ns`, :attr:`st_mtime_ns`, and :attr:`st_ctime_ns`.
20572073

20582074
For backward compatibility, the return value of :func:`~os.stat` is also accessible
20592075
as a tuple of at least 10 integers giving the most important (and portable)
@@ -2081,6 +2097,10 @@ Files and Directories
20812097

20822098
Availability: Unix, Windows.
20832099

2100+
.. versionadded:: 3.3
2101+
The :attr:`st_atime_ns`, :attr:`st_mtime_ns`,
2102+
and :attr:`st_ctime_ns` members.
2103+
20842104

20852105
.. function:: stat_float_times([newvalue])
20862106

Include/pytime.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
4444
PyObject *obj,
4545
time_t *sec);
4646

47+
/* Convert a time_t to a PyLong. */
48+
PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
49+
time_t sec);
50+
4751
/* Convert a number of seconds, int or float, to a timeval structure.
4852
usec is in the range [0; 999999] and rounded towards zero.
4953
For example, -1.2 is converted to (-2, 800000). */

Lib/test/test_os.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,13 @@ def trunc(x): return x
191191
result[getattr(stat, name)])
192192
self.assertIn(attr, members)
193193

194+
# Make sure that the st_?time and st_?time_ns fields roughly agree
195+
# (they should always agree up to the tens-of-microseconds magnitude)
196+
for name in 'st_atime st_mtime st_ctime'.split():
197+
floaty = int(getattr(result, name) * 100000)
198+
nanosecondy = getattr(result, name + "_ns") // 10000
199+
self.assertEqual(floaty, nanosecondy)
200+
194201
try:
195202
result[200]
196203
self.fail("No exception thrown")

Modules/_testcapimodule.c

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2362,17 +2362,6 @@ run_in_subinterp(PyObject *self, PyObject *args)
23622362
return PyLong_FromLong(r);
23632363
}
23642364

2365-
static PyObject*
2366-
_PyLong_FromTime_t(time_t value)
2367-
{
2368-
#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG
2369-
return PyLong_FromLongLong(value);
2370-
#else
2371-
assert(sizeof(time_t) <= sizeof(long));
2372-
return PyLong_FromLong(value);
2373-
#endif
2374-
}
2375-
23762365
static PyObject *
23772366
test_pytime_object_to_time_t(PyObject *self, PyObject *args)
23782367
{

Modules/posixmodule.c

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,6 +1550,9 @@ static PyStructSequence_Field stat_result_fields[] = {
15501550
{"st_atime", "time of last access"},
15511551
{"st_mtime", "time of last modification"},
15521552
{"st_ctime", "time of last change"},
1553+
{"st_atime_ns", "time of last access in nanoseconds"},
1554+
{"st_mtime_ns", "time of last modification in nanoseconds"},
1555+
{"st_ctime_ns", "time of last change in nanoseconds"},
15531556
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
15541557
{"st_blksize", "blocksize for filesystem I/O"},
15551558
#endif
@@ -1572,9 +1575,9 @@ static PyStructSequence_Field stat_result_fields[] = {
15721575
};
15731576

15741577
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
1575-
#define ST_BLKSIZE_IDX 13
1578+
#define ST_BLKSIZE_IDX 16
15761579
#else
1577-
#define ST_BLKSIZE_IDX 12
1580+
#define ST_BLKSIZE_IDX 15
15781581
#endif
15791582

15801583
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
@@ -1726,25 +1729,50 @@ stat_float_times(PyObject* self, PyObject *args)
17261729
return Py_None;
17271730
}
17281731

1732+
static PyObject *billion = NULL;
1733+
17291734
static void
17301735
fill_time(PyObject *v, int index, time_t sec, unsigned long nsec)
17311736
{
1732-
PyObject *fval,*ival;
1733-
#if SIZEOF_TIME_T > SIZEOF_LONG
1734-
ival = PyLong_FromLongLong((PY_LONG_LONG)sec);
1735-
#else
1736-
ival = PyLong_FromLong((long)sec);
1737-
#endif
1738-
if (!ival)
1739-
return;
1737+
PyObject *s = _PyLong_FromTime_t(sec);
1738+
PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec);
1739+
PyObject *s_in_ns = NULL;
1740+
PyObject *ns_total = NULL;
1741+
PyObject *float_s = NULL;
1742+
1743+
if (!(s && ns_fractional))
1744+
goto exit;
1745+
1746+
s_in_ns = PyNumber_Multiply(s, billion);
1747+
if (!s_in_ns)
1748+
goto exit;
1749+
1750+
ns_total = PyNumber_Add(s_in_ns, ns_fractional);
1751+
if (!ns_total)
1752+
goto exit;
1753+
17401754
if (_stat_float_times) {
1741-
fval = PyFloat_FromDouble(sec + 1e-9*nsec);
1742-
} else {
1743-
fval = ival;
1744-
Py_INCREF(fval);
1755+
float_s = PyFloat_FromDouble(sec + 1e-9*nsec);
1756+
if (!float_s)
1757+
goto exit;
1758+
}
1759+
else {
1760+
float_s = s;
1761+
Py_INCREF(float_s);
17451762
}
1746-
PyStructSequence_SET_ITEM(v, index, ival);
1747-
PyStructSequence_SET_ITEM(v, index+3, fval);
1763+
1764+
PyStructSequence_SET_ITEM(v, index, s);
1765+
PyStructSequence_SET_ITEM(v, index+3, float_s);
1766+
PyStructSequence_SET_ITEM(v, index+6, ns_total);
1767+
s = NULL;
1768+
float_s = NULL;
1769+
ns_total = NULL;
1770+
exit:
1771+
Py_XDECREF(s);
1772+
Py_XDECREF(ns_fractional);
1773+
Py_XDECREF(s_in_ns);
1774+
Py_XDECREF(ns_total);
1775+
Py_XDECREF(float_s);
17481776
}
17491777

17501778
/* pack a system stat C structure into the Python stat tuple
@@ -11627,6 +11655,10 @@ INITFUNC(void)
1162711655

1162811656
PyModule_AddObject(m, "terminal_size", (PyObject*) &TerminalSizeType);
1162911657

11658+
billion = PyLong_FromLong(1000000000);
11659+
if (!billion)
11660+
return NULL;
11661+
1163011662
return m;
1163111663

1163211664
}

Python/pytime.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ _PyLong_AsTime_t(PyObject *obj)
9696
return (time_t)val;
9797
}
9898

99+
PyObject *
100+
_PyLong_FromTime_t(time_t t)
101+
{
102+
#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG
103+
return PyLong_FromLongLong((PY_LONG_LONG)t);
104+
#else
105+
assert(sizeof(time_t) <= sizeof(long));
106+
return PyLong_FromLong((long)t);
107+
#endif
108+
}
109+
99110
static int
100111
_PyTime_ObjectToDenominator(PyObject *obj, time_t *sec, long *numerator,
101112
double denominator)

0 commit comments

Comments
 (0)