Kaydet (Commit) 76ad59b7 authored tarafından Larry Hastings's avatar Larry Hastings

Issue #14127: Add ns= parameter to utime, futimes, and lutimes.

Removed futimens as it is now redundant.
Changed shutil.copystat to use st_atime_ns and st_mtime_ns from os.stat
and ns= parameter to utime--it once again preserves exact metadata on Linux!
üst 3a7f7977
......@@ -934,13 +934,11 @@ as internal buffering of data.
.. versionadded:: 3.3
.. function:: futimes(fd[, times])
.. function:: futimes(fd[, times, *, ns=times])
Set the access and modified time of the file specified by the file
descriptor *fd* to the given values. *atimes* must be a 2-tuple of numbers,
of the form ``(atime, mtime)``, or None. If no second argument is used,
set the access and modified times to the current time.
descriptor *fd* to the given values. See :func:`utime` for proper
use of the *times* and *ns* arguments.
Availability: Unix.
.. versionadded:: 3.3
......@@ -1762,12 +1760,11 @@ Files and Directories
Added support for Windows 6.0 (Vista) symbolic links.
.. function:: lutimes(path[, times])
.. function:: lutimes(path[, times, *, ns=times])
Like :func:`utime`, but if *path* is a symbolic link, it is not
dereferenced. *times* must be a 2-tuple of numbers, of the form
``(atime, mtime)``, or None.
dereferenced. See :func:`utime` for proper use of the
*times* and *ns* arguments.
Availability: Unix.
......@@ -2226,22 +2223,43 @@ Files and Directories
Availability: Unix, Windows.
.. function:: utime(path[, times])
.. function:: utime(path[, times, *, ns=(atime_ns, mtime_ns)])
Set the access and modified times of the file specified by *path*.
:func:`utime` takes two optional parameters, *times* and *ns*.
These specify the times set on *path* and are used as follows:
Set the access and modified times of the file specified by *path*. If *times*
is ``None`` or not specified, then the file's access and modified times are
set to the current time. (The effect is similar to running the Unix program
:program:`touch` on the path.) Otherwise, *times* must be a 2-tuple of
numbers, of the form ``(atime, mtime)`` which is used to set the access and
modified times, respectively. Whether a directory can be given for *path*
- If *ns* is specified,
it must be a 2-tuple of the form ``(atime_ns, mtime_ns)``
where each member is an int expressing nanoseconds.
- If *times* is specified and is not ``None``,
it must be a 2-tuple of the form ``(atime, mtime)``
where each member is an int or float expressing seconds.
- If *times* is specified as ``None``,
this is equivalent to specifying an ``(atime, mtime)``
where both times are the current time.
(The effect is similar to running the Unix program
:program:`touch` on *path*.)
- If neither *ns* nor *times* is specified, this is
equivalent to specifying *times* as ``None``.
Specifying both *times* and *ns* simultaneously is an error.
Whether a directory can be given for *path*
depends on whether the operating system implements directories as files
(for example, Windows does not). Note that the exact times you set here may
not be returned by a subsequent :func:`~os.stat` call, depending on the
resolution with which your operating system records access and modification
times; see :func:`~os.stat`.
times; see :func:`~os.stat`. The best way to preserve exact times is to
use the *st_atime_ns* and *st_mtime_ns* fields from the :func:`os.stat`
result object with the *ns* parameter to `utime`.
Availability: Unix, Windows.
.. versionadded:: 3.3
The :attr:`ns` keyword parameter.
.. function:: walk(top, topdown=True, onerror=None, followlinks=False)
......
......@@ -62,6 +62,10 @@ PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
time_t sec);
/* Convert a PyLong to a time_t. */
PyAPI_FUNC(time_t) _PyLong_AsTime_t(
PyObject *obj);
/* Convert a number of seconds, int or float, to a timeval structure.
usec is in the range [0; 999999] and rounded towards zero.
For example, -1.2 is converted to (-2, 800000). */
......
......@@ -154,7 +154,7 @@ def copystat(src, dst, symlinks=False):
st = stat_func(src)
mode = stat.S_IMODE(st.st_mode)
utime_func(dst, (st.st_atime, st.st_mtime))
utime_func(dst, ns=(st.st_atime_ns, st.st_mtime_ns))
chmod_func(dst, mode)
if hasattr(st, 'st_flags'):
try:
......
......@@ -192,11 +192,11 @@ class StatAttributeTests(unittest.TestCase):
self.assertIn(attr, members)
# Make sure that the st_?time and st_?time_ns fields roughly agree
# (they should always agree up to the tens-of-microseconds magnitude)
# (they should always agree up to around tens-of-microseconds)
for name in 'st_atime st_mtime st_ctime'.split():
floaty = int(getattr(result, name) * 100000)
nanosecondy = getattr(result, name + "_ns") // 10000
self.assertEqual(floaty, nanosecondy)
self.assertAlmostEqual(floaty, nanosecondy, delta=2)
try:
result[200]
......@@ -303,20 +303,80 @@ class StatAttributeTests(unittest.TestCase):
st2 = os.stat(support.TESTFN)
self.assertEqual(st2.st_mtime, int(st.st_mtime-delta))
def test_utime_noargs(self):
def _test_utime(self, filename, attr, utime, delta):
# Issue #13327 removed the requirement to pass None as the
# second argument. Check that the previous methods of passing
# a time tuple or None work in addition to no argument.
st = os.stat(support.TESTFN)
st0 = os.stat(filename)
# Doesn't set anything new, but sets the time tuple way
os.utime(support.TESTFN, (st.st_atime, st.st_mtime))
utime(filename, (attr(st0, "st_atime"), attr(st0, "st_mtime")))
# Setting the time to the time you just read, then reading again,
# should always return exactly the same times.
st1 = os.stat(filename)
self.assertEqual(attr(st0, "st_mtime"), attr(st1, "st_mtime"))
self.assertEqual(attr(st0, "st_atime"), attr(st1, "st_atime"))
# Set to the current time in the old explicit way.
os.utime(support.TESTFN, None)
st1 = os.stat(support.TESTFN)
# Set to the current time in the new way
os.utime(support.TESTFN)
os.utime(filename, None)
st2 = os.stat(support.TESTFN)
self.assertAlmostEqual(st1.st_mtime, st2.st_mtime, delta=10)
# Set to the current time in the new way
os.utime(filename)
st3 = os.stat(filename)
self.assertAlmostEqual(attr(st2, "st_mtime"), attr(st3, "st_mtime"), delta=delta)
def test_utime(self):
def utime(file, times):
return os.utime(file, times)
self._test_utime(self.fname, getattr, utime, 10)
self._test_utime(support.TESTFN, getattr, utime, 10)
def _test_utime_ns(self, set_times_ns, test_dir=True):
def getattr_ns(o, attr):
return getattr(o, attr + "_ns")
ten_s = 10 * 1000 * 1000 * 1000
self._test_utime(self.fname, getattr_ns, set_times_ns, ten_s)
if test_dir:
self._test_utime(support.TESTFN, getattr_ns, set_times_ns, ten_s)
def test_utime_ns(self):
def utime_ns(file, times):
return os.utime(file, ns=times)
self._test_utime_ns(utime_ns)
requires_lutimes = unittest.skipUnless(hasattr(os, 'lutimes'),
"os.lutimes required for this test.")
requires_futimes = unittest.skipUnless(hasattr(os, 'futimes'),
"os.futimes required for this test.")
@requires_lutimes
def test_lutimes_ns(self):
def lutimes_ns(file, times):
return os.lutimes(file, ns=times)
self._test_utime_ns(lutimes_ns)
@requires_futimes
def test_futimes_ns(self):
def futimes_ns(file, times):
with open(file, "wb") as f:
os.futimes(f.fileno(), ns=times)
self._test_utime_ns(futimes_ns, test_dir=False)
def _utime_invalid_arguments(self, name, arg):
with self.assertRaises(RuntimeError):
getattr(os, name)(arg, (5, 5), ns=(5, 5))
def test_utime_invalid_arguments(self):
self._utime_invalid_arguments('utime', self.fname)
@requires_lutimes
def test_lutimes_invalid_arguments(self):
self._utime_invalid_arguments('lutimes', self.fname)
@requires_futimes
def test_futimes_invalid_arguments(self):
with open(self.fname, "wb") as f:
self._utime_invalid_arguments('futimes', f.fileno())
@unittest.skipUnless(stat_supports_subsecond,
"os.stat() doesn't has a subsecond resolution")
......@@ -338,8 +398,7 @@ class StatAttributeTests(unittest.TestCase):
os.utime(filename, (atime, mtime))
self._test_utime_subsecond(set_time)
@unittest.skipUnless(hasattr(os, 'futimes'),
"os.futimes required for this test.")
@requires_futimes
def test_futimes_subsecond(self):
def set_time(filename, atime, mtime):
with open(filename, "wb") as f:
......@@ -375,8 +434,7 @@ class StatAttributeTests(unittest.TestCase):
os.close(dirfd)
self._test_utime_subsecond(set_time)
@unittest.skipUnless(hasattr(os, 'lutimes'),
"os.lutimes required for this test.")
@requires_lutimes
def test_lutimes_subsecond(self):
def set_time(filename, atime, mtime):
os.lutimes(filename, (atime, mtime))
......
This diff is collapsed.
......@@ -123,7 +123,7 @@ error_time_t_overflow(void)
"timestamp out of range for platform time_t");
}
static time_t
time_t
_PyLong_AsTime_t(PyObject *obj)
{
#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment