This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-io.git
The following commit(s) were added to refs/heads/master by this push:
new 0789b2bd Add NTFS conversion methods from
https://github.com/apache/commons-compress/pull/256
0789b2bd is described below
commit 0789b2bd4bd554c6eac1f822dec0259da06d7d51
Author: Gary Gregory <[email protected]>
AuthorDate: Sun Apr 17 10:16:10 2022 -0400
Add NTFS conversion methods from
https://github.com/apache/commons-compress/pull/256
---
.../commons/io/file/attribute/FileTimes.java | 102 +++++++++++++++++++-
.../commons/io/file/attribute/FileTimesTest.java | 106 ++++++++++++++++++++-
2 files changed, 205 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java
b/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java
index d7e2cec5..ab84211f 100644
--- a/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java
+++ b/src/main/java/org/apache/commons/io/file/attribute/FileTimes.java
@@ -22,9 +22,11 @@ import java.nio.file.Files;
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
*/
@@ -37,6 +39,28 @@ public class FileTimes {
*/
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 class FileTimes {
}
/**
- * 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 class FileTimes {
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 class FileTimes {
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.
}
diff --git
a/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java
b/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java
index 9233a346..8d506ccf 100644
--- a/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java
+++ b/src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java
@@ -18,21 +18,103 @@
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 class FileTimesTest {
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 class FileTimesTest {
assertEquals(Instant.EPOCH.plusSeconds(seconds),
FileTimes.plusSeconds(FileTimes.EPOCH, seconds).toInstant());
assertEquals(Instant.EPOCH, FileTimes.plusSeconds(FileTimes.EPOCH,
0).toInstant());
}
-
}