Skip to content

Commit 0789b2b

Browse files
author
Gary Gregory
committed
Add NTFS conversion methods from
apache/commons-compress#256
1 parent a72b458 commit 0789b2b

2 files changed

Lines changed: 205 additions & 3 deletions

File tree

src/main/java/org/apache/commons/io/file/attribute/FileTimes.java

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@
2222
import java.nio.file.Path;
2323
import java.nio.file.attribute.FileTime;
2424
import java.time.Instant;
25+
import java.util.Date;
26+
import java.util.concurrent.TimeUnit;
2527

2628
/**
27-
* Helps use {@link FileTime}.
29+
* Helps use {@link FileTime} and interoperate Date and NTFS times.
2830
*
2931
* @since 2.12.0
3032
*/
@@ -37,6 +39,28 @@ public class FileTimes {
3739
*/
3840
public static final FileTime EPOCH = FileTime.from(Instant.EPOCH);
3941

42+
/**
43+
* The offset of Windows time 0 to Unix epoch in 100-nanosecond intervals.
44+
*
45+
* <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290%28v=vs.85%29.aspx">Windows File Times</a>
46+
* <p>
47+
* A file time is a 64-bit value that represents the number of 100-nanosecond intervals that have elapsed since 12:00
48+
* A.M. January 1, 1601 Coordinated Universal Time (UTC). This is the offset of Windows time 0 to Unix epoch in
49+
* 100-nanosecond intervals.
50+
* </p>
51+
*/
52+
static final long WINDOWS_EPOCH_OFFSET = -116444736000000000L;
53+
54+
/**
55+
* The amount of 100-nanosecond intervals in one second.
56+
*/
57+
private static final long HUNDRED_NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1) / 100;
58+
59+
/**
60+
* The amount of 100-nanosecond intervals in one millisecond.
61+
*/
62+
static final long HUNDRED_NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1) / 100;
63+
4064
/**
4165
* Subtracts milliseconds from a source FileTime.
4266
*
@@ -71,14 +95,41 @@ public static FileTime minusSeconds(final FileTime fileTime, final long secondsT
7195
}
7296

7397
/**
74-
* Returns the current instant FileTime from the system clock.
98+
* Obtains the current instant FileTime from the system clock.
7599
*
76100
* @return the current instant FileTime from the system clock.
77101
*/
78102
public static FileTime now() {
79103
return FileTime.from(Instant.now());
80104
}
81105

106+
/**
107+
* Converts NTFS time (100 nanosecond units since 1 January 1601) to Java time.
108+
*
109+
* @param ntfsTime the NTFS time in 100 nanosecond units
110+
* @return the Date
111+
*/
112+
public static Date ntfsTimeToDate(final long ntfsTime) {
113+
final long javaHundredNanos = Math.addExact(ntfsTime, WINDOWS_EPOCH_OFFSET);
114+
final long javaMillis = Math.floorDiv(javaHundredNanos, HUNDRED_NANOS_PER_MILLISECOND);
115+
return new Date(javaMillis);
116+
}
117+
118+
/**
119+
* Converts NTFS time (100-nanosecond units since 1 January 1601) to a FileTime.
120+
*
121+
* @param ntfsTime the NTFS time in 100-nanosecond units
122+
* @return the FileTime
123+
*
124+
* @see #toNtfsTime(FileTime)
125+
*/
126+
public static FileTime ntfsTimeToFileTime(final long ntfsTime) {
127+
final long javaHundredsNanos = Math.addExact(ntfsTime, WINDOWS_EPOCH_OFFSET);
128+
final long javaSeconds = Math.floorDiv(javaHundredsNanos, HUNDRED_NANOS_PER_SECOND);
129+
final long javaNanos = Math.floorMod(javaHundredsNanos, HUNDRED_NANOS_PER_SECOND) * 100;
130+
return FileTime.from(Instant.ofEpochSecond(javaSeconds, javaNanos));
131+
}
132+
82133
/**
83134
* Adds milliseconds to a source FileTime.
84135
*
@@ -122,6 +173,53 @@ public static void setLastModifiedTime(final Path path) throws IOException {
122173
Files.setLastModifiedTime(path, now());
123174
}
124175

176+
/**
177+
* Converts {@link FileTime} to a {@link Date}. If the provided FileTime is {@code null}, the returned Date is also
178+
* {@code null}.
179+
*
180+
* @param fileTime the file time to be converted.
181+
* @return a {@link Date} which corresponds to the supplied time, or {@code null} if the time is {@code null}.
182+
* @see #toFileTime(Date)
183+
*/
184+
public static Date toDate(final FileTime fileTime) {
185+
return fileTime != null ? new Date(fileTime.toMillis()) : null;
186+
}
187+
188+
/**
189+
* Converts {@link Date} to a {@link FileTime}. If the provided Date is {@code null}, the returned FileTime is also
190+
* {@code null}.
191+
*
192+
* @param date the date to be converted.
193+
* @return a {@link FileTime} which corresponds to the supplied date, or {@code null} if the date is {@code null}.
194+
* @see #toDate(FileTime)
195+
*/
196+
public static FileTime toFileTime(final Date date) {
197+
return date != null ? FileTime.fromMillis(date.getTime()) : null;
198+
}
199+
200+
/**
201+
* Converts a {@link Date} to NTFS time.
202+
*
203+
* @param date the Date
204+
* @return the NTFS time
205+
*/
206+
public static long toNtfsTime(final Date date) {
207+
final long javaHundredNanos = date.getTime() * HUNDRED_NANOS_PER_MILLISECOND;
208+
return Math.subtractExact(javaHundredNanos, WINDOWS_EPOCH_OFFSET);
209+
}
210+
211+
/**
212+
* Converts a {@link FileTime} to NTFS time (100-nanosecond units since 1 January 1601).
213+
*
214+
* @param fileTime the FileTime
215+
* @return the NTFS time in 100-nanosecond units
216+
*/
217+
public static long toNtfsTime(final FileTime fileTime) {
218+
final Instant instant = fileTime.toInstant();
219+
final long javaHundredNanos = (instant.getEpochSecond() * HUNDRED_NANOS_PER_SECOND) + (instant.getNano() / 100);
220+
return Math.subtractExact(javaHundredNanos, WINDOWS_EPOCH_OFFSET);
221+
}
222+
125223
private FileTimes() {
126224
// No instances.
127225
}

src/test/java/org/apache/commons/io/file/attribute/FileTimesTest.java

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,103 @@
1818
package org.apache.commons.io.file.attribute;
1919

2020
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertNull;
2122

23+
import java.nio.file.attribute.FileTime;
2224
import java.time.Instant;
25+
import java.util.Date;
26+
import java.util.stream.Stream;
2327

2428
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.params.ParameterizedTest;
30+
import org.junit.jupiter.params.provider.Arguments;
31+
import org.junit.jupiter.params.provider.MethodSource;
2532

2633
/**
2734
* Tests {@link FileTimes}.
2835
*/
2936
public class FileTimesTest {
3037

38+
public static Stream<Arguments> dateToNtfsProvider() {
39+
// @formatter:off
40+
return Stream.of(
41+
Arguments.of("1601-01-01T00:00:00.000Z", 0),
42+
Arguments.of("1601-01-01T00:00:00.000Z", 1),
43+
Arguments.of("1600-12-31T23:59:59.999Z", -1),
44+
Arguments.of("1601-01-01T00:00:00.001Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
45+
Arguments.of("1601-01-01T00:00:00.001Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
46+
Arguments.of("1601-01-01T00:00:00.000Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
47+
Arguments.of("1600-12-31T23:59:59.999Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
48+
Arguments.of("1600-12-31T23:59:59.999Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
49+
Arguments.of("1600-12-31T23:59:59.998Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
50+
Arguments.of("1970-01-01T00:00:00.000Z", -FileTimes.WINDOWS_EPOCH_OFFSET),
51+
Arguments.of("1970-01-01T00:00:00.000Z", -FileTimes.WINDOWS_EPOCH_OFFSET + 1),
52+
Arguments.of("1970-01-01T00:00:00.001Z", -FileTimes.WINDOWS_EPOCH_OFFSET + FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
53+
Arguments.of("1969-12-31T23:59:59.999Z", -FileTimes.WINDOWS_EPOCH_OFFSET - 1),
54+
Arguments.of("1969-12-31T23:59:59.999Z", -FileTimes.WINDOWS_EPOCH_OFFSET - FileTimes.HUNDRED_NANOS_PER_MILLISECOND));
55+
// @formatter:on
56+
}
57+
58+
public static Stream<Arguments> fileTimeToNtfsProvider() {
59+
// @formatter:off
60+
return Stream.of(
61+
Arguments.of("1601-01-01T00:00:00.0000000Z", 0),
62+
Arguments.of("1601-01-01T00:00:00.0000001Z", 1),
63+
Arguments.of("1600-12-31T23:59:59.9999999Z", -1),
64+
Arguments.of("1601-01-01T00:00:00.0010000Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
65+
Arguments.of("1601-01-01T00:00:00.0010001Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
66+
Arguments.of("1601-01-01T00:00:00.0009999Z", FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
67+
Arguments.of("1600-12-31T23:59:59.9990000Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
68+
Arguments.of("1600-12-31T23:59:59.9990001Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND + 1),
69+
Arguments.of("1600-12-31T23:59:59.9989999Z", -FileTimes.HUNDRED_NANOS_PER_MILLISECOND - 1),
70+
Arguments.of("1970-01-01T00:00:00.0000000Z", -FileTimes.WINDOWS_EPOCH_OFFSET),
71+
Arguments.of("1970-01-01T00:00:00.0000001Z", -FileTimes.WINDOWS_EPOCH_OFFSET + 1),
72+
Arguments.of("1970-01-01T00:00:00.0010000Z", -FileTimes.WINDOWS_EPOCH_OFFSET + FileTimes.HUNDRED_NANOS_PER_MILLISECOND),
73+
Arguments.of("1969-12-31T23:59:59.9999999Z", -FileTimes.WINDOWS_EPOCH_OFFSET - 1),
74+
Arguments.of("1969-12-31T23:59:59.9990000Z", -FileTimes.WINDOWS_EPOCH_OFFSET - FileTimes.HUNDRED_NANOS_PER_MILLISECOND));
75+
// @formatter:on
76+
}
77+
78+
@ParameterizedTest
79+
@MethodSource("dateToNtfsProvider")
80+
public void testDateToFileTime(final String instant, final long ignored) {
81+
final Instant parsedInstant = Instant.parse(instant);
82+
final FileTime parsedFileTime = FileTime.from(parsedInstant);
83+
final Date parsedDate = Date.from(parsedInstant);
84+
assertEquals(parsedFileTime, FileTimes.toFileTime(parsedDate));
85+
}
86+
87+
@ParameterizedTest
88+
@MethodSource("dateToNtfsProvider")
89+
public void testDateToNtfsTime(final String instant, final long ntfsTime) {
90+
final long ntfsMillis = Math.floorDiv(ntfsTime, FileTimes.HUNDRED_NANOS_PER_MILLISECOND) * FileTimes.HUNDRED_NANOS_PER_MILLISECOND;
91+
final Date parsed = Date.from(Instant.parse(instant));
92+
assertEquals(ntfsMillis, FileTimes.toNtfsTime(parsed));
93+
}
94+
3195
@Test
3296
public void testEpoch() {
3397
assertEquals(0, FileTimes.EPOCH.toMillis());
3498
}
3599

100+
@ParameterizedTest
101+
@MethodSource("fileTimeToNtfsProvider")
102+
public void testFileTimeToDate(final String instant, final long ignored) {
103+
final Instant parsedInstant = Instant.parse(instant);
104+
final FileTime parsedFileTime = FileTime.from(parsedInstant);
105+
final Date parsedDate = Date.from(parsedInstant);
106+
assertEquals(parsedDate, FileTimes.toDate(parsedFileTime));
107+
}
108+
109+
@ParameterizedTest
110+
@MethodSource("fileTimeToNtfsProvider")
111+
public void testFileTimeToNtfsTime(final String instant, final long ntfsTime) {
112+
final FileTime parsed = FileTime.from(Instant.parse(instant));
113+
assertEquals(ntfsTime, FileTimes.toNtfsTime(parsed));
114+
}
115+
116+
//
117+
36118
@Test
37119
public void testMinusMillis() {
38120
final int millis = 2;
@@ -54,6 +136,29 @@ public void testMinusSeconds() {
54136
assertEquals(Instant.EPOCH, FileTimes.minusSeconds(FileTimes.EPOCH, 0).toInstant());
55137
}
56138

139+
@ParameterizedTest
140+
@MethodSource("dateToNtfsProvider")
141+
public void testNtfsTimeToDate(final String instant, final long ntfsTime) {
142+
assertEquals(Instant.parse(instant), FileTimes.ntfsTimeToDate(ntfsTime).toInstant());
143+
}
144+
145+
@ParameterizedTest
146+
@MethodSource("fileTimeToNtfsProvider")
147+
public void testNtfsTimeToFileTime(final String instant, final long ntfsTime) {
148+
final FileTime parsed = FileTime.from(Instant.parse(instant));
149+
assertEquals(parsed, FileTimes.ntfsTimeToFileTime(ntfsTime));
150+
}
151+
152+
@Test
153+
public void testNullDateToNullFileTime() {
154+
assertNull(FileTimes.toFileTime(null));
155+
}
156+
157+
@Test
158+
public void testNullFileTimeToNullDate() {
159+
assertNull(FileTimes.toDate(null));
160+
}
161+
57162
@Test
58163
public void testPlusMinusMillis() {
59164
final int millis = 2;
@@ -74,5 +179,4 @@ public void testPlusSeconds() {
74179
assertEquals(Instant.EPOCH.plusSeconds(seconds), FileTimes.plusSeconds(FileTimes.EPOCH, seconds).toInstant());
75180
assertEquals(Instant.EPOCH, FileTimes.plusSeconds(FileTimes.EPOCH, 0).toInstant());
76181
}
77-
78182
}

0 commit comments

Comments
 (0)