Project Nayuki


Windows timestamp accessor library

With help from C#, now Java and Python programs can get and set high-precision Windows file timestamps.

Windows stores timestamps for each file and folder in a file system to the granularity of 100 ns. However, most tools that are not specifically designed for Windows cannot operate at this precision, especially cross-platform file formats and programming languages that have a strong presence in the Unix world. For example, file archive formats might only store timestamps to the granularity of one second, or programming languages might only let you query the modification timestamp but not the creation timestamp. Furthermore, some programming languages don’t provide a way to set any file timestamps at all.

I’ve implemented a standalone Windows C# program that can get and set all the 3 types of file/folder timestamps at the full native precision, and libraries in Java and Python that interface with the C# program so these languages can benefit from the ability to manipulate these timestamps. Unicode paths are supported.

This set of software can be considered a meta-tool – a tool for building application tools. With the library, it’s easy to write programs like these: A tool that adds an offset to a file’s timestamps, a tool to copy timestamps from one file to another, a tool to archive file timestamps to a text file.

Download

Libraries:

Extras:

The EXE file must be placed in the current working directory when running the Java VM or Python interpreter.

API and examples

Java

API summary:

class WindowsTimestampAccessor {
    WindowsTimestampAccessor();
    void close();
    
    long getCreationTime(File f);
    long getModificationTime(File f);
    long getAccessTime(File f);
    
    void setCreationTime(File f, long ticks);
    void setModificationTime(File f, long ticks);
    void setAccessTime(File f, long ticks);
    
    static int[] ticksToDatetime(long ticks);
    static long datetimeToTicks(int... dt);
}

Usage example:

try (var wt = new WindowsTimestampAccessor()) {
    
    File f0 = new File("hello.txt");
    long ct = wt.getCreationTime(f0);
    
    File f1 = new File("goodbye.txt");
    long newct = ct + 86400_0000000L;  // A day later
    wt.setModificationTime(f1, newct);
    
    File f2 = new File("你好.png");  // Supports Unicode
    System.out.println(wt.getAccessTime(f2));
}

Notes:

  • Objects of this class are thread-safe.

  • Avoid creating more than one WindowsTimestampAccessor object:

    • The object is stateless, so it doesn’t matter what transactions were previously processed.

    • Each object creates a new external native process, which is expensive in time and memory.

    • Multiple threads can use one object without worrying about synchronization.

  • Please explicitly call close() to terminate the external native process. Otherwise, the process may linger until the Java VM exists. (Or if you’re lucky, the output stream on the Java side will be closed at garbage collection, the external process will see the end of its input and terminate itself.)

  • To convert from Unix time in seconds to Windows time in ticks: ticks = unixSeconds * 10_000_000L + WindowsTimestampAccessor.datetimeToTicks(1970, 1, 1, 0, 0, 0, 0);.

Python

API summary:

class WindowsTimestampAccessor:
    def __init__(self)
    def close(self)
    
    def __enter__(self)          # with-statement
    def __exit__(self, t, v, b)  # with-statement
    
    def get_creation_time    (self, path)  # Returns int
    def get_modification_time(self, path)  # Returns int
    def get_access_time      (self, path)  # Returns int
    
    def set_creation_time    (self, path, ticks)  # Returns None
    def set_modification_time(self, path, ticks)  # Returns None
    def set_access_time      (self, path, ticks)  # Returns None

# Module functions
def ticks_to_datetime(ticks)  # Returns datetime.datetime object
def datetime_to_ticks(dt)     # Returns int

Usage example:

import wintimestamp

# Using with-statement (preferred)
with wintimestamp.WindowsTimestampAccessor() as wt:
    t = wt.get_modification_time("myfile.doc")
    print(wt.ticks_to_datetime(t))
    print("Ticks: ", t)

# Old-style resource management
wt = wintimestamp.WindowsTimestampAccessor()
try:
    # Supports Unicode file names
	ct = 621355968000000000
    wt.set_creation_time(u"\u4F60\u597D.png", ct)
finally:
    wt.close()

Notes:

  • Objects of this class are not thread-safe. You will need to add your own locks when multithreading.

  • Please either use the with-statement or explicitly call close(), to ensure that the external native process is terminated in a timely manner.

C# program’s protocol

Syntax for both standard input and output (stdin, stdout):

  • The binary streams are interpreted as UTF-8 without BOM.

  • Newlines are universal (CR+LF / LF / CR). (Both sides must accept universal newlines.)

  • Each line is composed of one or more tab-separated tokens.

  • Each input line is a query command, which generates a single output line as a response.

  • The response to each input line is either “error” or the appropriate affirmative response.

Commands:

  • Input: GetCreationTime <tab> <path>
    Output: ok <tab> <int64 ticks>

  • Input: GetModificationTime <tab> <path>
    Output: ok <tab> <int64 ticks>

  • Input: GetAccessTime <tab> <path>
    Output: ok <tab> <int64 ticks>

  • Input: SetCreationTime <tab> <path> <tab> <int64 ticks>
    Output: ok

  • Input: SetModificationTime <tab> <path> <tab> <int64 ticks>
    Output: ok

  • Input: SetAccessTime <tab> <path> <tab> <int64 ticks>
    Output: ok

  • Input: (EOF)
    Output: (EOF, and the process exits with status 0)

Usage example:

C:\>WindowsTimestampAccessor.exe
GetModificationTime	C:\
ok	635462611321362140
SetCreationTime	D:\pic.jpg	635463434961076893
ok
GetFoobar
error
GetAccessTime	X:\notexist
error
SetAccessTime	C:\thing.doc	abc123
error

Notes:

  • The raw protocol is shown here for hardcore developers only. Typical developers can ignore this information and use the Java or Python library.

  • It is strongly recommended to use absolute paths rather than relative paths. Otherwise you need to be careful in keeping track of the process’s working directory.

  • The program cannot handle file paths that contain newline or tab characters (\r, \n, \t).

  • It is acceptable to connect stdin to a file and/or stdout to a file.

  • The program takes no command-line arguments.

  • The caller of the program should check the exit status code.

Notes

  • Windows’s native timestamp format, called ticks, is a signed 64-bit integer representing 107 times the number of seconds since midnight UTC on January 1st, Year 1 on the proleptic Gregorian calendar.

  • NTFS provides all 3 timestamps to the precision of ticks. But FAT12/FAT16/FAT32 provides the creation timestamp to 10 ms precision, the modification timestamp to 2 s precision, and the access timestamp to 1 day precision.

  • NTFS stores timestamps in UTC, so the values stay consistent even if the system time zone changes or the files are moved to another NTFS network share.

  • FAT stores timestamps in local time, so the meaning of the values depends on the current system time zone. Moving files between computers or between FAT and NTFS volumes has a high chance of wrecking meaningful timestamps.

  • Since the C# program uses stdin and stdout instead of foreign function interfaces, it’s relatively to interface with it from any programming language.

  • The C# program will set timestamps on a read-only file by temporarily unmarking it as read-only, changing the timestamp, and restoring the read-only attribute.

  • Java SE 7 adds a set of APIs to get and set these 3 timestamps on Windows systems. However, their API takes some effort to convert between linear timestamps and date-and-time fields. The set of relevant functions consists of: java.nio.Files.readAttributes(), java.nio.Files.setAttribute(), java.nio.file.attribute.BasicFileAttributes, java.nio.file.attribute.FileTime.

    OpenJDK’s initial implementation of Windows file timestamps, from Java 7 to 13, only supported microsecond granularity (i.e. 10 ticks, even when using TimeUnit.NANOSECONDS). This means that information is lost when reading or writing timestamps. However, this was fixed in OpenJDK 14.