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 040a649 Add and use PathUtils.isNewer(Path, ChronoZonedDateTime, LinkOption...) for more precision. 040a649 is described below commit 040a649d19cc8f61b846592d73ba584193c6fd74 Author: Gary Gregory <gardgreg...@gmail.com> AuthorDate: Tue Sep 14 17:15:31 2021 -0400 Add and use PathUtils.isNewer(Path, ChronoZonedDateTime, LinkOption...) for more precision. - Add and use PathUtils.isNewer(Path, Path) for more precision. - Add and use FileUtils.isNewer(File, FileTime) for more precision. - Tailer uses more precise file time checks. --- src/changes/changes.xml | 9 ++- src/main/java/org/apache/commons/io/FileUtils.java | 44 ++++++++++- .../java/org/apache/commons/io/file/PathUtils.java | 90 ++++++++++++++++------ .../commons/io/file/attribute/FileTimes.java | 71 ++++++++++++++++- .../java/org/apache/commons/io/input/Tailer.java | 9 ++- .../commons/io/FileUtilsFileNewerTestCase.java | 73 ++++++++---------- .../commons/io/file/attribute/FileTimesTest.java | 44 +++++++++++ 7 files changed, 266 insertions(+), 74 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 010208d..4d34fdf 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -176,8 +176,13 @@ The <action> type attribute can be add,update,fix,remove. Add FileUtils.current(). </action> <action dev="ggregory" type="add" due-to="Gary Gregory"> - Add and use PathUtils.setLastModifiedTime(Path) for better precision. - Add and use PathUtils.setLastModifiedTime(Path, Path) for better precision. + Add and use PathUtils.setLastModifiedTime(Path) for more precision. + Add and use PathUtils.setLastModifiedTime(Path, Path) for more precision. + </action> + <action dev="ggregory" type="add" due-to="Gary Gregory"> + Add and use PathUtils.isNewer(Path, ChronoZonedDateTime, LinkOption...) for more precision. + Add and use PathUtils.isNewer(Path, Path) for more precision. + Add and use FileUtils.isNewer(File, FileTime) for more precision. </action> <!-- UPDATE --> <action dev="ggregory" type="add" due-to="Gary Gregory"> diff --git a/src/main/java/org/apache/commons/io/FileUtils.java b/src/main/java/org/apache/commons/io/FileUtils.java index 2a97da3..a14ea28 100644 --- a/src/main/java/org/apache/commons/io/FileUtils.java +++ b/src/main/java/org/apache/commons/io/FileUtils.java @@ -1561,6 +1561,22 @@ public class FileUtils { } /** + * Tests if the specified {@code File} is newer than the specified {@code FileTime}. + * + * @param file the {@code File} of which the modification date must be compared. + * @param fileTime the file time reference. + * @return true if the {@code File} exists and has been modified after the given {@code FileTime}. + * @throws IOException if an I/O error occurs. + * @throws NullPointerException if the file or local date is {@code null}. + * + * @since 2.12.0 + */ + public static boolean isFileNewer(final File file, final FileTime fileTime) throws IOException { + Objects.requireNonNull(file, "file"); + return PathUtils.isNewer(file.toPath(), fileTime); + } + + /** * Tests if the specified {@code File} is newer than the specified {@code ChronoLocalDate} * at the specified time. * @@ -1637,7 +1653,12 @@ public class FileUtils { */ public static boolean isFileNewer(final File file, final ChronoZonedDateTime<?> chronoZonedDateTime) { Objects.requireNonNull(chronoZonedDateTime, "chronoZonedDateTime"); - return isFileNewer(file, chronoZonedDateTime.toInstant()); + try { + return PathUtils.isNewer(file.toPath(), chronoZonedDateTime); + } catch (final IOException e) { + // TODO Update method signature + throw new UncheckedIOException(e); + } } /** @@ -1666,7 +1687,12 @@ public class FileUtils { */ public static boolean isFileNewer(final File file, final File reference) { requireExists(reference, "reference"); - return isFileNewer(file, lastModifiedUnchecked(reference)); + try { + return PathUtils.isNewer(file.toPath(), reference.toPath()); + } catch (final IOException e) { + // TODO Update method signature + throw new UncheckedIOException(e); + } } /** @@ -1681,7 +1707,12 @@ public class FileUtils { */ public static boolean isFileNewer(final File file, final Instant instant) { Objects.requireNonNull(instant, "instant"); - return isFileNewer(file, instant.toEpochMilli()); + try { + return PathUtils.isNewer(file.toPath(), instant); + } catch (final IOException e) { + // TODO Update method signature + throw new UncheckedIOException(e); + } } /** @@ -1695,7 +1726,12 @@ public class FileUtils { */ public static boolean isFileNewer(final File file, final long timeMillis) { Objects.requireNonNull(file, "file"); - return file.exists() && lastModifiedUnchecked(file) > timeMillis; + try { + return PathUtils.isNewer(file.toPath(), timeMillis); + } catch (final IOException e) { + // TODO Update method signature + throw new UncheckedIOException(e); + } } /** diff --git a/src/main/java/org/apache/commons/io/file/PathUtils.java b/src/main/java/org/apache/commons/io/file/PathUtils.java index fc77ed0..ab63893 100644 --- a/src/main/java/org/apache/commons/io/file/PathUtils.java +++ b/src/main/java/org/apache/commons/io/file/PathUtils.java @@ -49,6 +49,7 @@ import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.time.Duration; import java.time.Instant; +import java.time.chrono.ChronoZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -220,6 +221,20 @@ public final class PathUtils { } /** + * Compares the specified {@code Path}'s last modified time to the given file time. + * + * @param file the {@code Path} of which the modification date must be compared + * @param fileTime the time reference. + * @param options options indicating how symbolic links are handled been modified after the given time reference. + * @return See {@link FileTime#compareTo(FileTime)} + * @throws IOException if an I/O error occurs. + * @throws NullPointerException if the file is {@code null} + */ + private static int compareLastModifiedTimeTo(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException { + return Files.getLastModifiedTime(file, options).compareTo(fileTime); + } + + /** * Copies a directory to another directory. * * @param sourceDirectory The source directory. @@ -511,7 +526,7 @@ public final class PathUtils { if (path1 == null || path2 == null) { return false; } - if (Files.notExists(path1) && Files.notExists(path2)) { + if (notExists(path1) && notExists(path2)) { return true; } final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE, linkOptions, fileVisitOption); @@ -769,20 +784,34 @@ public final class PathUtils { * Tests if the specified {@code Path} is newer than the specified time reference. * * @param file the {@code Path} of which the modification date must be compared + * @param czdt the time reference. + * @param options options indicating how symbolic links are handled been modified after the given time reference. + * @return true if the {@code Path} exists and has been modified after the given time reference. + * @throws IOException if an I/O error occurs. + * @throws NullPointerException if the file is {@code null} + * @since 2.12.0 + */ + public static boolean isNewer(final Path file, final ChronoZonedDateTime<?> czdt, final LinkOption... options) throws IOException { + Objects.requireNonNull(czdt, "czdt"); + return isNewer(file, czdt.toInstant(), options); + } + + /** + * Tests if the specified {@code Path} is newer than the specified time reference. + * + * @param file the {@code Path} of which the modification date must be compared * @param fileTime the time reference. - * @param options options indicating how symbolic links are handled * @return true if the {@code Path} exists and has - * been modified after the given time reference. + * @param options options indicating how symbolic links are handled been modified after the given time reference. * @return true if the {@code Path} exists and has been modified after the given time reference. * @throws IOException if an I/O error occurs. * @throws NullPointerException if the file is {@code null} * @since 2.12.0 */ public static boolean isNewer(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException { - Objects.requireNonNull(file, "file"); - if (Files.notExists(file)) { + if (notExists(file)) { return false; } - return Files.getLastModifiedTime(file, options).compareTo(fileTime) > 0; + return compareLastModifiedTimeTo(file, fileTime, options) > 0; } /** @@ -798,11 +827,7 @@ public final class PathUtils { * @since 2.12.0 */ public static boolean isNewer(final Path file, final Instant instant, final LinkOption... options) throws IOException { - Objects.requireNonNull(file, "file"); - if (Files.notExists(file)) { - return false; - } - return Files.getLastModifiedTime(file, options).toInstant().isAfter(instant); + return isNewer(file, FileTime.from(instant), options); } /** @@ -822,6 +847,20 @@ public final class PathUtils { } /** + * Tests if the specified {@code Path} is newer than the reference {@code Path}. + * + * @param file the {@code File} of which the modification date must be compared. + * @param reference the {@code File} of which the modification date is used. + * @return true if the {@code File} exists and has been modified more + * recently than the reference {@code File}. + * @throws IOException if an I/O error occurs. + * @since 2.12.0 + */ + public static boolean isNewer(final Path file, final Path reference) throws IOException { + return isNewer(file, Files.getLastModifiedTime(reference)); + } + + /** * Tests whether the specified {@code Path} is a regular file or not. Implemented as a null-safe delegate to * {@code Files.isRegularFile(Path path, LinkOption... options)}. * @@ -837,6 +876,7 @@ public final class PathUtils { return path != null && Files.isRegularFile(path, options); } + /** * Creates a new DirectoryStream for Paths rooted at the given directory. * @@ -869,6 +909,10 @@ public final class PathUtils { // @formatter:on } + private static boolean notExists(final Path file) { + return Files.notExists(Objects.requireNonNull(file, "file")); + } + /** * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}. * @@ -943,6 +987,17 @@ public final class PathUtils { } /** + * Sets the last modified time of the given file path to now. + * + * @param path The file path to set. + * @throws IOException if an I/O error occurs. + * @since 2.12.0 + */ + public static void setLastModifiedTime(final Path path) throws IOException { + Files.setLastModifiedTime(path, FileTime.from(Instant.now())); + } + + /** * Sets the given {@code targetFile}'s last modified time to the value from {@code sourceFile}. * * @param sourceFile The source path to query. @@ -958,17 +1013,6 @@ public final class PathUtils { } /** - * Sets the last modified time of the given file path to now. - * - * @param path The file path to set. - * @throws IOException if an I/O error occurs. - * @since 2.12.0 - */ - public static void setLastModifiedTime(final Path path) throws IOException { - Files.setLastModifiedTime(path, FileTime.from(Instant.now())); - } - - /** * Sets the given Path to the {@code readOnly} value. * <p> * This behavior is OS dependent. @@ -1109,7 +1153,7 @@ public final class PathUtils { * @throws NullPointerException if the file is {@code null}. * @since 2.12.0 */ - public static boolean waitFor(final Path file, final Duration timeout, LinkOption... options) { + public static boolean waitFor(final Path file, final Duration timeout, final LinkOption... options) { Objects.requireNonNull(file, "file"); final Instant finishInstant = Instant.now().plus(timeout); boolean interrupted = false; 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 72d92a9..ffc4c22 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,18 +22,85 @@ import java.time.Instant; /** * Helps use {@link FileTime}. + * + * @since 2.12.0 */ public class FileTimes { /** - * Constant for the 1970-01-01T00:00:00Z epoch time stamp attribute. + * Constant for the {@code 1970-01-01T00:00:00Z} epoch time stamp attribute. * * @see Instant#EPOCH */ public static final FileTime EPOCH = FileTime.from(Instant.EPOCH); + /** + * Subtracts milliseconds from a source FileTime. + * + * @param fileTime The source FileTime. + * @param millisToSubtract The milliseconds to subtract. + * @return The resulting FileTime. + */ + public static FileTime minusMillis(final FileTime fileTime, final long millisToSubtract) { + return FileTime.from(fileTime.toInstant().minusMillis(millisToSubtract)); + } + + /** + * Subtracts nanoseconds from a source FileTime. + * + * @param fileTime The source FileTime. + * @param nanosToSubtract The nanoseconds to subtract. + * @return The resulting FileTime. + */ + public static FileTime minusNanos(final FileTime fileTime, final long nanosToSubtract) { + return FileTime.from(fileTime.toInstant().minusNanos(nanosToSubtract)); + } + + /** + * Subtracts seconds from a source FileTime. + * + * @param fileTime The source FileTime. + * @param secondsToSubtract The seconds to subtract. + * @return The resulting FileTime. + */ + public static FileTime minusSeconds(final FileTime fileTime, final long secondsToSubtract) { + return FileTime.from(fileTime.toInstant().minusSeconds(secondsToSubtract)); + } + + /** + * Adds milliseconds to a source FileTime. + * + * @param fileTime The source FileTime. + * @param millisToAdd The milliseconds to add. + * @return The resulting FileTime. + */ + public static FileTime plusMillis(final FileTime fileTime, final long millisToAdd) { + return FileTime.from(fileTime.toInstant().plusMillis(millisToAdd)); + } + + /** + * Adds nanoseconds from a source FileTime. + * + * @param fileTime The source FileTime. + * @param nanosToSubtract The nanoseconds to subtract. + * @return The resulting FileTime. + */ + public static FileTime plusNanos(final FileTime fileTime, final long nanosToSubtract) { + return FileTime.from(fileTime.toInstant().plusNanos(nanosToSubtract)); + } + + /** + * Adds seconds to a source FileTime. + * + * @param fileTime The source FileTime. + * @param secondsToAdd The seconds to add. + * @return The resulting FileTime. + */ + public static FileTime plusSeconds(final FileTime fileTime, final long secondsToAdd) { + return FileTime.from(fileTime.toInstant().plusSeconds(secondsToAdd)); + } + private FileTimes() { // No instances. } - } diff --git a/src/main/java/org/apache/commons/io/input/Tailer.java b/src/main/java/org/apache/commons/io/input/Tailer.java index fc1e647..d8ca79c 100644 --- a/src/main/java/org/apache/commons/io/input/Tailer.java +++ b/src/main/java/org/apache/commons/io/input/Tailer.java @@ -26,6 +26,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.Charset; +import java.nio.file.attribute.FileTime; import java.time.Duration; import org.apache.commons.io.FileUtils; @@ -437,7 +438,7 @@ public class Tailer implements Runnable { public void run() { RandomAccessFile reader = null; try { - long last = 0; // The last time the file was checked for changes + FileTime last = FileTime.fromMillis(0); // The last time the file was checked for changes long position = 0; // position within the file // Open the file while (getRun() && reader == null) { @@ -451,7 +452,7 @@ public class Tailer implements Runnable { } else { // The current position in the file position = end ? file.length() : 0; - last = FileUtils.lastModified(file); + last = FileUtils.lastModifiedFileTime(file); reader.seek(position); } } @@ -486,7 +487,7 @@ public class Tailer implements Runnable { if (length > position) { // The file has more content than it did last time position = readLines(reader); - last = FileUtils.lastModified(file); + last = FileUtils.lastModifiedFileTime(file); } else if (newer) { /* * This can happen if the file is truncated or overwritten with the exact same length of @@ -497,7 +498,7 @@ public class Tailer implements Runnable { // Now we can read new lines position = readLines(reader); - last = FileUtils.lastModified(file); + last = FileUtils.lastModifiedFileTime(file); } if (reOpen && reader != null) { reader.close(); diff --git a/src/test/java/org/apache/commons/io/FileUtilsFileNewerTestCase.java b/src/test/java/org/apache/commons/io/FileUtilsFileNewerTestCase.java index ead1179..82badd3 100644 --- a/src/test/java/org/apache/commons/io/FileUtilsFileNewerTestCase.java +++ b/src/test/java/org/apache/commons/io/FileUtilsFileNewerTestCase.java @@ -23,8 +23,11 @@ import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.attribute.FileTime; import java.util.Date; +import java.util.concurrent.TimeUnit; +import org.apache.commons.io.file.attribute.FileTimes; import org.apache.commons.io.test.TestUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -50,25 +53,22 @@ public class FileUtilsFileNewerTestCase { testFile1 = new File(temporaryFolder, "file1-test.txt"); testFile2 = new File(temporaryFolder, "file2-test.txt"); if (!testFile1.getParentFile().exists()) { - throw new IOException("Cannot create file " + testFile1 - + " as the parent directory does not exist"); + throw new IOException("Cannot create file " + testFile1 + " as the parent directory does not exist"); } - try (final BufferedOutputStream output1 = - new BufferedOutputStream(Files.newOutputStream(testFile1.toPath()))) { + try (final BufferedOutputStream output1 = new BufferedOutputStream(Files.newOutputStream(testFile1.toPath()))) { TestUtils.generateTestData(output1, FILE1_SIZE); } if (!testFile2.getParentFile().exists()) { - throw new IOException("Cannot create file " + testFile2 - + " as the parent directory does not exist"); + throw new IOException("Cannot create file " + testFile2 + " as the parent directory does not exist"); } - try (final BufferedOutputStream output = - new BufferedOutputStream(Files.newOutputStream(testFile2.toPath()))) { + try (final BufferedOutputStream output = new BufferedOutputStream(Files.newOutputStream(testFile2.toPath()))) { TestUtils.generateTestData(output, FILE2_SIZE); } } /** * Tests the {@code isFileNewer(File, *)} methods which a "normal" file. + * * @throws IOException * * @see FileUtils#isFileNewer(File, long) @@ -81,16 +81,17 @@ public class FileUtilsFileNewerTestCase { throw new IllegalStateException("The testFile1 should exist"); } - final long fileLastModified = FileUtils.lastModified(testFile1); - final long TWO_SECOND = 2000; + final FileTime fileLastModified = Files.getLastModifiedTime(testFile1.toPath()); + final long TWO_SECOND = 2; - testIsFileNewer("two second earlier is not newer" , testFile1, fileLastModified + TWO_SECOND, false); - testIsFileNewer("same time is not newer" , testFile1, fileLastModified, false); - testIsFileNewer("two second later is newer" , testFile1, fileLastModified - TWO_SECOND, true); + testIsFileNewer("two second earlier is not newer", testFile1, FileTimes.plusSeconds(fileLastModified, TWO_SECOND), false); + testIsFileNewer("same time is not newer", testFile1, fileLastModified, false); + testIsFileNewer("two second later is newer", testFile1, FileTimes.minusSeconds(fileLastModified, TWO_SECOND), true); } /** * Tests the {@code isFileNewer(File, *)} methods which a not existing file. + * * @throws IOException if an I/O error occurs. * * @see FileUtils#isFileNewer(File, long) @@ -104,7 +105,7 @@ public class FileUtilsFileNewerTestCase { throw new IllegalStateException("The imaginary File exists"); } - testIsFileNewer("imaginary file can be newer" , imaginaryFile, FileUtils.lastModified(testFile2), false); + testIsFileNewer("imaginary file can be newer", imaginaryFile, FileUtils.lastModifiedFileTime(testFile2), false); } /** @@ -115,63 +116,57 @@ public class FileUtilsFileNewerTestCase { * <li>a {@code Date} which represents the time reference</li> * <li>a temporary file with the same last modification date as the time reference</li> * </ul> - * Then compares (with the needed {@code isFileNewer} method) the last modification date of - * the specified file with the specified time reference, the created {@code Date} and the temporary - * file. - * <br> + * Then compares (with the needed {@code isFileNewer} method) the last modification date of the specified file with the + * specified time reference, the created {@code Date} and the temporary file. + * <p> * The test is successful if the three comparisons return the specified wanted result. * * @param description describes the tested situation * @param file the file of which the last modification date is compared - * @param time the time reference measured in milliseconds since the epoch + * @param fileTime the time reference measured in milliseconds since the epoch * @param wantedResult the expected result * @throws IOException if an I/O error occurs. - * - * @see FileUtils#isFileNewer(File, long) - * @see FileUtils#isFileNewer(File, Date) - * @see FileUtils#isFileNewer(File, File) */ - protected void testIsFileNewer(final String description, final File file, final long time, final boolean wantedResult) throws IOException { - assertEquals(wantedResult, FileUtils.isFileNewer(file, time), description + " - time"); - assertEquals(wantedResult, FileUtils.isFileNewer(file, new Date(time)), description + " - date"); + protected void testIsFileNewer(final String description, final File file, final FileTime fileTime, final boolean wantedResult) throws IOException { + assertEquals(wantedResult, FileUtils.isFileNewer(file, fileTime), () -> description + " - FileTime"); + assertEquals(wantedResult, FileUtils.isFileNewer(file, fileTime.toInstant()), () -> description + " - Instant"); final File temporaryFile = testFile2; - - temporaryFile.setLastModified(time); - assertEquals(time, FileUtils.lastModified(temporaryFile), "The temporary file hasn't the right last modification date"); - assertEquals(wantedResult, FileUtils.isFileNewer(file, temporaryFile), description + " - file"); + Files.setLastModifiedTime(temporaryFile.toPath(), fileTime); + assertEquals(fileTime, Files.getLastModifiedTime(temporaryFile.toPath()), "The temporary file hasn't the right last modification date"); + assertEquals(wantedResult, FileUtils.isFileNewer(file, temporaryFile), () -> description + " - file"); } /** * Tests the {@code isFileNewer(File, long)} method without specifying a {@code File}. - * <br> + * <p> * The test is successful if the method throws an {@code IllegalArgumentException}. + * </p> */ @Test public void testIsFileNewerNoFile() { - assertThrows(NullPointerException.class, () -> FileUtils.isFileNewer(null,0), - "file"); + assertThrows(NullPointerException.class, () -> FileUtils.isFileNewer(null, 0), "file"); } /** * Tests the {@code isFileNewer(File, Date)} method without specifying a {@code Date}. - * <br> + * <p> * The test is successful if the method throws an {@code IllegalArgumentException}. + * </p> */ @Test public void testIsFileNewerNoDate() { - assertThrows(NullPointerException.class, () -> FileUtils.isFileNewer(testFile1, (Date) null), - "date"); + assertThrows(NullPointerException.class, () -> FileUtils.isFileNewer(testFile1, (Date) null), "date"); } /** * Tests the {@code isFileNewer(File, File)} method without specifying a reference {@code File}. - * <br> + * <p> * The test is successful if the method throws an {@code IllegalArgumentException}. + * </p> */ @Test public void testIsFileNewerNoFileReference() { - assertThrows(NullPointerException.class, () -> FileUtils.isFileNewer(testFile1, (File) null), - "reference"); + assertThrows(NullPointerException.class, () -> FileUtils.isFileNewer(testFile1, (File) null), "reference"); } } 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 6e1aeea..a96565d 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 @@ -19,6 +19,8 @@ package org.apache.commons.io.file.attribute; import static org.junit.jupiter.api.Assertions.assertEquals; +import java.time.Instant; + import org.junit.jupiter.api.Test; /** @@ -27,8 +29,50 @@ import org.junit.jupiter.api.Test; public class FileTimesTest { @Test + public void PlusMinusMillis() { + final int millis = 2; + assertEquals(Instant.EPOCH.plusMillis(millis), FileTimes.plusMillis(FileTimes.EPOCH, millis).toInstant()); + assertEquals(Instant.EPOCH, FileTimes.plusMillis(FileTimes.EPOCH, 0).toInstant()); + } + + @Test public void testEpoch() { assertEquals(0, FileTimes.EPOCH.toMillis()); } + @Test + public void testMinusMillis() { + final int millis = 2; + assertEquals(Instant.EPOCH.minusMillis(millis), FileTimes.minusMillis(FileTimes.EPOCH, millis).toInstant()); + assertEquals(Instant.EPOCH, FileTimes.minusMillis(FileTimes.EPOCH, 0).toInstant()); + } + + @Test + public void testMinusNanos() { + final int millis = 2; + assertEquals(Instant.EPOCH.minusNanos(millis), FileTimes.minusNanos(FileTimes.EPOCH, millis).toInstant()); + assertEquals(Instant.EPOCH, FileTimes.minusNanos(FileTimes.EPOCH, 0).toInstant()); + } + + @Test + public void testMinusSeconds() { + final int seconds = 2; + assertEquals(Instant.EPOCH.minusSeconds(seconds), FileTimes.minusSeconds(FileTimes.EPOCH, seconds).toInstant()); + assertEquals(Instant.EPOCH, FileTimes.minusSeconds(FileTimes.EPOCH, 0).toInstant()); + } + + @Test + public void testPlusNanos() { + final int millis = 2; + assertEquals(Instant.EPOCH.plusNanos(millis), FileTimes.plusNanos(FileTimes.EPOCH, millis).toInstant()); + assertEquals(Instant.EPOCH, FileTimes.plusNanos(FileTimes.EPOCH, 0).toInstant()); + } + + @Test + public void testPlusSeconds() { + final int seconds = 2; + assertEquals(Instant.EPOCH.plusSeconds(seconds), FileTimes.plusSeconds(FileTimes.EPOCH, seconds).toInstant()); + assertEquals(Instant.EPOCH, FileTimes.plusSeconds(FileTimes.EPOCH, 0).toInstant()); + } + }