Add NTFS conversion methods from
MODIFY
src/main/java/org/apache/commons/io/file/attribute/FileTimes.java
src/main/java/org/apache/commons/io/file/attribute/FileTimes.java
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
/**
- * Helps use {@link FileTime}.
+ * Helps use {@link FileTime} and interoperate Date and NTFS times.
*
* @since 2.12.0
*/
*/
public static final FileTime EPOCH = FileTime.from(Instant.EPOCH);
+ /**
+ * The offset of Windows time 0 to Unix epoch in 100-nanosecond intervals.
+ *
+ * <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290%28v=vs.85%29.aspx">Windows File Times</a>
+ * <p>
+ * A file time is a 64-bit value that represents the number of 100-nanosecond intervals that have elapsed since 12:00
+ * A.M. January 1, 1601 Coordinated Universal Time (UTC). This is the offset of Windows time 0 to Unix epoch in
+ * 100-nanosecond intervals.
+ * </p>
+ */
+ static final long WINDOWS_EPOCH_OFFSET = -116444736000000000L;
+
+ /**
+ * The amount of 100-nanosecond intervals in one second.
+ */
+ private static final long HUNDRED_NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1) / 100;
+
+ /**
+ * The amount of 100-nanosecond intervals in one millisecond.
+ */
+ static final long HUNDRED_NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1) / 100;
+
/**
* Subtracts milliseconds from a source FileTime.
*
@@ -71,7 +95,7 @@ public static FileTime minusSeconds(final FileTime fileTime, final long secondsT
}
/**
- * Returns the current instant FileTime from the system clock.
+ * Obtains the current instant FileTime from the system clock.
*
* @return the current instant FileTime from the system clock.
*/
@@ -79,6 +103,33 @@ public static FileTime now() {
return FileTime.from(Instant.now());
}
+ /**
+ * Converts NTFS time (100 nanosecond units since 1 January 1601) to Java time.
+ *
+ * @param ntfsTime the NTFS time in 100 nanosecond units
+ * @return the Date
+ */
+ public static Date ntfsTimeToDate(final long ntfsTime) {
+ final long javaHundredNanos = Math.addExact(ntfsTime, WINDOWS_EPOCH_OFFSET);
+ final long javaMillis = Math.floorDiv(javaHundredNanos, HUNDRED_NANOS_PER_MILLISECOND);
+ return new Date(javaMillis);
+ }
+
+ /**
+ * Converts NTFS time (100-nanosecond units since 1 January 1601) to a FileTime.
+ *
+ * @param ntfsTime the NTFS time in 100-nanosecond units
+ * @return the FileTime
+ *
+ * @see #toNtfsTime(FileTime)
+ */
+ public static FileTime ntfsTimeToFileTime(final long ntfsTime) {
+ final long javaHundredsNanos = Math.addExact(ntfsTime, WINDOWS_EPOCH_OFFSET);
+ final long javaSeconds = Math.floorDiv(javaHundredsNanos, HUNDRED_NANOS_PER_SECOND);
+ final long javaNanos = Math.floorMod(javaHundredsNanos, HUNDRED_NANOS_PER_SECOND) * 100;
+ return FileTime.from(Instant.ofEpochSecond(javaSeconds, javaNanos));
+ }
+
/**
* Adds milliseconds to a source FileTime.
*
@@ -122,6 +173,53 @@ public static void setLastModifiedTime(final Path path) throws IOException {
Files.setLastModifiedTime(path, now());
}
+ /**
+ * Converts {@link FileTime} to a {@link Date}. If the provided FileTime is {@code null}, the returned Date is also
+ * {@code null}.
+ *
+ * @param fileTime the file time to be converted.
+ * @return a {@link Date} which corresponds to the supplied time, or {@code null} if the time is {@code null}.
+ * @see #toFileTime(Date)
+ */
+ public static Date toDate(final FileTime fileTime) {
+ return fileTime != null ? new Date(fileTime.toMillis()) : null;
+ }
+
+ /**
+ * Converts {@link Date} to a {@link FileTime}. If the provided Date is {@code null}, the returned FileTime is also
+ * {@code null}.
+ *
+ * @param date the date to be converted.
+ * @return a {@link FileTime} which corresponds to the supplied date, or {@code null} if the date is {@code null}.
+ * @see #toDate(FileTime)
+ */
+ public static FileTime toFileTime(final Date date) {
+ return date != null ? FileTime.fromMillis(date.getTime()) : null;
+ }
+
+ /**
+ * Converts a {@link Date} to NTFS time.
+ *
+ * @param date the Date
+ * @return the NTFS time
+ */
+ public static long toNtfsTime(final Date date) {
+ final long javaHundredNanos = date.getTime() * HUNDRED_NANOS_PER_MILLISECOND;
+ return Math.subtractExact(javaHundredNanos, WINDOWS_EPOCH_OFFSET);
+ }
+
+ /**
+ * Converts a {@link FileTime} to NTFS time (100-nanosecond units since 1 January 1601).
+ *
+ * @param fileTime the FileTime
+ * @return the NTFS time in 100-nanosecond units
+ */
+ public static long toNtfsTime(final FileTime fileTime) {
+ final Instant instant = fileTime.toInstant();
+ final long javaHundredNanos = (instant.getEpochSecond() * HUNDRED_NANOS_PER_SECOND) + (instant.getNano() / 100);
+ return Math.subtractExact(javaHundredNanos, WINDOWS_EPOCH_OFFSET);
+ }
+
private FileTimes() {
// No instances.
}

MODIFY
src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java
src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java
package org.apache.commons.io.file.attribute;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import java.nio.file.attribute.FileTime;
import java.time.Instant;
+import java.util.Date;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests {@link FileTimes}.
*/
public class FileTimesTest {
+ public static Stream<Arguments> dateToNtfsProvider() {
+ // @formatter:off
+ return Stream.of(
+ Arguments.of("1601-01-01T00:00:00.000Z", 0),
+ Arguments.of("1601-01-01T00:00:00.000Z", 1),
+ Arguments.of("1600-12-31T23:59:59.999Z", -1),
+ Arguments.of("1601-01-01T00:00:00.001Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
+ Arguments.of("1601-01-01T00:00:00.001Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
+ Arguments.of("1601-01-01T00:00:00.000Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
+ Arguments.of("1600-12-31T23:59:59.999Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
+ Arguments.of("1600-12-31T23:59:59.999Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
+ Arguments.of("1600-12-31T23:59:59.998Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
+ Arguments.of("1970-01-01T00:00:00.000Z", -FileTimes.WINDOWS_EPOCH_OFFSET),
+ Arguments.of("1970-01-01T00:00:00.000Z", -FileTimes.WINDOWS_EPOCH_OFFSET + 1),
+ Arguments.of("1970-01-01T00:00:00.001Z", -FileTimes.WINDOWS_EPOCH_OFFSET + FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
+ Arguments.of("1969-12-31T23:59:59.999Z", -FileTimes.WINDOWS_EPOCH_OFFSET - 1),
+ Arguments.of("1969-12-31T23:59:59.999Z", -FileTimes.WINDOWS_EPOCH_OFFSET - FileTimes.HUNDRED_NANOS_PER_MILLISECOND));
+ // @formatter:on
+ }
+
+ public static Stream<Arguments> fileTimeToNtfsProvider() {
+ // @formatter:off
+ return Stream.of(
+ Arguments.of("1601-01-01T00:00:00.0000000Z", 0),
+ Arguments.of("1601-01-01T00:00:00.0000001Z", 1),
+ Arguments.of("1600-12-31T23:59:59.9999999Z", -1),
+ Arguments.of("1601-01-01T00:00:00.0010000Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
+ Arguments.of("1601-01-01T00:00:00.0010001Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
+ Arguments.of("1601-01-01T00:00:00.0009999Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
+ Arguments.of("1600-12-31T23:59:59.9990000Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
+ Arguments.of("1600-12-31T23:59:59.9990001Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
+ Arguments.of("1600-12-31T23:59:59.9989999Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
+ Arguments.of("1970-01-01T00:00:00.0000000Z", -FileTimes.WINDOWS_EPOCH_OFFSET),
+ Arguments.of("1970-01-01T00:00:00.0000001Z", -FileTimes.WINDOWS_EPOCH_OFFSET + 1),
+ Arguments.of("1970-01-01T00:00:00.0010000Z", -FileTimes.WINDOWS_EPOCH_OFFSET + FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
+ Arguments.of("1969-12-31T23:59:59.9999999Z", -FileTimes.WINDOWS_EPOCH_OFFSET - 1),
+ Arguments.of("1969-12-31T23:59:59.9990000Z", -FileTimes.WINDOWS_EPOCH_OFFSET - FileTimes.HUNDRED_NANOS_PER_MILLISECOND));
+ // @formatter:on
+ }
+
+ @ParameterizedTest
+ @MethodSource("dateToNtfsProvider")
+ public void testDateToFileTime(final String instant, final long ignored) {
+ final Instant parsedInstant = Instant.parse(instant);
+ final FileTime parsedFileTime = FileTime.from(parsedInstant);
+ final Date parsedDate = Date.from(parsedInstant);
+ assertEquals(parsedFileTime, FileTimes.toFileTime(parsedDate));
+ }
+
+ @ParameterizedTest
+ @MethodSource("dateToNtfsProvider")
+ public void testDateToNtfsTime(final String instant, final long ntfsTime) {
+ final long ntfsMillis = Math.floorDiv(ntfsTime, FileTimes.HUNDRED_NANOS_PER_MILLISECOND) * FileTimes.HUNDRED_NANOS_PER_MILLISECOND;
+ final Date parsed = Date.from(Instant.parse(instant));
+ assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed));
+ }
+
@Test
public void testEpoch() {
assertEquals(0, FileTimes.EPOCH.toMillis());
}
+ @ParameterizedTest
+ @MethodSource("fileTimeToNtfsProvider")
+ public void testFileTimeToDate(final String instant, final long ignored) {
+ final Instant parsedInstant = Instant.parse(instant);
+ final FileTime parsedFileTime = FileTime.from(parsedInstant);
+ final Date parsedDate = Date.from(parsedInstant);
+ assertEquals(parsedDate, FileTimes.toDate(parsedFileTime));
+ }
+
+ @ParameterizedTest
+ @MethodSource("fileTimeToNtfsProvider")
+ public void testFileTimeToNtfsTime(final String instant, final long ntfsTime) {
+ final FileTime parsed = FileTime.from(Instant.parse(instant));
+ assertEquals(ntfsTime, FileTimes.toNtfsTime(parsed));
+ }
+
+ //
+
@Test
public void testMinusMillis() {
final int millis = 2;
@@ -54,6 +136,29 @@ public void testMinusSeconds() {
assertEquals(Instant.EPOCH, FileTimes.minusSeconds(FileTimes.EPOCH, 0).toInstant());
}
+ @ParameterizedTest
+ @MethodSource("dateToNtfsProvider")
+ public void testNtfsTimeToDate(final String instant, final long ntfsTime) {
+ assertEquals(Instant.parse(instant), FileTimes.ntfsTimeToDate(ntfsTime).toInstant());
+ }
+
+ @ParameterizedTest
+ @MethodSource("fileTimeToNtfsProvider")
+ public void testNtfsTimeToFileTime(final String instant, final long ntfsTime) {
+ final FileTime parsed = FileTime.from(Instant.parse(instant));
+ assertEquals(parsed, FileTimes.ntfsTimeToFileTime(ntfsTime));
+ }
+
+ @Test
+ public void testNullDateToNullFileTime() {
+ assertNull(FileTimes.toFileTime(null));
+ }
+
+ @Test
+ public void testNullFileTimeToNullDate() {
+ assertNull(FileTimes.toDate(null));
+ }
+
@Test
public void testPlusMinusMillis() {
final int millis = 2;
@@ -74,5 +179,4 @@ public void testPlusSeconds() {
assertEquals(Instant.EPOCH.plusSeconds(seconds), FileTimes.plusSeconds(FileTimes.EPOCH, seconds).toInstant());
assertEquals(Instant.EPOCH, FileTimes.plusSeconds(FileTimes.EPOCH, 0).toInstant());
}
-
}

Add NTFS conversion methods from
https://github.com/apache/commons-compress/pull/256