Ignore system timezone when applying outputTimestamp to entries

Update `JarWriter` so that entry times are set with the default TimeZone
offset removed. The Javadoc for `ZipEntry.setTime` states:

  The file entry is "encoded in standard `MS-DOS date and time format`.
  The default TimeZone is used to convert the epoch time to the MS-DOS
  data and time.

Removing the offset from our UTC time before calling `entry.setTime()`
ensures that we get consistent bytes in the zip file when the output
stream reapplies the offset during write.

Fixes gh-34424
This commit is contained in:
Phillip Webb
2023-03-01 19:28:40 -08:00
parent 29a16a6428
commit 998d59b7ac
6 changed files with 149 additions and 7 deletions

View File

@@ -21,6 +21,7 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
@@ -401,10 +402,12 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
mavenBuild.project("jar-output-timestamp").execute((project) -> {
File repackaged = new File(project, "target/jar-output-timestamp-0.0.1.BUILD-SNAPSHOT.jar");
assertThat(repackaged).isFile();
assertThat(repackaged.lastModified()).isEqualTo(1584352800000L);
long expectedModified = 1584352800000L;
long offsetExpectedModified = expectedModified - TimeZone.getDefault().getOffset(expectedModified);
assertThat(repackaged.lastModified()).isEqualTo(expectedModified);
try (JarFile jar = new JarFile(repackaged)) {
List<String> unreproducibleEntries = jar.stream()
.filter((entry) -> entry.getLastModifiedTime().toMillis() != 1584352800000L)
.filter((entry) -> entry.getLastModifiedTime().toMillis() != offsetExpectedModified)
.map((entry) -> entry.getName() + ": " + entry.getLastModifiedTime())
.collect(Collectors.toList());
assertThat(unreproducibleEntries).isEmpty();

View File

@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
@@ -96,10 +97,12 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests {
mavenBuild.project("war-output-timestamp").execute((project) -> {
File repackaged = new File(project, "target/war-output-timestamp-0.0.1.BUILD-SNAPSHOT.war");
assertThat(repackaged).isFile();
assertThat(repackaged.lastModified()).isEqualTo(1584352800000L);
long expectedModified = 1584352800000L;
assertThat(repackaged.lastModified()).isEqualTo(expectedModified);
long offsetExpectedModified = expectedModified - TimeZone.getDefault().getOffset(expectedModified);
try (JarFile jar = new JarFile(repackaged)) {
List<String> unreproducibleEntries = jar.stream()
.filter((entry) -> entry.getLastModifiedTime().toMillis() != 1584352800000L)
.filter((entry) -> entry.getLastModifiedTime().toMillis() != offsetExpectedModified)
.map((entry) -> entry.getName() + ": " + entry.getLastModifiedTime())
.collect(Collectors.toList());
assertThat(unreproducibleEntries).isEmpty();