JDK-8177809 : File.lastModified() is losing milliseconds (always ends in 000)
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 8,9
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: linux
  • CPU: x86_64
  • Submitted: 2017-03-29
  • Updated: 2021-04-23
  • Resolved: 2017-05-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 10 JDK 8 Other
10 b09Fixed 8u301Fixed openjdk8u302Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
openjdk version "1.8.0_121"
OpenJDK Runtime Environment (build 1.8.0_121-b14)
OpenJDK 64-Bit Server VM (build 25.121-b14, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Linux ess-hwvj 4.10.5-200.fc25.x86_64 #1 SMP Wed Mar 22 20:37:08 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux


A DESCRIPTION OF THE PROBLEM :
File.getLastModified() always returns with second precision, loosing the milliseconds (i.e. a number ending in 000). I tried using Files.getLastModifiedTime and it seems to work correctly.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create and run the following the FileTest class added to the source-code for test case


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Test f.lastModified [1490606336718]: true
Test Files.getLastModifiedTime [1490606336718]: true

ACTUAL -
Test f.lastModified [1490606336000]: false
Test Files.getLastModifiedTime [1490606336718]: true


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

public class FileTest {
    private static final long LM = 1490606336718L;

    public static void main(String[] args) throws IOException {
        File f = new File("test.txt");
        f.createNewFile();

        f.setLastModified(LM);

        System.out.printf("Test f.lastModified [%s]: %b\n",
f.lastModified(), f.lastModified() == LM);
        System.out.printf("Test Files.getLastModifiedTime [%s]: %b\n",
Files.getLastModifiedTime(f.toPath()).toMillis(),
(Files.getLastModifiedTime(f.toPath()).toMillis() == LM));

        f.delete();
    }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Call Files.getLastModifiedTime(f.toPath()).toMillis() instead of f.lastModified() 


Comments
Fix Request (8u) I would like to backport this patch to 8u for parity with Oracle 8u301. The original patch does not apply cleanly. The only conflict was copyright year line of UnixFileSystem_md.c. 8u patch has been reviewed.
21-04-2021

8u code review thread: https://mail.openjdk.java.net/pipermail/jdk8u-dev/2021-April/013671.html
20-04-2021

Indeed on OS X HFS+ stores times only to a granularity of one second: "HFS Plus stores dates in several data structures, including the volume header and catalog records. These dates are stored in unsigned 32-bit integers (UInt32) containing the number of seconds since midnight, January 1, 1904, GMT." https://developer.apple.com/legacy/library/technotes/tn/tn1150.html#HFSPlusDates
31-03-2017

Here's an updated patch which builds and runs on Linux, OS X, and Solaris. The <stdio.h> include and printf() calls will have to be removed. This fixes the problem on Linux (Ubuntu 16.04) and Solaris 11.3 but not on macOS 10.9.5. I assume that the problem on OS X is that utimes() does not store as high a resolution value as on Linux and Solaris. I also ran the test on Windows 7 and the problem did not reproduce so it is not an issue on Windows. # HG changeset patch # Parent b23f0d9ff042767ccb5b264ea8052267cd91719a # Parent 171e1006179895a294aa4a489c7eb376e8d6fe2d 8177809: File.lastModified() is losing milliseconds (always ends in 000) diff -r 171e10061798 src/java.base/unix/native/libjava/UnixFileSystem_md.c --- a/src/java.base/unix/native/libjava/UnixFileSystem_md.c Wed Mar 29 09:40:41 2017 -0700 +++ b/src/java.base/unix/native/libjava/UnixFileSystem_md.c Fri Mar 31 12:07:40 2017 -0700 @@ -23,6 +23,7 @@ * questions. */ +#include <stdio.h> #include <unistd.h> #include <assert.h> #include <sys/types.h> @@ -229,7 +230,17 @@ WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) { struct stat64 sb; if (stat64(path, &sb) == 0) { - rv = 1000 * (jlong)sb.st_mtime; +#ifndef MACOSX + printf("get: %ld %ld\n", + sb.st_mtim.tv_sec, sb.st_mtim.tv_nsec); + rv = (jlong)sb.st_mtim.tv_sec * 1000; + rv += (jlong)sb.st_mtim.tv_nsec / 1000000; +#else + printf("get: %ld %ld\n", + sb.st_mtimespec.tv_sec, sb.st_mtimespec.tv_nsec); + rv = (jlong)sb.st_mtimespec.tv_sec * 1000; + rv += (jlong)sb.st_mtimespec.tv_nsec / 1000000; +#endif } } END_PLATFORM_STRING(env, path); return rv; @@ -413,12 +424,24 @@ struct timeval tv[2]; /* Preserve access time */ - tv[0].tv_sec = sb.st_atime; - tv[0].tv_usec = 0; +#ifndef MACOSX + tv[0].tv_sec = sb.st_atim.tv_sec; + tv[0].tv_usec = sb.st_atim.tv_nsec / 1000; + printf("set atime: %ld %ld\n", tv[0].tv_sec, tv[0].tv_usec); +#else + tv[0].tv_sec = sb.st_atimespec.tv_sec; + tv[0].tv_usec = sb.st_atimespec.tv_nsec / 1000; + printf("set atime: %ld %d\n", tv[0].tv_sec, tv[0].tv_usec); +#endif /* Change last-modified time */ tv[1].tv_sec = time / 1000; tv[1].tv_usec = (time % 1000) * 1000; +#ifndef MACOSX + printf("set mtime: %ld %ld\n", tv[1].tv_sec, tv[1].tv_usec); +#else + printf("set mtime: %ld %d\n", tv[1].tv_sec, tv[1].tv_usec); +#endif if (utimes(path, tv) == 0) rv = JNI_TRUE;
31-03-2017

The patch as-is will not work on OS X as the struct name is different: struct timespec st_mtimespec; /* time of last data modification */
31-03-2017

I don't see why we need to round up to seconds if the data is already stored and available. It looks like an issue which originally stemmed from a legacy issues in some unix kernels. Relevant section from stat64(2) man page : == Since kernel 2.5.48, the stat structure supports nanosecond resolution for the three file timestamp fields. Glibc exposes the nanosecond component of each field using names of the form st_atim.tv_nsec if the _BSD_SOURCE or _SVID_SOURCE feature test macro is defined. These fields are specified in POSIX.1-2008, and, starting with version 2.12, glibc also exposes these field names if _POSIX_C_SOURCE is defined with the value 200809L or greater, or _XOPEN_SOURCE is defined with the value 700 or greater. If none of the aforementioned macros are defined, then the nanosecond values are exposed with names of the form st_atimensec. On file systems that do not support subsecond timestamps, the nanosecond fields are returned with the value 0. == I haven't seen how macosx stores this (yet) - updated patch : diff --git a/src/solaris/native/java/io/UnixFileSystem_md.c b/src/solaris/native/java/io/UnixFileSystem_md.c --- a/src/solaris/native/java/io/UnixFileSystem_md.c +++ b/src/solaris/native/java/io/UnixFileSystem_md.c @@ -208,7 +208,10 @@ WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) { struct stat64 sb; if (stat64(path, &sb) == 0) { - rv = 1000 * (jlong)sb.st_mtime; + rv = (jlong)sb.st_mtime * 1000; +#ifndef MACOSX + rv += (jlong)sb.st_mtim.tv_nsec / 1000000; +#endif }
31-03-2017

Looks like the specification [1] already dealt with the situation via the "possibly truncated" phrase: "If the operation succeeds and no intervening operations on the file take place, then the next invocation of the lastModified() method will return the (possibly truncated) time argument that was passed to this method." [1] http://download.java.net/java/jdk9/docs/api/java/io/File.html#setLastModified-long-
31-03-2017

Oops did not notice Sean's patch before writing about the same thing which does work at least on Ubuntu 16.04. Stuart has a good point about the resolution however. --- a/src/java.base/unix/native/libjava/UnixFileSystem_md.c +++ b/src/java.base/unix/native/libjava/UnixFileSystem_md.c @@ -224,17 +224,18 @@ Java_java_io_UnixFileSystem_getLastModifiedTime(JNIEnv *env, jobject this, jobject file) { jlong rv = 0; WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) { struct stat64 sb; if (stat64(path, &sb) == 0) { - rv = 1000 * (jlong)sb.st_mtime; + struct timespec mtime = sb.st_mtim; + rv = (jlong)(1000*mtime.tv_sec) + (jlong)(mtime.tv_nsec/1000000); } } END_PLATFORM_STRING(env, path); return rv; }
31-03-2017

Historically, many Unix filesystems would only report values for things like modification time with a resolution in seconds. File.lastModified() returns a value whose *units* are in milliseconds, but that doesn't imply the *resolution* is in milliseconds. As it stands, File.lastModified() appears to be using kernel values for modification time in units of seconds, converting to units of milliseconds as required by the return value by multiplying by 1000. Thus the fractional part is always zero. Files.getLastModifiedTime() apparently returns higher resolution values, and it probably uses different kernel interfaces to get this information. Note that its return type is a FileTime, whose resolution is unspecified, but which can be converted into different units. If Files.getLastModifiedTime() is reporting information with sufficient resolution, just use that instead. I'm not sure it's worth updating File.lastModified() to behave differently. Indeed, this might cause subtle bugs in applications that expect to get values that have one-second resolution.
31-03-2017

possible patch : ============= diff --git a/src/solaris/native/java/io/UnixFileSystem_md.c b/src/solaris/native/java/io/UnixFileSystem_md.c --- a/src/solaris/native/java/io/UnixFileSystem_md.c +++ b/src/solaris/native/java/io/UnixFileSystem_md.c @@ -208,7 +208,7 @@ WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) { struct stat64 sb; if (stat64(path, &sb) == 0) { - rv = 1000 * (jlong)sb.st_mtime; + rv = (jlong)sb.st_mtime * 1000 + (jlong)sb.st_mtim.tv_nsec / 1000000; } } END_PLATFORM_STRING(env, path); return rv; ========== we'd have to check if tv_nsec struct field is available on all supported unix kernels.
30-03-2017

Reproducible on Oracle binaries. To reproduce the issue, run the attached test case: Windows : JDK 8u121- Pass JDK 9-ea +161 - Pass Linux (Ubuntu 14.04) JDK 8u121 - Fail JDK 9-ea +161 - Fail Output with JDK 8u121 on Linux ubuntu: $ /shared/jdk1.8.0_121/bin/java JI9048368 Test f.lastModified [1490606336000]: false Test Files.getLastModifiedTime [1490606336000]: false $ /shared/jdk-9/bin/java JI9048368 Test f.lastModified [1490606336000]: false Test Files.getLastModifiedTime [1490606336718]: true Output on Windows 7 with JDK 8u121 and 9-ea: >java JI9048368 Test f.lastModified [1490606336718]: true Test Files.getLastModifiedTime [1490606336718]: true
30-03-2017