Skip to content

Commit a01fb67

Browse files
committed
Add st_birthtime_ns and deprecate st_ctime instead of changing it
1 parent 00460d8 commit a01fb67

File tree

3 files changed

+85
-41
lines changed

3 files changed

+85
-41
lines changed

Doc/library/os.rst

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2791,8 +2791,10 @@ features:
27912791
for :class:`bytes` paths on Windows.
27922792

27932793
.. versionchanged:: 3.12
2794-
The ``st_ctime`` attributes of a stat result are now set to zero, rather
2795-
than being incorrectly set to the creation time of the file.
2794+
The ``st_ctime`` attribute of a stat result is deprecated on Windows.
2795+
The file creation time is properly available as ``st_birthtime``, and
2796+
in the future ``st_ctime`` may be changed to return zero or the
2797+
metadata change time, if available.
27962798

27972799

27982800
.. function:: stat(path, *, dir_fd=None, follow_symlinks=True)
@@ -2911,6 +2913,11 @@ features:
29112913

29122914
Time of most recent metadata change expressed in seconds.
29132915

2916+
.. versionchanged:: 3.12
2917+
``st_ctime`` is deprecated on Windows. Use ``st_birthtime`` for
2918+
the file creation time. In the future, ``st_ctime`` will contain
2919+
the time of the most recent metadata change, as for other platforms.
2920+
29142921
.. attribute:: st_atime_ns
29152922

29162923
Time of most recent access expressed in nanoseconds as an integer.
@@ -2925,23 +2932,45 @@ features:
29252932
Time of most recent metadata change expressed in nanoseconds as an
29262933
integer.
29272934

2935+
.. versionchanged:: 3.12
2936+
``st_ctime_ns`` is deprecated on Windows. Use ``st_birthtime_ns``
2937+
for the file creation time. In the future, ``st_ctime`` will contain
2938+
the time of the most recent metadata change, as for other platforms.
2939+
2940+
.. attribute:: st_birthtime
2941+
2942+
Time of file creation expressed in seconds. This attribute is not
2943+
always available, and may raise :exc:`AttributeError`.
2944+
2945+
.. versionchanged:: 3.12
2946+
``st_birthtime`` is now available on Windows.
2947+
2948+
.. attribute:: st_birthtime_ns
2949+
2950+
Time of file creation expressed in nanoseconds as an integer.
2951+
This attribute is not always available, and may raise
2952+
:exc:`AttributeError`.
2953+
2954+
.. versionadded:: 3.12
2955+
29282956
.. note::
29292957

29302958
The exact meaning and resolution of the :attr:`st_atime`,
2931-
:attr:`st_mtime`, and :attr:`st_ctime` attributes depend on the operating
2932-
system and the file system. For example, on Windows systems using the FAT
2933-
or FAT32 file systems, :attr:`st_mtime` has 2-second resolution, and
2934-
:attr:`st_atime` has only 1-day resolution. See your operating system
2935-
documentation for details.
2959+
:attr:`st_mtime`, :attr:`st_ctime` and :attr:`st_birthtime` attributes
2960+
depend on the operating system and the file system. For example, on
2961+
Windows systems using the FAT32 file systems, :attr:`st_mtime` has
2962+
2-second resolution, and :attr:`st_atime` has only 1-day resolution.
2963+
See your operating system documentation for details.
29362964

29372965
Similarly, although :attr:`st_atime_ns`, :attr:`st_mtime_ns`,
2938-
and :attr:`st_ctime_ns` are always expressed in nanoseconds, many
2939-
systems do not provide nanosecond precision. On systems that do
2940-
provide nanosecond precision, the floating-point object used to
2941-
store :attr:`st_atime`, :attr:`st_mtime`, and :attr:`st_ctime`
2942-
cannot preserve all of it, and as such will be slightly inexact.
2943-
If you need the exact timestamps you should always use
2944-
:attr:`st_atime_ns`, :attr:`st_mtime_ns`, and :attr:`st_ctime_ns`.
2966+
:attr:`st_ctime_ns` and :attr:`st_birthtime_ns` are always expressed in
2967+
nanoseconds, many systems do not provide nanosecond precision. On
2968+
systems that do provide nanosecond precision, the floating-point object
2969+
used to store :attr:`st_atime`, :attr:`st_mtime`, :attr:`st_ctime` and
2970+
:attr:`st_birthtime` cannot preserve all of it, and as such will be
2971+
slightly inexact. If you need the exact timestamps you should always use
2972+
:attr:`st_atime_ns`, :attr:`st_mtime_ns`, :attr:`st_ctime_ns` and
2973+
:attr:`st_birthtime_ns`.
29452974

29462975
On some Unix systems (such as Linux), the following attributes may also be
29472976
available:
@@ -2971,10 +3000,6 @@ features:
29713000

29723001
File generation number.
29733002

2974-
.. attribute:: st_birthtime
2975-
2976-
Time of file creation. This is also available on Windows.
2977-
29783003
On Solaris and derivatives, the following attributes may also be
29793004
available:
29803005

@@ -2997,8 +3022,7 @@ features:
29973022

29983023
File type.
29993024

3000-
On Windows systems, as well as ``st_birthtime`` above, the following
3001-
attributes are also available:
3025+
On Windows systems, the following attributes are also available:
30023026

30033027
.. attribute:: st_file_attributes
30043028

@@ -3049,9 +3073,10 @@ features:
30493073
as appropriate.
30503074

30513075
.. versionchanged:: 3.12
3052-
On Windows, :attr:`st_ctime` now also contains the last metadata
3053-
change time, for consistency with other platforms.
3054-
Use :attr:`st_birthtime` for the creation time when available.
3076+
On Windows, :attr:`st_ctime` is deprecated. Eventually, it will
3077+
contain the last metadata change time, for consistency with other
3078+
platforms, but for now still contains creation time.
3079+
Use :attr:`st_birthtime` for the creation time.
30553080

30563081
.. versionchanged:: 3.12
30573082
On Windows, :attr:`st_ino` may now be up to 128 bits, depending

Modules/posixmodule.c

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2071,6 +2071,10 @@ win32_xstat(const wchar_t *path, struct _Py_stat_struct *result, BOOL traverse)
20712071
setting it to a POSIX error. Callers should use GetLastError. */
20722072
int code = win32_xstat_impl(path, result, traverse);
20732073
errno = 0;
2074+
2075+
/* ctime is only deprecated from 3.12, so we copy birthtime across */
2076+
result->st_ctime = result->st_birthtime;
2077+
result->st_ctime_nsec = result->st_birthtime_nsec;
20742078
return code;
20752079
}
20762080
/* About the following functions: win32_lstat_w, win32_stat, win32_stat_w
@@ -2144,6 +2148,9 @@ static PyStructSequence_Field stat_result_fields[] = {
21442148
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS)
21452149
{"st_birthtime", "time of creation"},
21462150
#endif
2151+
#ifdef MS_WINDOWS
2152+
{"st_birthtime_ns", "time of creation in nanoseconds"},
2153+
#endif
21472154
#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES
21482155
{"st_file_attributes", "Windows file attribute bits"},
21492156
#endif
@@ -2192,10 +2199,16 @@ static PyStructSequence_Field stat_result_fields[] = {
21922199
#define ST_BIRTHTIME_IDX ST_GEN_IDX
21932200
#endif
21942201

2202+
#ifdef MS_WINDOWS
2203+
#define ST_BIRTHTIME_NS_IDX (ST_BIRTHTIME_IDX+1)
2204+
#else
2205+
#define ST_BIRTHTIME_NS_IDX ST_BIRTHTIME_IDX
2206+
#endif
2207+
21952208
#if defined(HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES) || defined(MS_WINDOWS)
2196-
#define ST_FILE_ATTRIBUTES_IDX (ST_BIRTHTIME_IDX+1)
2209+
#define ST_FILE_ATTRIBUTES_IDX (ST_BIRTHTIME_NS_IDX+1)
21972210
#else
2198-
#define ST_FILE_ATTRIBUTES_IDX ST_BIRTHTIME_IDX
2211+
#define ST_FILE_ATTRIBUTES_IDX ST_BIRTHTIME_NS_IDX
21992212
#endif
22002213

22012214
#ifdef HAVE_STRUCT_STAT_ST_FSTYPE
@@ -2364,7 +2377,7 @@ _posix_free(void *module)
23642377
}
23652378

23662379
static void
2367-
fill_time(PyObject *module, PyObject *v, int index, time_t sec, unsigned long nsec)
2380+
fill_time(PyObject *module, PyObject *v, int s_index, int f_index, int ns_index, time_t sec, unsigned long nsec)
23682381
{
23692382
PyObject *s = _PyLong_FromTime_t(sec);
23702383
PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec);
@@ -2388,12 +2401,18 @@ fill_time(PyObject *module, PyObject *v, int index, time_t sec, unsigned long ns
23882401
goto exit;
23892402
}
23902403

2391-
PyStructSequence_SET_ITEM(v, index, s);
2392-
PyStructSequence_SET_ITEM(v, index+3, float_s);
2393-
PyStructSequence_SET_ITEM(v, index+6, ns_total);
2394-
s = NULL;
2395-
float_s = NULL;
2396-
ns_total = NULL;
2404+
if (s_index >= 0) {
2405+
PyStructSequence_SET_ITEM(v, s_index, s);
2406+
s = NULL;
2407+
}
2408+
if (f_index >= 0) {
2409+
PyStructSequence_SET_ITEM(v, f_index, float_s);
2410+
float_s = NULL;
2411+
}
2412+
if (ns_index >= 0) {
2413+
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
2414+
ns_total = NULL;
2415+
}
23972416
exit:
23982417
Py_XDECREF(s);
23992418
Py_XDECREF(ns_fractional);
@@ -2477,9 +2496,9 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)
24772496
#else
24782497
ansec = mnsec = cnsec = 0;
24792498
#endif
2480-
fill_time(module, v, 7, st->st_atime, ansec);
2481-
fill_time(module, v, 8, st->st_mtime, mnsec);
2482-
fill_time(module, v, 9, st->st_ctime, cnsec);
2499+
fill_time(module, v, 7, 10, 13, st->st_atime, ansec);
2500+
fill_time(module, v, 8, 11, 14, st->st_mtime, mnsec);
2501+
fill_time(module, v, 9, 12, 15, st->st_ctime, cnsec);
24832502

24842503
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
24852504
PyStructSequence_SET_ITEM(v, ST_BLKSIZE_IDX,
@@ -2497,14 +2516,12 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)
24972516
PyStructSequence_SET_ITEM(v, ST_GEN_IDX,
24982517
PyLong_FromLong((long)st->st_gen));
24992518
#endif
2500-
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS)
2519+
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME)
25012520
{
25022521
PyObject *val;
25032522
unsigned long bsec,bnsec;
25042523
bsec = (long)st->st_birthtime;
2505-
#ifdef MS_WINDOWS
2506-
bnsec = st->st_birthtime_nsec;
2507-
#elif defined(HAVE_STAT_TV_NSEC2)
2524+
#ifdef HAVE_STAT_TV_NSEC2
25082525
bnsec = st->st_birthtimespec.tv_nsec;
25092526
#else
25102527
bnsec = 0;
@@ -2513,6 +2530,9 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)
25132530
PyStructSequence_SET_ITEM(v, ST_BIRTHTIME_IDX,
25142531
val);
25152532
}
2533+
#elif defined(MS_WINDOWS)
2534+
fill_time(module, v, ST_BIRTHTIME_IDX, -1, ST_BIRTHTIME_NS_IDX,
2535+
st->st_birthtime, st->st_birthtime_nsec);
25162536
#endif
25172537
#ifdef HAVE_STRUCT_STAT_ST_FLAGS
25182538
PyStructSequence_SET_ITEM(v, ST_FLAGS_IDX,

Python/fileutils.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,15 +1096,14 @@ _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag,
10961096
result->st_size = (((__int64)info->nFileSizeHigh)<<32) + info->nFileSizeLow;
10971097
result->st_dev = id_info ? id_info->VolumeSerialNumber : info->dwVolumeSerialNumber;
10981098
result->st_rdev = 0;
1099+
/* st_ctime is deprecated, but we preserve the legacy value in our caller, not here */
10991100
if (basic_info) {
11001101
LARGE_INTEGER_to_time_t_nsec(&basic_info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec);
11011102
LARGE_INTEGER_to_time_t_nsec(&basic_info->ChangeTime, &result->st_ctime, &result->st_ctime_nsec);
11021103
LARGE_INTEGER_to_time_t_nsec(&basic_info->LastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
11031104
LARGE_INTEGER_to_time_t_nsec(&basic_info->LastAccessTime, &result->st_atime, &result->st_atime_nsec);
11041105
} else {
11051106
FILE_TIME_to_time_t_nsec(&info->ftCreationTime, &result->st_birthtime, &result->st_birthtime_nsec);
1106-
/* We leave ctime as zero because we do not have it without FILE_BASIC_INFO.
1107-
Our callers will replace it with birthtime if they want legacy behaviour */
11081107
FILE_TIME_to_time_t_nsec(&info->ftLastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
11091108
FILE_TIME_to_time_t_nsec(&info->ftLastAccessTime, &result->st_atime, &result->st_atime_nsec);
11101109
}

0 commit comments

Comments
 (0)