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-compress.git


The following commit(s) were added to refs/heads/master by this push:
     new 650ad055 COMPRESS-614: Use FileTime in SevenZArchiveEntry (#256)
650ad055 is described below

commit 650ad0555e3702177c7403544ae8850f4ebbc9b1
Author: Andre Brait <andrebr...@gmail.com>
AuthorDate: Thu Dec 8 02:10:16 2022 +0100

    COMPRESS-614: Use FileTime in SevenZArchiveEntry (#256)
    
    * COMPRESS-612: Add deprecation notice
    
    * COMPRESS-614: Move NTFS <-> FileTime to ZipUtil
    
    * COMPRESS-614: Use FileTime in SevenZArchiveEntry
    
    * COMPRESS-614: Make Sonar happy
    
    * COMPRESS-614: Reuse time operations in TimeUtils
    
    * COMPRESS-614: Fix NTFS time conversion
    
    * COMPRESS-614: Remove unused imports from ZipUtil
    
    * COMPRESS-614: Revert some documentation changes
    
    * COMPRESS-614: Add tests for TimeUtils
    
    * COMPRESS-614: Refine tests
    
    * COMPRESS-614: Add missing license text
    
    * COMPRESS-614: Add missing since tags
    
    * COMPRESS-614: Workaround for Java 17 on *NIX OSes
    
    Java 17 supports nanosecond-precision FileTimes on Linux and macOS
    NTFS dates support only 100ns units, so truncate dates when comparing
    
    * COMPRESS-614: Add missing test cases
    
    * Update Javadoc since tags
    
    * Update Javadoc tag
    
    * Update Javadoc tags
    
    * Update TimeUtils.java
    
    Co-authored-by: Gary Gregory <garydgreg...@users.noreply.github.com>
---
 .../archivers/sevenz/SevenZArchiveEntry.java       | 160 +++++++++++++-----
 .../archivers/sevenz/SevenZOutputFile.java         |  30 +++-
 .../compress/archivers/tar/TarArchiveEntry.java    |  11 +-
 .../commons/compress/archivers/zip/X000A_NTFS.java | 105 ++++++++++--
 .../apache/commons/compress/utils/TimeUtils.java   | 141 ++++++++++++++++
 .../commons/compress/archivers/SevenZTestCase.java |  26 ++-
 .../archivers/sevenz/SevenZArchiveEntryTest.java   |  28 ++++
 .../compress/archivers/sevenz/SevenZFileTest.java  |  72 +++++++-
 .../archivers/sevenz/SevenZOutputFileTest.java     |  36 +++-
 .../compress/archivers/zip/X000A_NTFSTest.java     |  42 +++++
 .../commons/compress/utils/TimeUtilsTest.java      | 181 +++++++++++++++++++++
 src/test/resources/times.7z                        | Bin 0 -> 3934 bytes
 12 files changed, 737 insertions(+), 95 deletions(-)

diff --git 
a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZArchiveEntry.java
 
b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZArchiveEntry.java
index 74d4276e..b303085f 100644
--- 
a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZArchiveEntry.java
+++ 
b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZArchiveEntry.java
@@ -18,15 +18,15 @@
 package org.apache.commons.compress.archivers.sevenz;
 
 import java.util.Arrays;
-import java.util.Calendar;
+import java.nio.file.attribute.FileTime;
 import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.Objects;
-import java.util.TimeZone;
 
 import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.utils.TimeUtils;
 
 /**
  * An entry in a 7z archive.
@@ -42,9 +42,9 @@ public class SevenZArchiveEntry implements ArchiveEntry {
     private boolean hasCreationDate;
     private boolean hasLastModifiedDate;
     private boolean hasAccessDate;
-    private long creationDate;
-    private long lastModifiedDate;
-    private long accessDate;
+    private FileTime creationDate;
+    private FileTime lastModifiedDate;
+    private FileTime accessDate;
     private boolean hasWindowsAttributes;
     private int windowsAttributes;
     private boolean hasCrc;
@@ -148,13 +148,26 @@ public class SevenZArchiveEntry implements ArchiveEntry {
 
     /**
      * Gets the creation date.
-     * @throws UnsupportedOperationException if the entry hasn't got a
-     * creation date.
-     * @return the creation date
+     * This is equivalent to {@link SevenZArchiveEntry#getCreationTime()}, but 
precision is truncated to milliseconds.
+     *
+     * @throws UnsupportedOperationException if the entry hasn't got a 
creation date.
+     * @return the new creation date
+     * @see SevenZArchiveEntry#getCreationTime()
      */
     public Date getCreationDate() {
+        return TimeUtils.fileTimeToDate(getCreationTime());
+    }
+
+    /**
+     * Gets the creation time.
+     *
+     * @throws UnsupportedOperationException if the entry hasn't got a 
creation time.
+     * @return the creation time
+     * @since 1.23
+     */
+    public FileTime getCreationTime() {
         if (hasCreationDate) {
-            return ntfsTimeToJavaTime(creationDate);
+            return creationDate;
         }
         throw new UnsupportedOperationException(
                 "The entry doesn't have this timestamp");
@@ -166,17 +179,29 @@ public class SevenZArchiveEntry implements ArchiveEntry {
      * @param ntfsCreationDate the creation date
      */
     public void setCreationDate(final long ntfsCreationDate) {
-        this.creationDate = ntfsCreationDate;
+        this.creationDate = TimeUtils.ntfsTimeToFileTime(ntfsCreationDate);
     }
 
     /**
-     * Sets the creation date,
-     * @param creationDate the creation date
+     * Sets the creation date.
+     *
+     * @param creationDate the new creation date
+     * @see SevenZArchiveEntry#setCreationTime(FileTime)
      */
     public void setCreationDate(final Date creationDate) {
-        hasCreationDate = creationDate != null;
+        setCreationTime(TimeUtils.dateToFileTime(creationDate));
+    }
+
+    /**
+     * Sets the creation time.
+     *
+     * @param time the new creation time
+     * @since 1.23
+     */
+    public void setCreationTime(final FileTime time) {
+        hasCreationDate = time != null;
         if (hasCreationDate) {
-            this.creationDate = javaTimeToNtfsTime(creationDate);
+            this.creationDate = time;
         }
     }
 
@@ -199,14 +224,27 @@ public class SevenZArchiveEntry implements ArchiveEntry {
 
     /**
      * Gets the last modified date.
-     * @throws UnsupportedOperationException if the entry hasn't got a
-     * last modified date.
+     * This is equivalent to {@link SevenZArchiveEntry#getLastModifiedTime()}, 
but precision is truncated to milliseconds.
+     *
+     * @throws UnsupportedOperationException if the entry hasn't got a last 
modified date.
      * @return the last modified date
+     * @see SevenZArchiveEntry#getLastModifiedTime()
      */
     @Override
     public Date getLastModifiedDate() {
+        return TimeUtils.fileTimeToDate(getLastModifiedTime());
+    }
+
+    /**
+     * Gets the last modified time.
+     *
+     * @throws UnsupportedOperationException if the entry hasn't got a last 
modified time.
+     * @return the last modified time
+     * @since 1.23
+     */
+    public FileTime getLastModifiedTime() {
         if (hasLastModifiedDate) {
-            return ntfsTimeToJavaTime(lastModifiedDate);
+            return lastModifiedDate;
         }
         throw new UnsupportedOperationException(
                 "The entry doesn't have this timestamp");
@@ -218,17 +256,29 @@ public class SevenZArchiveEntry implements ArchiveEntry {
      * @param ntfsLastModifiedDate the last modified date
      */
     public void setLastModifiedDate(final long ntfsLastModifiedDate) {
-        this.lastModifiedDate = ntfsLastModifiedDate;
+        this.lastModifiedDate = 
TimeUtils.ntfsTimeToFileTime(ntfsLastModifiedDate);
     }
 
     /**
-     * Sets the last modified date,
-     * @param lastModifiedDate the last modified date
+     * Sets the last modified date.
+     *
+     * @param lastModifiedDate the new last modified date
+     * @see SevenZArchiveEntry#setLastModifiedTime(FileTime)
      */
     public void setLastModifiedDate(final Date lastModifiedDate) {
-        hasLastModifiedDate = lastModifiedDate != null;
+        setLastModifiedTime(TimeUtils.dateToFileTime(lastModifiedDate));
+    }
+
+    /**
+     * Sets the last modified time.
+     *
+     * @param time the new last modified time
+     * @since 1.23
+     */
+    public void setLastModifiedTime(final FileTime time) {
+        hasLastModifiedDate = time != null;
         if (hasLastModifiedDate) {
-            this.lastModifiedDate = javaTimeToNtfsTime(lastModifiedDate);
+            this.lastModifiedDate = time;
         }
     }
 
@@ -250,13 +300,26 @@ public class SevenZArchiveEntry implements ArchiveEntry {
 
     /**
      * Gets the access date.
-     * @throws UnsupportedOperationException if the entry hasn't got a
-     * access date.
+     * This is equivalent to {@link SevenZArchiveEntry#getAccessTime()}, but 
precision is truncated to milliseconds.
+     *
+     * @throws UnsupportedOperationException if the entry hasn't got an access 
date.
      * @return the access date
+     * @see SevenZArchiveEntry#getAccessTime()
      */
     public Date getAccessDate() {
+        return TimeUtils.fileTimeToDate(getAccessTime());
+    }
+
+    /**
+     * Gets the access time.
+     *
+     * @throws UnsupportedOperationException if the entry hasn't got an access 
time.
+     * @return the access time
+     * @since 1.23
+     */
+    public FileTime getAccessTime() {
         if (hasAccessDate) {
-            return ntfsTimeToJavaTime(accessDate);
+            return accessDate;
         }
         throw new UnsupportedOperationException(
                 "The entry doesn't have this timestamp");
@@ -268,17 +331,29 @@ public class SevenZArchiveEntry implements ArchiveEntry {
      * @param ntfsAccessDate the access date
      */
     public void setAccessDate(final long ntfsAccessDate) {
-        this.accessDate = ntfsAccessDate;
+        this.accessDate = TimeUtils.ntfsTimeToFileTime(ntfsAccessDate);
     }
 
     /**
-     * Sets the access date,
-     * @param accessDate the access date
+     * Sets the access date.
+     *
+     * @param accessDate the new access date
+     * @see SevenZArchiveEntry#setAccessTime(FileTime)
      */
     public void setAccessDate(final Date accessDate) {
-        hasAccessDate = accessDate != null;
+        setAccessTime(TimeUtils.dateToFileTime(accessDate));
+    }
+
+    /**
+     * Sets the access time.
+     *
+     * @param time the new access time
+     * @since 1.23
+     */
+    public void setAccessTime(final FileTime time) {
+        hasAccessDate = time != null;
         if (hasAccessDate) {
-            this.accessDate = javaTimeToNtfsTime(accessDate);
+            this.accessDate = time;
         }
     }
 
@@ -528,9 +603,9 @@ public class SevenZArchiveEntry implements ArchiveEntry {
             hasCreationDate == other.hasCreationDate &&
             hasLastModifiedDate == other.hasLastModifiedDate &&
             hasAccessDate == other.hasAccessDate &&
-            creationDate == other.creationDate &&
-            lastModifiedDate == other.lastModifiedDate &&
-            accessDate == other.accessDate &&
+            Objects.equals(creationDate, other.creationDate) &&
+            Objects.equals(lastModifiedDate, other.lastModifiedDate) &&
+            Objects.equals(accessDate, other.accessDate) &&
             hasWindowsAttributes == other.hasWindowsAttributes &&
             windowsAttributes == other.windowsAttributes &&
             hasCrc == other.hasCrc &&
@@ -546,27 +621,24 @@ public class SevenZArchiveEntry implements ArchiveEntry {
      * to Java time.
      * @param ntfsTime the NTFS time in 100 nanosecond units
      * @return the Java time
+     * @deprecated Use {@link TimeUtils#ntfsTimeToDate(long)} instead.
+     * @see TimeUtils#ntfsTimeToDate(long)
      */
+    @Deprecated
     public static Date ntfsTimeToJavaTime(final long ntfsTime) {
-        final Calendar ntfsEpoch = Calendar.getInstance();
-        ntfsEpoch.setTimeZone(TimeZone.getTimeZone("GMT+0"));
-        ntfsEpoch.set(1601, 0, 1, 0, 0, 0);
-        ntfsEpoch.set(Calendar.MILLISECOND, 0);
-        final long realTime = ntfsEpoch.getTimeInMillis() + (ntfsTime / 
(10*1000));
-        return new Date(realTime);
+        return TimeUtils.ntfsTimeToDate(ntfsTime);
     }
 
     /**
      * Converts Java time to NTFS time.
      * @param date the Java time
      * @return the NTFS time
+     * @deprecated Use {@link TimeUtils#dateToNtfsTime(Date)} instead.
+     * @see TimeUtils#dateToNtfsTime(Date)
      */
+    @Deprecated
     public static long javaTimeToNtfsTime(final Date date) {
-        final Calendar ntfsEpoch = Calendar.getInstance();
-        ntfsEpoch.setTimeZone(TimeZone.getTimeZone("GMT+0"));
-        ntfsEpoch.set(1601, 0, 1, 0, 0, 0);
-        ntfsEpoch.set(Calendar.MILLISECOND, 0);
-        return ((date.getTime() - ntfsEpoch.getTimeInMillis())* 1000 * 10);
+        return TimeUtils.dateToNtfsTime(date);
     }
 
     private boolean equalSevenZMethods(final Iterable<? extends 
SevenZMethodConfiguration> c1,
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
 
b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
index ba6de451..903e0df1 100644
--- 
a/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
+++ 
b/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.java
@@ -36,6 +36,7 @@ import java.nio.file.LinkOption;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -50,6 +51,7 @@ import java.util.zip.CRC32;
 
 import org.apache.commons.compress.archivers.ArchiveEntry;
 import org.apache.commons.compress.utils.CountingOutputStream;
+import org.apache.commons.compress.utils.TimeUtils;
 
 /**
  * Writes a 7z file.
@@ -159,7 +161,11 @@ public class SevenZOutputFile implements Closeable {
         final SevenZArchiveEntry entry = new SevenZArchiveEntry();
         entry.setDirectory(inputFile.isDirectory());
         entry.setName(entryName);
-        entry.setLastModifiedDate(new Date(inputFile.lastModified()));
+        try {
+            fillDates(inputFile.toPath(), entry);
+        } catch (IOException e) { // NOSONAR
+            entry.setLastModifiedDate(new Date(inputFile.lastModified()));
+        }
         return entry;
     }
 
@@ -179,10 +185,18 @@ public class SevenZOutputFile implements Closeable {
         final SevenZArchiveEntry entry = new SevenZArchiveEntry();
         entry.setDirectory(Files.isDirectory(inputPath, options));
         entry.setName(entryName);
-        entry.setLastModifiedDate(new 
Date(Files.getLastModifiedTime(inputPath, options).toMillis()));
+        fillDates(inputPath, entry, options);
         return entry;
     }
 
+    private void fillDates(final Path inputPath, final SevenZArchiveEntry 
entry,
+        final LinkOption... options) throws IOException {
+        BasicFileAttributes attributes = Files.readAttributes(inputPath, 
BasicFileAttributes.class, options);
+        entry.setLastModifiedTime(attributes.lastModifiedTime());
+        entry.setCreationTime(attributes.creationTime());
+        entry.setAccessTime(attributes.lastAccessTime());
+    }
+
     /**
      * Records an archive entry to add.
      *
@@ -651,8 +665,8 @@ public class SevenZOutputFile implements Closeable {
             out.write(0);
             for (final SevenZArchiveEntry entry : files) {
                 if (entry.getHasCreationDate()) {
-                    out.writeLong(Long.reverseBytes(
-                            
SevenZArchiveEntry.javaTimeToNtfsTime(entry.getCreationDate())));
+                    final long ntfsTime = 
TimeUtils.fileTimeToNtfsTime(entry.getCreationTime());
+                    out.writeLong(Long.reverseBytes(ntfsTime));
                 }
             }
             out.flush();
@@ -687,8 +701,8 @@ public class SevenZOutputFile implements Closeable {
             out.write(0);
             for (final SevenZArchiveEntry entry : files) {
                 if (entry.getHasAccessDate()) {
-                    out.writeLong(Long.reverseBytes(
-                            
SevenZArchiveEntry.javaTimeToNtfsTime(entry.getAccessDate())));
+                    final long ntfsTime = 
TimeUtils.fileTimeToNtfsTime(entry.getAccessTime());
+                    out.writeLong(Long.reverseBytes(ntfsTime));
                 }
             }
             out.flush();
@@ -723,8 +737,8 @@ public class SevenZOutputFile implements Closeable {
             out.write(0);
             for (final SevenZArchiveEntry entry : files) {
                 if (entry.getHasLastModifiedDate()) {
-                    out.writeLong(Long.reverseBytes(
-                            
SevenZArchiveEntry.javaTimeToNtfsTime(entry.getLastModifiedDate())));
+                    final long ntfsTime = 
TimeUtils.fileTimeToNtfsTime(entry.getLastModifiedTime());
+                    out.writeLong(Long.reverseBytes(ntfsTime));
                 }
             }
             out.flush();
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java 
b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
index a8a60c67..689c211b 100644
--- 
a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
+++ 
b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
@@ -49,6 +49,7 @@ import 
org.apache.commons.compress.archivers.EntryStreamOffsets;
 import org.apache.commons.compress.archivers.zip.ZipEncoding;
 import org.apache.commons.compress.utils.ArchiveUtils;
 import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.compress.utils.TimeUtils;
 
 /**
  * This class represents an entry in a Tar archive. It consists
@@ -210,7 +211,11 @@ public class TarArchiveEntry implements ArchiveEntry, 
TarConstants, EntryStreamO
     /** Default permissions bits for files */
     public static final int DEFAULT_FILE_MODE = 0100644;
 
-    /** Convert millis to seconds */
+    /**
+     * Convert millis to seconds
+     * @deprecated Unused.
+     */
+    @Deprecated
     public static final int MILLIS_PER_SECOND = 1000;
 
     private static FileTime fileTimeFromOptionalSeconds(long seconds) {
@@ -992,7 +997,7 @@ public class TarArchiveEntry implements ArchiveEntry, 
TarConstants, EntryStreamO
      * @see TarArchiveEntry#getLastModifiedTime()
      */
     public Date getModTime() {
-        return new Date(mTime.toMillis());
+        return TimeUtils.fileTimeToDate(mTime);
     }
 
     /**
@@ -1906,7 +1911,7 @@ public class TarArchiveEntry implements ArchiveEntry, 
TarConstants, EntryStreamO
      * @see TarArchiveEntry#setLastModifiedTime(FileTime)
      */
     public void setModTime(final Date time) {
-        setLastModifiedTime(FileTime.fromMillis(time.getTime()));
+        setLastModifiedTime(TimeUtils.dateToFileTime(time));
     }
 
     /**
diff --git 
a/src/main/java/org/apache/commons/compress/archivers/zip/X000A_NTFS.java 
b/src/main/java/org/apache/commons/compress/archivers/zip/X000A_NTFS.java
index 2f8645d4..542b2d91 100644
--- a/src/main/java/org/apache/commons/compress/archivers/zip/X000A_NTFS.java
+++ b/src/main/java/org/apache/commons/compress/archivers/zip/X000A_NTFS.java
@@ -17,6 +17,9 @@
  */
 package org.apache.commons.compress.archivers.zip;
 
+import org.apache.commons.compress.utils.TimeUtils;
+
+import java.nio.file.attribute.FileTime;
 import java.util.Date;
 import java.util.Objects;
 import java.util.zip.ZipException;
@@ -244,6 +247,39 @@ public class X000A_NTFS implements ZipExtraField {
         return zipToDate(createTime);
     }
 
+    /**
+     * Gets the modify time as as a {@link FileTime}
+     * of this zip entry, or null if no such timestamp exists in the zip entry.
+     *
+     * @return modify time as a {@link FileTime} or null.
+     * @since 1.23
+     */
+    public FileTime getModifyFileTime() {
+        return zipToFileTime(modifyTime);
+    }
+
+    /**
+     * Gets the access time as a {@link FileTime}
+     * of this zip entry, or null if no such timestamp exists in the zip entry.
+     *
+     * @return access time as a {@link FileTime} or null.
+     * @since 1.23
+     */
+    public FileTime getAccessFileTime() {
+        return zipToFileTime(accessTime);
+    }
+
+    /**
+     * Gets the create time as a {@link FileTime}
+     * of this zip entry, or null if no such timestamp exists in the zip entry.
+     *
+     * @return create time as a {@link FileTime} or null.
+     * @since 1.23
+     */
+    public FileTime getCreateFileTime() {
+        return zipToFileTime(createTime);
+    }
+
     /**
      * Sets the File last modification time of this zip entry using a
      * ZipEightByteInteger object.
@@ -304,6 +340,36 @@ public class X000A_NTFS implements ZipExtraField {
      */
     public void setCreateJavaTime(final Date d) { setCreateTime(dateToZip(d)); 
}
 
+    /**
+     * Sets the modify time.
+     *
+     * @param time modify time as a {@link FileTime}
+     * @since 1.23
+     */
+    public void setModifyFileTime(final FileTime time) {
+        setModifyTime(fileTimeToZip(time));
+    }
+
+    /**
+     * Sets the access time.
+     *
+     * @param time access time as a {@link FileTime}
+     * @since 1.23
+     */
+    public void setAccessFileTime(final FileTime time) {
+        setAccessTime(fileTimeToZip(time));
+    }
+
+    /**
+     * Sets the create time.
+     *
+     * @param time create time as a {@link FileTime}
+     * @since 1.23
+     */
+    public void setCreateFileTime(final FileTime time) {
+        setCreateTime(fileTimeToZip(time));
+    }
+
     /**
      * Returns a String representation of this class useful for
      * debugging purposes.
@@ -315,9 +381,9 @@ public class X000A_NTFS implements ZipExtraField {
     public String toString() {
         final StringBuilder buf = new StringBuilder();
         buf.append("0x000A Zip Extra Field:")
-            .append(" Modify:[").append(getModifyJavaTime()).append("] ")
-            .append(" Access:[").append(getAccessJavaTime()).append("] ")
-            .append(" Create:[").append(getCreateJavaTime()).append("] ");
+            .append(" Modify:[").append(getModifyFileTime()).append("] ")
+            .append(" Access:[").append(getAccessFileTime()).append("] ")
+            .append(" Create:[").append(getCreateFileTime()).append("] ");
         return buf.toString();
     }
 
@@ -374,22 +440,31 @@ public class X000A_NTFS implements ZipExtraField {
         }
     }
 
-    // 
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290%28v=vs.85%29.aspx
-    // 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
-    private static final long EPOCH_OFFSET = -116444736000000000L;
-
     private static ZipEightByteInteger dateToZip(final Date d) {
-        if (d == null) { return null; }
-        return new ZipEightByteInteger((d.getTime() * 10000L) - EPOCH_OFFSET);
+        if (d == null) {
+            return null;
+        }
+        return new ZipEightByteInteger(TimeUtils.dateToNtfsTime(d));
+    }
+
+    private static ZipEightByteInteger fileTimeToZip(final FileTime time) {
+        if (time == null) {
+            return null;
+        }
+        return new ZipEightByteInteger(TimeUtils.fileTimeToNtfsTime(time));
     }
 
     private static Date zipToDate(final ZipEightByteInteger z) {
-        if (z == null || ZipEightByteInteger.ZERO.equals(z)) { return null; }
-        final long l = (z.getLongValue() + EPOCH_OFFSET) / 10000L;
-        return new Date(l);
+        if (z == null || ZipEightByteInteger.ZERO.equals(z)) {
+            return null;
+        }
+        return TimeUtils.ntfsTimeToDate(z.getLongValue());
     }
 
+    private static FileTime zipToFileTime(final ZipEightByteInteger z) {
+        if (z == null || ZipEightByteInteger.ZERO.equals(z)) {
+            return null;
+        }
+        return TimeUtils.ntfsTimeToFileTime(z.getLongValue());
+    }
 }
diff --git a/src/main/java/org/apache/commons/compress/utils/TimeUtils.java 
b/src/main/java/org/apache/commons/compress/utils/TimeUtils.java
new file mode 100644
index 00000000..e4bc119c
--- /dev/null
+++ b/src/main/java/org/apache/commons/compress/utils/TimeUtils.java
@@ -0,0 +1,141 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.commons.compress.utils;
+
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility class for handling time-related types and conversions.
+ *
+ * @since 1.23
+ */
+public final class TimeUtils {
+
+    /** Private constructor to prevent instantiation of this utility class. */
+    private TimeUtils(){
+    }
+
+    /**
+     * <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>
+     */
+    public static final long WINDOWS_EPOCH_OFFSET = -116444736000000000L;
+
+    /** The amount of 100-nanosecond intervals in one second. */
+    public static final long HUNDRED_NANOS_PER_SECOND = 
TimeUnit.SECONDS.toNanos(1) / 100;
+
+    /** The amount of 100-nanosecond intervals in one millisecond. */
+    public static final long HUNDRED_NANOS_PER_MILLISECOND = 
TimeUnit.MILLISECONDS.toNanos(1) / 100;
+
+    /**
+     * 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 a {@link Date} to NTFS time.
+     *
+     * @param date the Date
+     * @return the NTFS time
+     */
+    public static long dateToNtfsTime(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 time the FileTime
+     * @return the NTFS time in 100-nanosecond units
+     *
+     * @see TimeUtils#WINDOWS_EPOCH_OFFSET
+     * @see TimeUtils#ntfsTimeToFileTime(long)
+     */
+    public static long fileTimeToNtfsTime(final FileTime time) {
+        final Instant instant = time.toInstant();
+        final long javaHundredNanos = (instant.getEpochSecond() * 
HUNDRED_NANOS_PER_SECOND) + (instant.getNano() / 100);
+        return Math.subtractExact(javaHundredNanos, WINDOWS_EPOCH_OFFSET);
+    }
+
+    /**
+     * 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 TimeUtils#WINDOWS_EPOCH_OFFSET
+     * @see TimeUtils#fileTimeToNtfsTime(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));
+    }
+
+    /**
+     * Truncates a FileTime to 100-nanosecond precision.
+     *
+     * @param fileTime the FileTime to be truncated
+     * @return the truncated FileTime
+     */
+    public static FileTime truncateToHundredNanos(final FileTime fileTime) {
+        final Instant instant = fileTime.toInstant();
+        return FileTime.from(Instant.ofEpochSecond(instant.getEpochSecond(), 
(instant.getNano() / 100) * 100));
+    }
+
+    /**
+     * 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 TimeUtils#fileTimeToDate(FileTime)
+     */
+    public static FileTime dateToFileTime(final Date date) {
+        return date != null ? FileTime.fromMillis(date.getTime()) : null;
+    }
+
+    /**
+     * Converts {@link FileTime} to a {@link Date}.
+     * If the provided FileTime is {@code null}, the returned Date is also 
{@code null}.
+     *
+     * @param time 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 TimeUtils#dateToFileTime(Date)
+     */
+    public static Date fileTimeToDate(final FileTime time) {
+        return time != null ? new Date(time.toMillis()) : null;
+    }
+}
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/SevenZTestCase.java 
b/src/test/java/org/apache/commons/compress/archivers/SevenZTestCase.java
index 1f6535de..704addc8 100644
--- a/src/test/java/org/apache/commons/compress/archivers/SevenZTestCase.java
+++ b/src/test/java/org/apache/commons/compress/archivers/SevenZTestCase.java
@@ -17,12 +17,11 @@
  */
 package org.apache.commons.compress.archivers;
 
-import static org.junit.Assert.assertEquals;
-
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.security.NoSuchAlgorithmException;
 
 import javax.crypto.Cipher;
@@ -32,10 +31,13 @@ import 
org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
 import org.apache.commons.compress.archivers.sevenz.SevenZFile;
 import org.apache.commons.compress.archivers.sevenz.SevenZMethod;
 import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
+import org.apache.commons.compress.utils.TimeUtils;
 import org.junit.Assume;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import static org.junit.Assert.*;
+
 public class SevenZTestCase extends AbstractTestCase {
 
     private File output;
@@ -84,14 +86,22 @@ public class SevenZTestCase extends AbstractTestCase {
             SevenZArchiveEntry entry;
 
             entry = archive.getNextEntry();
-            assert (entry != null);
-            assertEquals(entry.getName(), file1.getName());
+            assertNotNull(entry);
+            assertEquals(file1.getName(),entry.getName());
+            BasicFileAttributes attributes = 
Files.readAttributes(file1.toPath(), BasicFileAttributes.class);
+            
assertEquals(TimeUtils.truncateToHundredNanos(attributes.lastModifiedTime()), 
entry.getLastModifiedTime());
+            
assertEquals(TimeUtils.truncateToHundredNanos(attributes.creationTime()), 
entry.getCreationTime());
+            assertNotNull(entry.getAccessTime());
 
             entry = archive.getNextEntry();
-            assert (entry != null);
-            assertEquals(entry.getName(), file2.getName());
-
-            assert (archive.getNextEntry() == null);
+            assertNotNull(entry);
+            assertEquals(file2.getName(), entry.getName());
+            attributes = Files.readAttributes(file2.toPath(), 
BasicFileAttributes.class);
+            
assertEquals(TimeUtils.truncateToHundredNanos(attributes.lastModifiedTime()), 
entry.getLastModifiedTime());
+            
assertEquals(TimeUtils.truncateToHundredNanos(attributes.creationTime()), 
entry.getCreationTime());
+            assertNotNull(entry.getAccessTime());
+
+            assertNull(archive.getNextEntry());
         }
     }
 
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZArchiveEntryTest.java
 
b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZArchiveEntryTest.java
index 77c9b3c1..ce8feb61 100644
--- 
a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZArchiveEntryTest.java
+++ 
b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZArchiveEntryTest.java
@@ -96,4 +96,32 @@ public class SevenZArchiveEntryTest {
         assertThrows(UnsupportedOperationException.class, () -> new 
SevenZArchiveEntry().getLastModifiedDate());
     }
 
+    @Test
+    public void shouldThrowIfAccessDateIsSetToNull() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            SevenZArchiveEntry entry = new SevenZArchiveEntry();
+            entry.setAccessDate(null);
+            entry.getAccessDate();
+        });
+    }
+
+    @Test
+    public void shouldThrowIfCreationDateIsSetToNull() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            SevenZArchiveEntry entry = new SevenZArchiveEntry();
+            entry.setCreationDate(null);
+            entry.getCreationDate();
+        });
+    }
+
+    @Test
+    public void shouldThrowIfLastModifiedDateIsSetToNull() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            SevenZArchiveEntry entry = new SevenZArchiveEntry();
+            entry.setLastModifiedDate(null);
+            entry.getLastModifiedDate();
+        });
+    }
+
+
 }
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java
 
b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java
index 21fe7947..1c605bbb 100644
--- 
a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java
+++ 
b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZFileTest.java
@@ -35,15 +35,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
 import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
+import java.time.Instant;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
 import javax.crypto.Cipher;
 
@@ -124,7 +121,11 @@ public class SevenZFileTest extends AbstractTestCase {
     @Test
     public void testAllEmptyFilesArchive() throws Exception {
         try (SevenZFile archive = new 
SevenZFile(getFile("7z-empty-mhc-off.7z"))) {
-            assertNotNull(archive.getNextEntry());
+            SevenZArchiveEntry e = archive.getNextEntry();
+            assertNotNull(e);
+            assertEquals("empty", e.getName());
+            assertDates(e, "2013-05-14T17:50:19Z", null, null);
+            assertNull(archive.getNextEntry());
         }
     }
 
@@ -775,6 +776,32 @@ public class SevenZFileTest extends AbstractTestCase {
         }
     }
 
+    @Test
+    public void readTimesFromFile() throws IOException {
+        try (SevenZFile sevenZFile = new SevenZFile(getFile("times.7z"))) {
+            SevenZArchiveEntry entry = sevenZFile.getNextEntry();
+            assertNotNull(entry);
+            assertEquals("test", entry.getName());
+            assertTrue(entry.isDirectory());
+            assertDates(entry, "2022-03-21T14:50:46.2099751Z", 
"2022-03-21T14:50:46.2099751Z", "2022-03-16T10:19:24.1051115Z");
+
+            entry = sevenZFile.getNextEntry();
+            assertNotNull(entry);
+            assertEquals("test/test-times.txt", entry.getName());
+            assertFalse(entry.isDirectory());
+            assertDates(entry, "2022-03-18T10:00:15Z", 
"2022-03-18T10:14:37.8130002Z", "2022-03-18T10:14:37.8110032Z");
+
+            entry = sevenZFile.getNextEntry();
+            assertNotNull(entry);
+            assertEquals("test/test-times2.txt", entry.getName());
+            assertFalse(entry.isDirectory());
+            assertDates(entry, "2022-03-18T10:00:19Z", 
"2022-03-18T10:14:37.8170038Z", "2022-03-18T10:14:37.8140004Z");
+
+            entry = sevenZFile.getNextEntry();
+            assertNull(entry);
+        }
+    }
+
 
     private void test7zUnarchive(final File f, final SevenZMethod m, final 
byte[] password) throws Exception {
         try (SevenZFile sevenZFile = new SevenZFile(f, password)) {
@@ -797,9 +824,11 @@ public class SevenZFileTest extends AbstractTestCase {
     private void test7zUnarchive(final SevenZFile sevenZFile, final 
SevenZMethod m) throws Exception {
         SevenZArchiveEntry entry = sevenZFile.getNextEntry();
         assertEquals("test1.xml", entry.getName());
+        assertDates(entry, "2007-11-14T10:19:02Z", null, null);
         assertEquals(m, 
entry.getContentMethods().iterator().next().getMethod());
         entry = sevenZFile.getNextEntry();
         assertEquals("test2.xml", entry.getName());
+        assertDates(entry, "2007-11-14T10:19:02Z", null, null);
         assertEquals(m, 
entry.getContentMethods().iterator().next().getMethod());
         final byte[] contents = new byte[(int) entry.getSize()];
         int off = 0;
@@ -816,6 +845,7 @@ public class SevenZFileTest extends AbstractTestCase {
         try (SevenZFile sevenZFile = new SevenZFile(getFile(filename))) {
             final SevenZArchiveEntry entry = sevenZFile.getNextEntry();
             assertEquals("Hello world.txt", entry.getName());
+            assertDates(entry, "2013-05-07T19:40:48Z", null, null);
             final byte[] contents = new byte[(int) entry.getSize()];
             int off = 0;
             while ((off < contents.length)) {
@@ -831,4 +861,28 @@ public class SevenZFileTest extends AbstractTestCase {
     private static boolean isStrongCryptoAvailable() throws 
NoSuchAlgorithmException {
         return Cipher.getMaxAllowedKeyLength("AES/ECB/PKCS5Padding") >= 256;
     }
+
+    private void assertDates(SevenZArchiveEntry entry, String modified, String 
access, String creation) {
+        assertDate(entry, modified, SevenZArchiveEntry::getHasLastModifiedDate,
+            SevenZArchiveEntry::getLastModifiedTime, 
SevenZArchiveEntry::getLastModifiedDate);
+        assertDate(entry, access, SevenZArchiveEntry::getHasAccessDate,
+            SevenZArchiveEntry::getAccessTime, 
SevenZArchiveEntry::getAccessDate);
+        assertDate(entry, creation, SevenZArchiveEntry::getHasCreationDate,
+            SevenZArchiveEntry::getCreationTime, 
SevenZArchiveEntry::getCreationDate);
+    }
+
+    private void assertDate(SevenZArchiveEntry entry, String value, 
Function<SevenZArchiveEntry, Boolean> hasValue,
+        Function<SevenZArchiveEntry, FileTime> timeFunction, 
Function<SevenZArchiveEntry, Date> dateFunction) {
+        if (value != null) {
+            assertTrue(hasValue.apply(entry));
+            final Instant parsedInstant = Instant.parse(value);
+            final FileTime parsedFileTime = FileTime.from(parsedInstant);
+            assertEquals(parsedFileTime, timeFunction.apply(entry));
+            assertEquals(Date.from(parsedInstant), dateFunction.apply(entry));
+        } else {
+            assertFalse(hasValue.apply(entry));
+            assertThrows(UnsupportedOperationException.class, () -> 
timeFunction.apply(entry));
+            assertThrows(UnsupportedOperationException.class, () -> 
dateFunction.apply(entry));
+        }
+    }
 }
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
 
b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
index 4dcd2d85..908951fd 100644
--- 
a/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
+++ 
b/src/test/java/org/apache/commons/compress/archivers/sevenz/SevenZOutputFileTest.java
@@ -23,13 +23,18 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import org.apache.commons.compress.utils.TimeUtils;
+import org.junit.jupiter.api.Test;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
-import java.util.ArrayList;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
 import java.util.Date;
@@ -38,7 +43,6 @@ import java.util.Iterator;
 import org.apache.commons.compress.AbstractTestCase;
 import org.apache.commons.compress.utils.ByteUtils;
 import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
-import org.junit.jupiter.api.Test;
 import org.tukaani.xz.LZMA2Options;
 
 public class SevenZOutputFileTest extends AbstractTestCase {
@@ -67,7 +71,8 @@ public class SevenZOutputFileTest extends AbstractTestCase {
     public void testDirectoriesAndEmptyFiles() throws Exception {
         output = new File(dir, "empties.7z");
 
-        final Date accessDate = new Date();
+        final FileTime accessTime = getHundredNanosFileTime();
+        final Date accessDate = new Date(accessTime.toMillis());
         final Calendar cal = Calendar.getInstance();
         cal.add(Calendar.HOUR, -1);
         final Date creationDate = cal.getTime();
@@ -80,7 +85,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             entry = new SevenZArchiveEntry();
             entry.setName("foo/bar");
             entry.setCreationDate(creationDate);
-            entry.setAccessDate(accessDate);
+            entry.setAccessTime(accessTime);
             outArchive.putArchiveEntry(entry);
             outArchive.write(ByteUtils.EMPTY_BYTE_ARRAY);
             outArchive.closeArchiveEntry();
@@ -88,7 +93,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             entry = new SevenZArchiveEntry();
             entry.setName("foo/bar/boo0");
             entry.setCreationDate(creationDate);
-            entry.setAccessDate(accessDate);
+            entry.setAccessTime(accessTime);
             outArchive.putArchiveEntry(entry);
             outArchive.write(new 
ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY));
             outArchive.closeArchiveEntry();
@@ -96,7 +101,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             entry = new SevenZArchiveEntry();
             entry.setName("foo/bar/boo1");
             entry.setCreationDate(creationDate);
-            entry.setAccessDate(accessDate);
+            entry.setAccessTime(accessTime);
             outArchive.putArchiveEntry(entry);
             outArchive.write(new ByteArrayInputStream(new byte[] {'a'}));
             outArchive.closeArchiveEntry();
@@ -104,7 +109,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             entry = new SevenZArchiveEntry();
             entry.setName("foo/bar/boo10000");
             entry.setCreationDate(creationDate);
-            entry.setAccessDate(accessDate);
+            entry.setAccessTime(accessTime);
             outArchive.putArchiveEntry(entry);
             outArchive.write(new ByteArrayInputStream(new byte[10000]));
             outArchive.closeArchiveEntry();
@@ -112,7 +117,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             entry = new SevenZArchiveEntry();
             entry.setName("foo/bar/test.txt");
             entry.setCreationDate(creationDate);
-            entry.setAccessDate(accessDate);
+            entry.setAccessTime(accessTime);
             outArchive.putArchiveEntry(entry);
             outArchive.write(Paths.get("src/test/resources/test.txt"));
             outArchive.closeArchiveEntry();
@@ -159,6 +164,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             assertFalse(entry.isAntiItem());
             assertEquals(0, entry.getSize());
             assertFalse(entry.getHasLastModifiedDate());
+            assertEquals(accessTime, entry.getAccessTime());
             assertEquals(accessDate, entry.getAccessDate());
             assertEquals(creationDate, entry.getCreationDate());
 
@@ -169,6 +175,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             assertFalse(entry.isAntiItem());
             assertEquals(0, entry.getSize());
             assertFalse(entry.getHasLastModifiedDate());
+            assertEquals(accessTime, entry.getAccessTime());
             assertEquals(accessDate, entry.getAccessDate());
             assertEquals(creationDate, entry.getCreationDate());
 
@@ -179,6 +186,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             assertFalse(entry.isAntiItem());
             assertEquals(1, entry.getSize());
             assertFalse(entry.getHasLastModifiedDate());
+            assertEquals(accessTime, entry.getAccessTime());
             assertEquals(accessDate, entry.getAccessDate());
             assertEquals(creationDate, entry.getCreationDate());
 
@@ -189,6 +197,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             assertFalse(entry.isAntiItem());
             assertEquals(10000, entry.getSize());
             assertFalse(entry.getHasLastModifiedDate());
+            assertEquals(accessTime, entry.getAccessTime());
             assertEquals(accessDate, entry.getAccessDate());
             assertEquals(creationDate, entry.getCreationDate());
 
@@ -199,6 +208,7 @@ public class SevenZOutputFileTest extends AbstractTestCase {
             assertFalse(entry.isAntiItem());
             assertEquals(Files.size(Paths.get("src/test/resources/test.txt")), 
entry.getSize());
             assertFalse(entry.getHasLastModifiedDate());
+            assertEquals(accessTime, entry.getAccessTime());
             assertEquals(accessDate, entry.getAccessDate());
             assertEquals(creationDate, entry.getCreationDate());
 
@@ -237,6 +247,16 @@ public class SevenZOutputFileTest extends AbstractTestCase 
{
 
     }
 
+    private FileTime getHundredNanosFileTime() {
+        final Instant now = Instant.now();
+        // In some platforms, Java's Instant has a precision of milliseconds.
+        // Add some nanos at the end to test 100ns intervals.
+        final FileTime fileTime = 
FileTime.from(Instant.ofEpochSecond(now.getEpochSecond(), now.getNano() + 
999900));
+        // However, in some platforms, Java's Instant has a precision of 
nanoseconds.
+        // Truncate the resulting FileTime to 100ns intervals.
+        return TimeUtils.truncateToHundredNanos(fileTime);
+    }
+
     @Test
     public void testDirectoriesOnly() throws Exception {
         output = new File(dir, "dirs.7z");
diff --git 
a/src/test/java/org/apache/commons/compress/archivers/zip/X000A_NTFSTest.java 
b/src/test/java/org/apache/commons/compress/archivers/zip/X000A_NTFSTest.java
index 6521f9bb..55ee1220 100644
--- 
a/src/test/java/org/apache/commons/compress/archivers/zip/X000A_NTFSTest.java
+++ 
b/src/test/java/org/apache/commons/compress/archivers/zip/X000A_NTFSTest.java
@@ -20,6 +20,8 @@ package org.apache.commons.compress.archivers.zip;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
 import java.util.Date;
 
 import org.junit.jupiter.api.Test;
@@ -41,4 +43,44 @@ public class X000A_NTFSTest {
         assertEquals(new Date(-11644473601000L), xf2.getAccessJavaTime());
         assertNull(xf2.getCreateJavaTime());
     }
+
+    @Test
+    public void simpleRountripWithHighPrecisionDatesWithSmallValues() throws 
Exception {
+        final X000A_NTFS xf = new X000A_NTFS();
+        // The last 2 digits should not be written due to the 100ns precision
+        xf.setModifyFileTime(FileTime.from(Instant.ofEpochSecond(0, 1234)));
+        // one second past midnight
+        
xf.setAccessFileTime(FileTime.from(Instant.ofEpochSecond(-11644473601L)));
+        xf.setCreateFileTime(null);
+        final byte[] b = xf.getLocalFileDataData();
+
+        final X000A_NTFS xf2 = new X000A_NTFS();
+        xf2.parseFromLocalFileData(b, 0, b.length);
+        assertEquals(FileTime.from(Instant.ofEpochSecond(0, 1200)), 
xf2.getModifyFileTime());
+        assertEquals(new Date(0), xf2.getModifyJavaTime());
+        assertEquals(FileTime.from(Instant.ofEpochSecond(-11644473601L)), 
xf2.getAccessFileTime());
+        assertEquals(new Date(-11644473601000L), xf2.getAccessJavaTime());
+        assertNull(xf2.getCreateFileTime());
+        assertNull(xf2.getCreateJavaTime());
+    }
+
+    @Test
+    public void simpleRountripWithHighPrecisionDatesWithBigValues() throws 
Exception {
+        final X000A_NTFS xf = new X000A_NTFS();
+        
xf.setModifyFileTime(FileTime.from(Instant.ofEpochSecond(123456789101L, 
123456700)));
+        // one second past midnight
+        
xf.setAccessFileTime(FileTime.from(Instant.ofEpochSecond(-11644473601L)));
+        // 765432100ns past midnight
+        
xf.setCreateFileTime(FileTime.from(Instant.ofEpochSecond(-11644473600L, 
765432100)));
+        final byte[] b = xf.getLocalFileDataData();
+
+        final X000A_NTFS xf2 = new X000A_NTFS();
+        xf2.parseFromLocalFileData(b, 0, b.length);
+        assertEquals(FileTime.from(Instant.ofEpochSecond(123456789101L, 
123456700)), xf2.getModifyFileTime());
+        assertEquals(new Date(123456789101123L), xf2.getModifyJavaTime());
+        assertEquals(FileTime.from(Instant.ofEpochSecond(-11644473601L)), 
xf2.getAccessFileTime());
+        assertEquals(new Date(-11644473601000L), xf2.getAccessJavaTime());
+        assertEquals(FileTime.from(Instant.ofEpochSecond(-11644473600L, 
765432100)), xf2.getCreateFileTime());
+        assertEquals(new Date(-11644473599235L).toInstant(), 
xf2.getCreateJavaTime().toInstant());
+    }
 }
diff --git a/src/test/java/org/apache/commons/compress/utils/TimeUtilsTest.java 
b/src/test/java/org/apache/commons/compress/utils/TimeUtilsTest.java
new file mode 100644
index 00000000..455fefbc
--- /dev/null
+++ b/src/test/java/org/apache/commons/compress/utils/TimeUtilsTest.java
@@ -0,0 +1,181 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+package org.apache.commons.compress.utils;
+
+import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
+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;
+
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+import java.util.Date;
+import java.util.stream.Stream;
+
+import static org.apache.commons.compress.utils.TimeUtils.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class TimeUtilsTest {
+
+    public static Stream<Arguments> dateToNtfsProvider() {
+        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", 
HUNDRED_NANOS_PER_MILLISECOND),
+            Arguments.of("1601-01-01T00:00:00.001Z", 
HUNDRED_NANOS_PER_MILLISECOND + 1),
+            Arguments.of("1601-01-01T00:00:00.000Z", 
HUNDRED_NANOS_PER_MILLISECOND - 1),
+            Arguments.of("1600-12-31T23:59:59.999Z", 
-HUNDRED_NANOS_PER_MILLISECOND),
+            Arguments.of("1600-12-31T23:59:59.999Z", 
-HUNDRED_NANOS_PER_MILLISECOND + 1),
+            Arguments.of("1600-12-31T23:59:59.998Z", 
-HUNDRED_NANOS_PER_MILLISECOND - 1),
+            Arguments.of("1970-01-01T00:00:00.000Z", -WINDOWS_EPOCH_OFFSET),
+            Arguments.of("1970-01-01T00:00:00.000Z", -WINDOWS_EPOCH_OFFSET + 
1),
+            Arguments.of("1970-01-01T00:00:00.001Z", -WINDOWS_EPOCH_OFFSET + 
HUNDRED_NANOS_PER_MILLISECOND),
+            Arguments.of("1969-12-31T23:59:59.999Z", -WINDOWS_EPOCH_OFFSET - 
1),
+            Arguments.of("1969-12-31T23:59:59.999Z", -WINDOWS_EPOCH_OFFSET - 
HUNDRED_NANOS_PER_MILLISECOND)
+        );
+    }
+
+    public static Stream<Arguments> fileTimeToNtfsProvider() {
+        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", 
HUNDRED_NANOS_PER_MILLISECOND),
+            Arguments.of("1601-01-01T00:00:00.0010001Z", 
HUNDRED_NANOS_PER_MILLISECOND + 1),
+            Arguments.of("1601-01-01T00:00:00.0009999Z", 
HUNDRED_NANOS_PER_MILLISECOND - 1),
+            Arguments.of("1600-12-31T23:59:59.9990000Z", 
-HUNDRED_NANOS_PER_MILLISECOND),
+            Arguments.of("1600-12-31T23:59:59.9990001Z", 
-HUNDRED_NANOS_PER_MILLISECOND + 1),
+            Arguments.of("1600-12-31T23:59:59.9989999Z", 
-HUNDRED_NANOS_PER_MILLISECOND - 1),
+            Arguments.of("1970-01-01T00:00:00.0000000Z", 
-WINDOWS_EPOCH_OFFSET),
+            Arguments.of("1970-01-01T00:00:00.0000001Z", -WINDOWS_EPOCH_OFFSET 
+ 1),
+            Arguments.of("1970-01-01T00:00:00.0010000Z", -WINDOWS_EPOCH_OFFSET 
+ HUNDRED_NANOS_PER_MILLISECOND),
+            Arguments.of("1969-12-31T23:59:59.9999999Z", -WINDOWS_EPOCH_OFFSET 
- 1),
+            Arguments.of("1969-12-31T23:59:59.9990000Z", -WINDOWS_EPOCH_OFFSET 
- HUNDRED_NANOS_PER_MILLISECOND)
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("dateToNtfsProvider")
+    public void shouldConvertNtfsTimeToDate(final String instant, final long 
ntfsTime) {
+        final Date converted = ntfsTimeToDate(ntfsTime);
+        assertEquals(Instant.parse(instant), converted.toInstant());
+        // ensuring the deprecated method still works
+        assertEquals(converted, 
SevenZArchiveEntry.ntfsTimeToJavaTime(ntfsTime));
+    }
+
+    @ParameterizedTest
+    @MethodSource("dateToNtfsProvider")
+    public void shouldConvertDateToNtfsTime(final String instant, final long 
ntfsTime) {
+        final long ntfsMillis = Math.floorDiv(ntfsTime, 
HUNDRED_NANOS_PER_MILLISECOND) * HUNDRED_NANOS_PER_MILLISECOND;
+        final Date parsed = Date.from(Instant.parse(instant));
+        final long converted = dateToNtfsTime(parsed);
+        assertEquals(ntfsMillis, converted);
+        // ensuring the deprecated method still works
+        assertEquals(converted, SevenZArchiveEntry.javaTimeToNtfsTime(parsed));
+    }
+
+    @ParameterizedTest
+    @MethodSource("fileTimeToNtfsProvider")
+    public void shouldConvertFileTimeToNtfsTime(final String instant, final 
long ntfsTime) {
+        final FileTime parsed = FileTime.from(Instant.parse(instant));
+        assertEquals(ntfsTime, fileTimeToNtfsTime(parsed));
+    }
+
+    @ParameterizedTest
+    @MethodSource("fileTimeToNtfsProvider")
+    public void shouldConvertNtfsTimeToFileTime(final String instant, final 
long ntfsTime) {
+        final FileTime parsed = FileTime.from(Instant.parse(instant));
+        assertEquals(parsed, ntfsTimeToFileTime(ntfsTime));
+    }
+
+    @Test
+    public void shouldConvertNullDateToNullFileTime() {
+        assertNull(dateToFileTime(null));
+    }
+
+    @Test
+    public void shouldConvertNullFileTimeToNullDate() {
+        assertNull(fileTimeToDate(null));
+    }
+
+    @ParameterizedTest
+    @MethodSource("dateToNtfsProvider")
+    public void shouldConvertDateToFileTime(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, dateToFileTime(parsedDate));
+    }
+
+    @ParameterizedTest
+    @MethodSource("fileTimeToNtfsProvider")
+    public void shouldConvertFileTimeToDate(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, fileTimeToDate(parsedFileTime));
+    }
+
+    public static Stream<Arguments> truncateFileTimeProvider() {
+        return Stream.of(
+                Arguments.of(
+                        "2022-05-10T18:25:33.123456789Z",
+                        "2022-05-10T18:25:33.1234567Z"
+                ),
+                Arguments.of(
+                        "1970-01-01T00:00:00.000000001Z",
+                        "1970-01-01T00:00:00.0000000Z"
+                ),
+                Arguments.of(
+                        "1970-01-01T00:00:00.000000010Z",
+                        "1970-01-01T00:00:00.0000000Z"
+                ),
+                Arguments.of(
+                        "1970-01-01T00:00:00.000000199Z",
+                        "1970-01-01T00:00:00.0000001Z"
+                ),
+                Arguments.of(
+                        "1969-12-31T23:59:59.999999999Z",
+                        "1969-12-31T23:59:59.9999999Z"
+                ),
+                Arguments.of(
+                        "1969-12-31T23:59:59.000000001Z",
+                        "1969-12-31T23:59:59.0000000Z"
+                ),
+                Arguments.of(
+                        "1969-12-31T23:59:59.000000010Z",
+                        "1969-12-31T23:59:59.0000000Z"
+                ),
+                Arguments.of(
+                        "1969-12-31T23:59:59.000000199Z",
+                        "1969-12-31T23:59:59.0000001Z"
+                )
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("truncateFileTimeProvider")
+    public void shouldTruncateFileTimeToHundredNanos(final String original, 
final String truncated) {
+        final FileTime originalTime = FileTime.from(Instant.parse(original));
+        final FileTime truncatedTime = FileTime.from(Instant.parse(truncated));
+        assertEquals(truncatedTime, 
TimeUtils.truncateToHundredNanos(originalTime));
+    }
+}
diff --git a/src/test/resources/times.7z b/src/test/resources/times.7z
new file mode 100644
index 00000000..5e2910ce
Binary files /dev/null and b/src/test/resources/times.7z differ

Reply via email to