0
votes

Java has method lastModified to get File modified time (a long value), which is measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).

It seems NTFS has file time resolution of 100 ns, and the data range starts from January 1601. So there is no lossless conversion from NTFS time to the long value.

JNI may be the solution to get NTFS timestamps, but could there be any easier solution?

I just need the 64-bit FILETIME value on NTFS. Maybe there is some simple way for a Java application to get it?

2

2 Answers

0
votes

If you are using Java 7 or newer you should investigate the java.nio.file package.

In particular, java.nio.file.attribute.FileTime may solve your problem.

0
votes

1. Use Powershell

long fileTimeUtc = 0;

InputStream inpStrm = Runtime.getRuntime().exec(new String[] {
    "powershell", "[System.IO.File]::GetLastWriteTimeUtc('" + fullFilename.replace("'", "''") + "').ToFileTimeUtc();"
}).getInputStream();

for (;;) {
    int incomingByte = inpStrm.read();
    if (incomingByte < 48 || incomingByte > 57) break;
    fileTimeUtc = fileTimeUtc * 10 + incomingByte - 48;
}

There is always a newline at the end of this ps command's output. So only need to check the byte value to end the loop. Single quote (') should be safer and easier than double quote (") for powershell in this case.

If the file doesn't exist, the output is 0.

DateTime structure of .net also has 100-nanosecond resolution, so the conversion ToFileTimeUtc() should be lossless for most practical applications (negative long value is not handled in this code, you can add the logic, although negative result should be uncommon). (For some rare cases, there could be a problem, because the time range of DateTime is 0001-9999, and NTFS has 1601-60056.)

2. Use executable

Similarly, you can always write native code to get the value of last write time, and then Java can run that executable to get its output.

#include <stdio.h>
#include <windows.h>

int main(int argc, char *argv[])
{
    HANDLE hFile;
    if( argc != 2 )
    {
        printf("0");
        return 1;
    }
    hFile = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("0");
        return 1;
    }
    FILETIME ftCreate, ftAccess, ftWrite;
    if (GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite)){
        printf("%lld\n",*(long long *)(&ftWrite));
        CloseHandle(hFile);
        return 0;
    } else {
        printf("0");
        CloseHandle(hFile);
        return 1;
    }
}

Then call it with short path name like this:

Runtime.getRuntime().exec(new String[]{
    "cmd",
    "/c @echo off & for %I in (\""+fullFilename+"\") do a.exe %~fsI"
})

This way is not so different from the first one, but without conversion, it's more straightforward.