View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.utils;
20  
21  import static org.apache.commons.compress.utils.TimeUtils.HUNDRED_NANOS_PER_MILLISECOND;
22  import static org.apache.commons.compress.utils.TimeUtils.WINDOWS_EPOCH_OFFSET;
23  import static org.apache.commons.compress.utils.TimeUtils.ntfsTimeToDate;
24  import static org.apache.commons.compress.utils.TimeUtils.ntfsTimeToFileTime;
25  import static org.apache.commons.compress.utils.TimeUtils.toDate;
26  import static org.apache.commons.compress.utils.TimeUtils.toFileTime;
27  import static org.apache.commons.compress.utils.TimeUtils.toNtfsTime;
28  import static org.junit.jupiter.api.Assertions.assertEquals;
29  import static org.junit.jupiter.api.Assertions.assertFalse;
30  import static org.junit.jupiter.api.Assertions.assertNull;
31  import static org.junit.jupiter.api.Assertions.assertTrue;
32  
33  import java.nio.file.attribute.FileTime;
34  import java.time.Instant;
35  import java.util.Date;
36  import java.util.stream.Stream;
37  
38  import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
39  import org.apache.commons.io.file.attribute.FileTimes;
40  import org.junit.jupiter.api.Test;
41  import org.junit.jupiter.params.ParameterizedTest;
42  import org.junit.jupiter.params.provider.Arguments;
43  import org.junit.jupiter.params.provider.MethodSource;
44  
45  public class TimeUtilsTest {
46  
47      public static Stream<Arguments> dateToNtfsProvider() {
48          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),
49                  Arguments.of("1601-01-01T00:00:00.001Z", HUNDRED_NANOS_PER_MILLISECOND),
50                  Arguments.of("1601-01-01T00:00:00.001Z", HUNDRED_NANOS_PER_MILLISECOND + 1),
51                  Arguments.of("1601-01-01T00:00:00.000Z", HUNDRED_NANOS_PER_MILLISECOND - 1),
52                  Arguments.of("1600-12-31T23:59:59.999Z", -HUNDRED_NANOS_PER_MILLISECOND),
53                  Arguments.of("1600-12-31T23:59:59.999Z", -HUNDRED_NANOS_PER_MILLISECOND + 1),
54                  Arguments.of("1600-12-31T23:59:59.998Z", -HUNDRED_NANOS_PER_MILLISECOND - 1), Arguments.of("1970-01-01T00:00:00.000Z", -WINDOWS_EPOCH_OFFSET),
55                  Arguments.of("1970-01-01T00:00:00.000Z", -WINDOWS_EPOCH_OFFSET + 1),
56                  Arguments.of("1970-01-01T00:00:00.001Z", -WINDOWS_EPOCH_OFFSET + HUNDRED_NANOS_PER_MILLISECOND),
57                  Arguments.of("1969-12-31T23:59:59.999Z", -WINDOWS_EPOCH_OFFSET - 1),
58                  Arguments.of("1969-12-31T23:59:59.999Z", -WINDOWS_EPOCH_OFFSET - HUNDRED_NANOS_PER_MILLISECOND));
59      }
60  
61      public static Stream<Arguments> fileTimeToNtfsProvider() {
62          return Stream.of(Arguments.of("1601-01-01T00:00:00.0000000Z", 0), Arguments.of("1601-01-01T00:00:00.0000001Z", 1),
63                  Arguments.of("1600-12-31T23:59:59.9999999Z", -1), Arguments.of("1601-01-01T00:00:00.0010000Z", HUNDRED_NANOS_PER_MILLISECOND),
64                  Arguments.of("1601-01-01T00:00:00.0010001Z", HUNDRED_NANOS_PER_MILLISECOND + 1),
65                  Arguments.of("1601-01-01T00:00:00.0009999Z", HUNDRED_NANOS_PER_MILLISECOND - 1),
66                  Arguments.of("1600-12-31T23:59:59.9990000Z", -HUNDRED_NANOS_PER_MILLISECOND),
67                  Arguments.of("1600-12-31T23:59:59.9990001Z", -HUNDRED_NANOS_PER_MILLISECOND + 1),
68                  Arguments.of("1600-12-31T23:59:59.9989999Z", -HUNDRED_NANOS_PER_MILLISECOND - 1),
69                  Arguments.of("1970-01-01T00:00:00.0000000Z", -WINDOWS_EPOCH_OFFSET), Arguments.of("1970-01-01T00:00:00.0000001Z", -WINDOWS_EPOCH_OFFSET + 1),
70                  Arguments.of("1970-01-01T00:00:00.0010000Z", -WINDOWS_EPOCH_OFFSET + HUNDRED_NANOS_PER_MILLISECOND),
71                  Arguments.of("1969-12-31T23:59:59.9999999Z", -WINDOWS_EPOCH_OFFSET - 1),
72                  Arguments.of("1969-12-31T23:59:59.9990000Z", -WINDOWS_EPOCH_OFFSET - HUNDRED_NANOS_PER_MILLISECOND));
73      }
74  
75      public static Stream<Arguments> fileTimeToUnixTimeArguments() {
76          return Stream.of(Arguments.of(0L, "1970-01-01T00:00:00Z"), Arguments.of(1672141989L, "2022-12-27T11:53:09Z"),
77                  Arguments.of(Integer.MAX_VALUE, "2038-01-19T03:14:07Z"));
78      }
79  
80      public static Stream<Arguments> truncateFileTimeProvider() {
81          return Stream.of(Arguments.of("2022-05-10T18:25:33.123456789Z", "2022-05-10T18:25:33.1234567Z"),
82                  Arguments.of("1970-01-01T00:00:00.000000001Z", "1970-01-01T00:00:00.0000000Z"),
83                  Arguments.of("1970-01-01T00:00:00.000000010Z", "1970-01-01T00:00:00.0000000Z"),
84                  Arguments.of("1970-01-01T00:00:00.000000199Z", "1970-01-01T00:00:00.0000001Z"),
85                  Arguments.of("1969-12-31T23:59:59.999999999Z", "1969-12-31T23:59:59.9999999Z"),
86                  Arguments.of("1969-12-31T23:59:59.000000001Z", "1969-12-31T23:59:59.0000000Z"),
87                  Arguments.of("1969-12-31T23:59:59.000000010Z", "1969-12-31T23:59:59.0000000Z"),
88                  Arguments.of("1969-12-31T23:59:59.000000199Z", "1969-12-31T23:59:59.0000001Z"));
89      }
90  
91      /**
92       * Truncates a FileTime to 100-nanosecond precision.
93       *
94       * @param fileTime the FileTime to be truncated.
95       * @return the truncated FileTime.
96       */
97      public static FileTime truncateToHundredNanos(final FileTime fileTime) {
98          final Instant instant = fileTime.toInstant();
99          return FileTime.from(Instant.ofEpochSecond(instant.getEpochSecond(), instant.getNano() / 100 * 100));
100     }
101 
102     @Test
103     void testIsUnixTime() {
104         assertTrue(TimeUtils.isUnixTime(null));
105         assertTrue(TimeUtils.isUnixTime(FileTime.from(Instant.parse("2022-12-27T12:45:22Z"))));
106         assertTrue(TimeUtils.isUnixTime(FileTime.from(Instant.parse("2038-01-19T03:14:07Z"))));
107         assertTrue(TimeUtils.isUnixTime(FileTime.from(Instant.parse("1901-12-13T23:14:08Z"))));
108         assertFalse(TimeUtils.isUnixTime(FileTime.from(Instant.parse("1901-12-13T03:14:08Z"))));
109         assertFalse(TimeUtils.isUnixTime(FileTime.from(Instant.parse("2038-01-19T03:14:08Z"))));
110         assertFalse(TimeUtils.isUnixTime(FileTime.from(Instant.parse("2099-06-30T12:31:42Z"))));
111     }
112 
113     @ParameterizedTest
114     @MethodSource("dateToNtfsProvider")
115     void testJavaTimeToNtfsTime(final String instant, final long ntfsTime) {
116         final long ntfsMillis = Math.floorDiv(ntfsTime, HUNDRED_NANOS_PER_MILLISECOND) * HUNDRED_NANOS_PER_MILLISECOND;
117         final Date parsed = Date.from(Instant.parse(instant));
118         final long converted = toNtfsTime(parsed);
119         assertEquals(ntfsMillis, converted);
120         // ensuring the deprecated method still works
121         assertEquals(converted, SevenZArchiveEntry.javaTimeToNtfsTime(parsed));
122     }
123 
124     @ParameterizedTest
125     @MethodSource("fileTimeToNtfsProvider")
126     void testNtfsTimeToFileTime(final String instant, final long ntfsTime) {
127         final FileTime parsed = FileTime.from(Instant.parse(instant));
128         assertEquals(parsed, ntfsTimeToFileTime(ntfsTime));
129     }
130 
131     @ParameterizedTest
132     @MethodSource("dateToNtfsProvider")
133     void testNtfsTimeToJavaTime(final String instant, final long ntfsTime) {
134         final Date converted = ntfsTimeToDate(ntfsTime);
135         assertEquals(Instant.parse(instant), converted.toInstant());
136         // ensuring the deprecated method still works
137         assertEquals(converted, SevenZArchiveEntry.ntfsTimeToJavaTime(ntfsTime));
138     }
139 
140     @ParameterizedTest
141     @MethodSource("fileTimeToNtfsProvider")
142     void testToDate(final String instant, final long ignored) {
143         final Instant parsedInstant = Instant.parse(instant);
144         final FileTime parsedFileTime = FileTime.from(parsedInstant);
145         final Date parsedDate = Date.from(parsedInstant);
146         assertEquals(parsedDate, toDate(parsedFileTime));
147     }
148 
149     @Test
150     void testToDateNull() {
151         assertNull(toDate(null));
152     }
153 
154     @ParameterizedTest
155     @MethodSource("dateToNtfsProvider")
156     void testToFileTime(final String instant, final long ignored) {
157         final Instant parsedInstant = Instant.parse(instant);
158         final FileTime parsedFileTime = FileTime.from(parsedInstant);
159         final Date parsedDate = Date.from(parsedInstant);
160         assertEquals(parsedFileTime, toFileTime(parsedDate));
161     }
162 
163     @Test
164     void testToFileTimeNull() {
165         assertNull(toFileTime(null));
166     }
167 
168     @ParameterizedTest
169     @MethodSource("fileTimeToNtfsProvider")
170     void testToNtfsTime(final String instant, final long ntfsTime) {
171         final FileTime parsed = FileTime.from(Instant.parse(instant));
172         assertEquals(ntfsTime, toNtfsTime(parsed));
173     }
174 
175     @ParameterizedTest
176     @MethodSource("fileTimeToUnixTimeArguments")
177     void testToUnixTime(final long expectedUnixTime, final String instant) {
178         assertEquals(expectedUnixTime, FileTimes.toUnixTime(FileTime.from(Instant.parse(instant))));
179     }
180 
181     @ParameterizedTest
182     @MethodSource("truncateFileTimeProvider")
183     void testTruncateToHundredNanos(final String original, final String truncated) {
184         final FileTime originalTime = FileTime.from(Instant.parse(original));
185         final FileTime truncatedTime = FileTime.from(Instant.parse(truncated));
186         assertEquals(truncatedTime, TimeUtils.truncateToHundredNanos(originalTime));
187     }
188 
189     @ParameterizedTest
190     @MethodSource("fileTimeToUnixTimeArguments")
191     void testUnixTimeToFileTime(final long unixTime, final String expectedInstant) {
192         assertEquals(Instant.parse(expectedInstant), TimeUtils.unixTimeToFileTime(unixTime).toInstant());
193     }
194 }