diff --git a/Doc/library/os.rst b/Doc/library/os.rst --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1819,6 +1819,16 @@ :attr:`st_mtime` has 2-second resolution, and :attr:`st_atime` has only 1-day resolution. See your operating system documentation for details. + On all systems, the following high resolution timestamps are also available + although the resolution is dependent on the system: + + * :attr:`st_atim` - time of most recent access, + * :attr:`st_ctim` - platform dependent; time of most recent metadata change on + Unix, or the time of creation on Windows) + * :attr:`st_mtim` - time of most recent content modification + + Each timestamp is exposed as a tuple of integers ``(seconds, nanoseconds)``. + For backward compatibility, the return value of :func:`~os.stat` is also accessible as a tuple of at least 10 integers giving the most important (and portable) members of the :c:type:`stat` structure, in the order :attr:`st_mode`, diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -307,6 +307,17 @@ except TypeError: pass + # Check for nanosecond resolution fields + self.assertIn("st_atim", members) + self.assertIn("st_ctim", members) + self.assertIn("st_mtim", members) + self.assertEqual(result.st_atim[0], int(result.st_atime)) + self.assertIsInstance(result.st_atim[1], int) + self.assertEqual(result.st_ctim[0], int(result.st_ctime)) + self.assertIsInstance(result.st_ctim[1], int) + self.assertEqual(result.st_mtim[0], int(result.st_mtime)) + self.assertIsInstance(result.st_mtim[1], int) + def test_stat_attributes(self): self.check_stat_attributes(self.fname) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1430,6 +1430,9 @@ #ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME {"st_birthtime", "time of creation"}, #endif + {"st_atim", "high resolution time of last access"}, + {"st_ctim", "high resolution time of last modification"}, + {"st_mtim", "high resolution time of last change"}, {0} }; @@ -1469,6 +1472,10 @@ #define ST_BIRTHTIME_IDX ST_GEN_IDX #endif +#define ST_ATIM_IDX (ST_BIRTHTIME_IDX+1) +#define ST_CTIM_IDX (ST_ATIM_IDX+1) +#define ST_MTIM_IDX (ST_CTIM_IDX+1) + static PyStructSequence_Desc stat_result_desc = { "stat_result", /* name */ stat_result__doc__, /* doc */ @@ -1658,6 +1665,36 @@ fill_time(v, 8, st->st_mtime, mnsec); fill_time(v, 9, st->st_ctime, cnsec); +#ifdef HAVE_LARGEFILE_SUPPORT +#define _STAT_FMT "Lk" +#else +#define _STAT_FMT "kk" +#endif + + PyObject *atim = Py_BuildValue(_STAT_FMT, + st->st_atime, st->st_atim.tv_nsec); + if (atim == NULL) { + Py_DECREF(v); + return NULL; + } + PyStructSequence_SET_ITEM(v, ST_ATIM_IDX, atim); + + PyObject *ctim = Py_BuildValue(_STAT_FMT, + st->st_ctime, st->st_ctim.tv_nsec); + if (ctim == NULL) { + Py_DECREF(v); + return NULL; + } + PyStructSequence_SET_ITEM(v, ST_CTIM_IDX, ctim); + + PyObject *mtim = Py_BuildValue(_STAT_FMT, + st->st_mtime, st->st_mtim.tv_nsec); + if (mtim == NULL) { + Py_DECREF(v); + return NULL; + } + PyStructSequence_SET_ITEM(v, ST_MTIM_IDX, mtim); + #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE PyStructSequence_SET_ITEM(v, ST_BLKSIZE_IDX, PyLong_FromLong((long)st->st_blksize));