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. ...@@ -934,13 +934,11 @@ as internal buffering of data.
.. versionadded:: 3.3 .. 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 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, descriptor *fd* to the given values. See :func:`utime` for proper
of the form ``(atime, mtime)``, or None. If no second argument is used, use of the *times* and *ns* arguments.
set the access and modified times to the current time.
Availability: Unix. Availability: Unix.
.. versionadded:: 3.3 .. versionadded:: 3.3
...@@ -1762,12 +1760,11 @@ Files and Directories ...@@ -1762,12 +1760,11 @@ Files and Directories
Added support for Windows 6.0 (Vista) symbolic links. 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 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 dereferenced. See :func:`utime` for proper use of the
``(atime, mtime)``, or None. *times* and *ns* arguments.
Availability: Unix. Availability: Unix.
...@@ -2226,22 +2223,43 @@ Files and Directories ...@@ -2226,22 +2223,43 @@ Files and Directories
Availability: Unix, Windows. 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* - If *ns* is specified,
is ``None`` or not specified, then the file's access and modified times are it must be a 2-tuple of the form ``(atime_ns, mtime_ns)``
set to the current time. (The effect is similar to running the Unix program where each member is an int expressing nanoseconds.
:program:`touch` on the path.) Otherwise, *times* must be a 2-tuple of - If *times* is specified and is not ``None``,
numbers, of the form ``(atime, mtime)`` which is used to set the access and it must be a 2-tuple of the form ``(atime, mtime)``
modified times, respectively. Whether a directory can be given for *path* 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 depends on whether the operating system implements directories as files
(for example, Windows does not). Note that the exact times you set here may (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 not be returned by a subsequent :func:`~os.stat` call, depending on the
resolution with which your operating system records access and modification 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. Availability: Unix, Windows.
.. versionadded:: 3.3
The :attr:`ns` keyword parameter.
.. function:: walk(top, topdown=True, onerror=None, followlinks=False) .. function:: walk(top, topdown=True, onerror=None, followlinks=False)
......
...@@ -62,6 +62,10 @@ PyAPI_FUNC(int) _PyTime_ObjectToTime_t( ...@@ -62,6 +62,10 @@ PyAPI_FUNC(int) _PyTime_ObjectToTime_t(
PyAPI_FUNC(PyObject *) _PyLong_FromTime_t( PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
time_t sec); 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. /* Convert a number of seconds, int or float, to a timeval structure.
usec is in the range [0; 999999] and rounded towards zero. usec is in the range [0; 999999] and rounded towards zero.
For example, -1.2 is converted to (-2, 800000). */ For example, -1.2 is converted to (-2, 800000). */
......
...@@ -154,7 +154,7 @@ def copystat(src, dst, symlinks=False): ...@@ -154,7 +154,7 @@ def copystat(src, dst, symlinks=False):
st = stat_func(src) st = stat_func(src)
mode = stat.S_IMODE(st.st_mode) 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) chmod_func(dst, mode)
if hasattr(st, 'st_flags'): if hasattr(st, 'st_flags'):
try: try:
......
...@@ -192,11 +192,11 @@ class StatAttributeTests(unittest.TestCase): ...@@ -192,11 +192,11 @@ class StatAttributeTests(unittest.TestCase):
self.assertIn(attr, members) self.assertIn(attr, members)
# Make sure that the st_?time and st_?time_ns fields roughly agree # 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(): for name in 'st_atime st_mtime st_ctime'.split():
floaty = int(getattr(result, name) * 100000) floaty = int(getattr(result, name) * 100000)
nanosecondy = getattr(result, name + "_ns") // 10000 nanosecondy = getattr(result, name + "_ns") // 10000
self.assertEqual(floaty, nanosecondy) self.assertAlmostEqual(floaty, nanosecondy, delta=2)
try: try:
result[200] result[200]
...@@ -303,20 +303,80 @@ class StatAttributeTests(unittest.TestCase): ...@@ -303,20 +303,80 @@ class StatAttributeTests(unittest.TestCase):
st2 = os.stat(support.TESTFN) st2 = os.stat(support.TESTFN)
self.assertEqual(st2.st_mtime, int(st.st_mtime-delta)) 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 # Issue #13327 removed the requirement to pass None as the
# second argument. Check that the previous methods of passing # second argument. Check that the previous methods of passing
# a time tuple or None work in addition to no argument. # 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 # 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. # Set to the current time in the old explicit way.
os.utime(support.TESTFN, None) os.utime(filename, None)
st1 = os.stat(support.TESTFN)
# Set to the current time in the new way
os.utime(support.TESTFN)
st2 = os.stat(support.TESTFN) 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, @unittest.skipUnless(stat_supports_subsecond,
"os.stat() doesn't has a subsecond resolution") "os.stat() doesn't has a subsecond resolution")
...@@ -338,8 +398,7 @@ class StatAttributeTests(unittest.TestCase): ...@@ -338,8 +398,7 @@ class StatAttributeTests(unittest.TestCase):
os.utime(filename, (atime, mtime)) os.utime(filename, (atime, mtime))
self._test_utime_subsecond(set_time) self._test_utime_subsecond(set_time)
@unittest.skipUnless(hasattr(os, 'futimes'), @requires_futimes
"os.futimes required for this test.")
def test_futimes_subsecond(self): def test_futimes_subsecond(self):
def set_time(filename, atime, mtime): def set_time(filename, atime, mtime):
with open(filename, "wb") as f: with open(filename, "wb") as f:
...@@ -375,8 +434,7 @@ class StatAttributeTests(unittest.TestCase): ...@@ -375,8 +434,7 @@ class StatAttributeTests(unittest.TestCase):
os.close(dirfd) os.close(dirfd)
self._test_utime_subsecond(set_time) self._test_utime_subsecond(set_time)
@unittest.skipUnless(hasattr(os, 'lutimes'), @requires_lutimes
"os.lutimes required for this test.")
def test_lutimes_subsecond(self): def test_lutimes_subsecond(self):
def set_time(filename, atime, mtime): def set_time(filename, atime, mtime):
os.lutimes(filename, (atime, mtime)) os.lutimes(filename, (atime, mtime))
......
This diff is collapsed.
...@@ -123,7 +123,7 @@ error_time_t_overflow(void) ...@@ -123,7 +123,7 @@ error_time_t_overflow(void)
"timestamp out of range for platform time_t"); "timestamp out of range for platform time_t");
} }
static time_t time_t
_PyLong_AsTime_t(PyObject *obj) _PyLong_AsTime_t(PyObject *obj)
{ {
#if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG #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