michael-o commented on code in PR #22:
URL: https://github.com/apache/maven-archiver/pull/22#discussion_r902241569


##########
src/main/java/org/apache/maven/archiver/MavenArchiver.java:
##########
@@ -97,6 +101,10 @@
         "${artifact.groupIdPath}/${artifact.artifactId}/" + 
"${artifact.baseVersion}/${artifact.artifactId}-"
             + 
"${artifact.baseVersion}${dashClassifier?}.${artifact.extension}";
 
+    private static final ZonedDateTime DATE_MIN = ZonedDateTime.parse( 
"1980-01-01T00:00:02Z" );
+
+    private static final ZonedDateTime DATE_MAX = ZonedDateTime.parse( 
"2099-12-31T23:59:59Z" );

Review Comment:
   Isn't this for Zip files only?



##########
src/main/java/org/apache/maven/archiver/MavenArchiver.java:
##########
@@ -812,28 +820,78 @@ public void setBuildJdkSpecDefaultEntry( boolean 
buildJdkSpecDefaultEntry )
      * @return the parsed timestamp, may be <code>null</code> if 
<code>null</code> input or input contains only 1
      *         character
      * @since 3.5.0
-     * @throws java.lang.IllegalArgumentException if the outputTimestamp is 
neither ISO 8601 nor an integer
+     * @throws IllegalArgumentException if the outputTimestamp is neither ISO 
8601 nor an integer
+     * @deprecated Use {@link #parseBuildOutputTimestamp(String)} instead.
      */
+    @Deprecated
     public Date parseOutputTimestamp( String outputTimestamp )
     {
-        if ( StringUtils.isNumeric( outputTimestamp ) && 
StringUtils.isNotEmpty( outputTimestamp ) )
+        return parseBuildOutputTimestamp( outputTimestamp ).map( Date::from 
).orElse( null );
+    }
+
+    /**
+     * Configure Reproducible Builds archive creation if a timestamp is 
provided.
+     *
+     * @param outputTimestamp the value of {@code 
${project.build.outputTimestamp}} (may be {@code null})
+     * @return the parsed timestamp as {@link java.util.Date}
+     * @since 3.5.0
+     * @see #parseOutputTimestamp
+     * @deprecated Use {@link #configureReproducibleBuild(String)} instead.
+     */
+    @Deprecated
+    public Date configureReproducible( String outputTimestamp )
+    {
+        configureReproducibleBuild( outputTimestamp );
+        return parseOutputTimestamp( outputTimestamp );
+    }
+
+    /**
+     * Parse output timestamp configured for Reproducible Builds' archive 
entries.
+     *
+     * <p>Either as {@link 
java.time.format.DateTimeFormatter#ISO_ZONED_DATE_TIME} or as a number 
representing seconds
+     * since the epoch (like <a 
href="https://reproducible-builds.org/docs/source-date-epoch/";>SOURCE_DATE_EPOCH</a>).
+     *
+     * @param outputTimestamp the value of {@code 
${project.build.outputTimestamp}} (may be {@code null})
+     * @return the parsed timestamp as an {@code Optional<Instant>}, {@code 
empty} if input is {@code null} or input
+     *         contains only 1 character (not a number)
+     * @since 3.6.0
+     * @throws IllegalArgumentException if the outputTimestamp is neither ISO 
8601 nor an integer
+     */
+    public static Optional<Instant> parseBuildOutputTimestamp( String 
outputTimestamp )
+    {
+        // Fail-fast on nulls
+        if ( outputTimestamp == null )
+        {
+            return Optional.empty();
+        }
+
+        // Number representing seconds since the epoch
+        if ( StringUtils.isNotEmpty( outputTimestamp ) && 
StringUtils.isNumeric( outputTimestamp ) )
         {
-            return new Date( Long.parseLong( outputTimestamp ) * 1000 );
+            return Optional.of( Instant.ofEpochSecond( Long.parseLong( 
outputTimestamp ) ) );
         }
 
-        if ( outputTimestamp == null || outputTimestamp.length() < 2 )
+        // no timestamp configured (1 character configuration is useful to 
override a full value during pom
+        // inheritance)
+        if ( outputTimestamp.length() < 2 )
         {
-            // no timestamp configured (1 character configuration is useful to 
override a full value during pom
-            // inheritance)
-            return null;
+            return Optional.empty();
         }
 
-        DateFormat df = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ssXXX" );
         try
         {
-            return df.parse( outputTimestamp );
+            // Parse the date in UTC such as '2011-12-03T10:15:30Z' or with an 
offset '2019-10-05T20:37:42+06:00'.
+            final ZonedDateTime date = ZonedDateTime.parse( outputTimestamp )
+                .withZoneSameInstant( ZoneOffset.UTC ).truncatedTo( 
ChronoUnit.SECONDS );

Review Comment:
   That reads beautifully...



##########
src/test/java/org/apache/maven/archiver/MavenArchiverTest.java:
##########
@@ -1444,54 +1447,76 @@ public void testParseOutputTimestamp()
         assertThat( archiver.parseOutputTimestamp( "*" ) ).isNull();
 
         assertThat( archiver.parseOutputTimestamp( "1570300662" ).getTime() 
).isEqualTo( 1570300662000L );
-        assertThat( archiver.parseOutputTimestamp( "0" ).getTime() 
).isEqualTo( 0L );
+        assertThat( archiver.parseOutputTimestamp( "0" ).getTime() ).isZero();
         assertThat( archiver.parseOutputTimestamp( "1" ).getTime() 
).isEqualTo( 1000L );
 
-        assertThat( archiver.parseOutputTimestamp( "2019-10-05T18:37:42Z" 
).getTime() ).isEqualTo( 1570300662000L );
-        assertThat( archiver.parseOutputTimestamp( "2019-10-05T20:37:42+02:00" 
).getTime() ).isEqualTo(
-                1570300662000L );
-        assertThat( archiver.parseOutputTimestamp( "2019-10-05T16:37:42-02:00" 
).getTime() ).isEqualTo(
-                1570300662000L );
+        assertThat( archiver.parseOutputTimestamp( "2019-10-05T18:37:42Z" 
).getTime() )
+            .isEqualTo( 1570300662000L );
+        assertThat( archiver.parseOutputTimestamp( "2019-10-05T20:37:42+02:00" 
).getTime() )
+            .isEqualTo( 1570300662000L );
+        assertThat( archiver.parseOutputTimestamp( "2019-10-05T16:37:42-02:00" 
).getTime() )
+            .isEqualTo( 1570300662000L );
 
         // These must result in IAE because we expect extended ISO format only 
(ie with - separator for date and
         // : separator for timezone), hence the XXX SimpleDateFormat for tz 
offset
         // X SimpleDateFormat accepts timezone without separator while date 
has separator, which is a mix between
         // basic (no separators, both for date and timezone) and extended 
(separator for both)
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42+0200" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42-0200" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
+        assertThatExceptionOfType( IllegalArgumentException.class )
+            .isThrownBy( () -> archiver.parseOutputTimestamp( 
"2019-10-05T20:37:42+0200" ) );
+        assertThatExceptionOfType( IllegalArgumentException.class )
+            .isThrownBy( () -> archiver.parseOutputTimestamp( 
"2019-10-05T20:37:42-0200" ) );
+    }
 
-        // These unfortunately fail although the input is valid according to 
ISO 8601
-        // SDF does not allow strict telescoping parsing w/o permitting 
invalid input as depicted above.
-        // One has to use the new Java Time API for this.
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42+02" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42-02" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
+    @ParameterizedTest
+    @NullAndEmptySource
+    @ValueSource( strings = { ".", " ", "_", "-", "T", "/", "!", "!", "*", "ñ" 
} )
+    public void testEmptyParseOutputTimestampInstant( String value )
+    {
+        // Empty optional if null or 1 char
+        assertThat( MavenArchiver.parseBuildOutputTimestamp( value ) 
).isEmpty();
+    }
+
+    @ParameterizedTest
+    @CsvSource( { "0,0", "1,1", "9,9", "1570300662,1570300662", 
"2147483648,2147483648",
+        "2019-10-05T18:37:42Z,1570300662", 
"2019-10-05T20:37:42+02:00,1570300662",
+        "2019-10-05T16:37:42-02:00,1570300662", 
"1988-02-22T15:23:47.76598Z,572541827",
+        "2011-12-03T10:15:30+01:00[Europe/Paris],1322903730",
+        "1980-01-01T00:00:02Z,315532802", "2099-12-31T23:59:59Z,4102444799" } )
+    public void testParseOutputTimestampInstant( String value, long expected )
+    {
+        assertThat( MavenArchiver.parseBuildOutputTimestamp( value ) )
+            .contains( Instant.ofEpochSecond( expected ) );
+    }
+
+    @ParameterizedTest
+    @ValueSource( strings = { "2019-10-05T20:37:42+0200", 
"2019-10-05T20:37:42-0200", "2019-10-05T25:00:00Z",
+        "2019-10-05", "XYZ", "Tue, 3 Jun 2008 11:05:30 GMT" } )
+    public void testThrownParseOutputTimestampInstant( String outputTimestamp )
+    {
+        // Invalid parsing
+        assertThatExceptionOfType( IllegalArgumentException.class )
+            .isThrownBy( () -> MavenArchiver.parseBuildOutputTimestamp( 
outputTimestamp ) )
+            .withCauseInstanceOf( DateTimeParseException.class );
+    }
+
+    @ParameterizedTest
+    @ValueSource( strings = { "1980-01-01T00:00:01Z", "2100-01-01T00:00Z", 
"2100-02-28T23:59:59Z",
+        "2099-12-31T23:59:59-01:00", 
"1980-01-01T00:15:35+01:00[Europe/Madrid]" } )
+    public void testThrownParseOutputTimestampValidRange( String 
outputTimestamp )
+    {
+        // date is not within the valid range 1980-01-01T00:00:02Z to 
2099-12-31T23:59:59Z
+        assertThatExceptionOfType( IllegalArgumentException.class )
+            .isThrownBy( () -> MavenArchiver.parseBuildOutputTimestamp( 
outputTimestamp ) )
+            .withMessageContaining("is not within the valid range 
1980-01-01T00:00:02Z to 2099-12-31T23:59:59Z");
+    }
+
+    @ParameterizedTest
+    @CsvSource( { "2011-12-03T10:15:30+01[Europe/Paris],1322903730", 
"2019-10-05T20:37:42+02,1570300662",
+        "2011-12-03T10:15:30+06[America/Managua],1322885730", 
"1988-02-22T20:37:42+06,572539062" } )

Review Comment:
   Here too.



##########
src/test/java/org/apache/maven/archiver/MavenArchiverTest.java:
##########
@@ -1444,54 +1447,76 @@ public void testParseOutputTimestamp()
         assertThat( archiver.parseOutputTimestamp( "*" ) ).isNull();
 
         assertThat( archiver.parseOutputTimestamp( "1570300662" ).getTime() 
).isEqualTo( 1570300662000L );
-        assertThat( archiver.parseOutputTimestamp( "0" ).getTime() 
).isEqualTo( 0L );
+        assertThat( archiver.parseOutputTimestamp( "0" ).getTime() ).isZero();
         assertThat( archiver.parseOutputTimestamp( "1" ).getTime() 
).isEqualTo( 1000L );
 
-        assertThat( archiver.parseOutputTimestamp( "2019-10-05T18:37:42Z" 
).getTime() ).isEqualTo( 1570300662000L );
-        assertThat( archiver.parseOutputTimestamp( "2019-10-05T20:37:42+02:00" 
).getTime() ).isEqualTo(
-                1570300662000L );
-        assertThat( archiver.parseOutputTimestamp( "2019-10-05T16:37:42-02:00" 
).getTime() ).isEqualTo(
-                1570300662000L );
+        assertThat( archiver.parseOutputTimestamp( "2019-10-05T18:37:42Z" 
).getTime() )
+            .isEqualTo( 1570300662000L );
+        assertThat( archiver.parseOutputTimestamp( "2019-10-05T20:37:42+02:00" 
).getTime() )
+            .isEqualTo( 1570300662000L );
+        assertThat( archiver.parseOutputTimestamp( "2019-10-05T16:37:42-02:00" 
).getTime() )
+            .isEqualTo( 1570300662000L );
 
         // These must result in IAE because we expect extended ISO format only 
(ie with - separator for date and
         // : separator for timezone), hence the XXX SimpleDateFormat for tz 
offset
         // X SimpleDateFormat accepts timezone without separator while date 
has separator, which is a mix between
         // basic (no separators, both for date and timezone) and extended 
(separator for both)
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42+0200" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42-0200" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
+        assertThatExceptionOfType( IllegalArgumentException.class )
+            .isThrownBy( () -> archiver.parseOutputTimestamp( 
"2019-10-05T20:37:42+0200" ) );
+        assertThatExceptionOfType( IllegalArgumentException.class )
+            .isThrownBy( () -> archiver.parseOutputTimestamp( 
"2019-10-05T20:37:42-0200" ) );
+    }
 
-        // These unfortunately fail although the input is valid according to 
ISO 8601
-        // SDF does not allow strict telescoping parsing w/o permitting 
invalid input as depicted above.
-        // One has to use the new Java Time API for this.
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42+02" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42-02" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
+    @ParameterizedTest
+    @NullAndEmptySource
+    @ValueSource( strings = { ".", " ", "_", "-", "T", "/", "!", "!", "*", "ñ" 
} )
+    public void testEmptyParseOutputTimestampInstant( String value )
+    {
+        // Empty optional if null or 1 char
+        assertThat( MavenArchiver.parseBuildOutputTimestamp( value ) 
).isEmpty();
+    }
+
+    @ParameterizedTest
+    @CsvSource( { "0,0", "1,1", "9,9", "1570300662,1570300662", 
"2147483648,2147483648",
+        "2019-10-05T18:37:42Z,1570300662", 
"2019-10-05T20:37:42+02:00,1570300662",
+        "2019-10-05T16:37:42-02:00,1570300662", 
"1988-02-22T15:23:47.76598Z,572541827",
+        "2011-12-03T10:15:30+01:00[Europe/Paris],1322903730",

Review Comment:
   `2011-12-03T10:15:30+01:00[Europe/Paris]` is invalid. Should not pass.



##########
src/test/java/org/apache/maven/archiver/MavenArchiverTest.java:
##########
@@ -1444,54 +1447,76 @@ public void testParseOutputTimestamp()
         assertThat( archiver.parseOutputTimestamp( "*" ) ).isNull();
 
         assertThat( archiver.parseOutputTimestamp( "1570300662" ).getTime() 
).isEqualTo( 1570300662000L );
-        assertThat( archiver.parseOutputTimestamp( "0" ).getTime() 
).isEqualTo( 0L );
+        assertThat( archiver.parseOutputTimestamp( "0" ).getTime() ).isZero();
         assertThat( archiver.parseOutputTimestamp( "1" ).getTime() 
).isEqualTo( 1000L );
 
-        assertThat( archiver.parseOutputTimestamp( "2019-10-05T18:37:42Z" 
).getTime() ).isEqualTo( 1570300662000L );
-        assertThat( archiver.parseOutputTimestamp( "2019-10-05T20:37:42+02:00" 
).getTime() ).isEqualTo(
-                1570300662000L );
-        assertThat( archiver.parseOutputTimestamp( "2019-10-05T16:37:42-02:00" 
).getTime() ).isEqualTo(
-                1570300662000L );
+        assertThat( archiver.parseOutputTimestamp( "2019-10-05T18:37:42Z" 
).getTime() )
+            .isEqualTo( 1570300662000L );
+        assertThat( archiver.parseOutputTimestamp( "2019-10-05T20:37:42+02:00" 
).getTime() )
+            .isEqualTo( 1570300662000L );
+        assertThat( archiver.parseOutputTimestamp( "2019-10-05T16:37:42-02:00" 
).getTime() )
+            .isEqualTo( 1570300662000L );
 
         // These must result in IAE because we expect extended ISO format only 
(ie with - separator for date and
         // : separator for timezone), hence the XXX SimpleDateFormat for tz 
offset
         // X SimpleDateFormat accepts timezone without separator while date 
has separator, which is a mix between
         // basic (no separators, both for date and timezone) and extended 
(separator for both)
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42+0200" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42-0200" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
+        assertThatExceptionOfType( IllegalArgumentException.class )
+            .isThrownBy( () -> archiver.parseOutputTimestamp( 
"2019-10-05T20:37:42+0200" ) );
+        assertThatExceptionOfType( IllegalArgumentException.class )
+            .isThrownBy( () -> archiver.parseOutputTimestamp( 
"2019-10-05T20:37:42-0200" ) );
+    }
 
-        // These unfortunately fail although the input is valid according to 
ISO 8601
-        // SDF does not allow strict telescoping parsing w/o permitting 
invalid input as depicted above.
-        // One has to use the new Java Time API for this.
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42+02" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
-        try
-        {
-            archiver.parseOutputTimestamp( "2019-10-05T20:37:42-02" );
-            fail();
-        }
-        catch ( IllegalArgumentException ignored )
-        {
-        }
+    @ParameterizedTest
+    @NullAndEmptySource
+    @ValueSource( strings = { ".", " ", "_", "-", "T", "/", "!", "!", "*", "ñ" 
} )
+    public void testEmptyParseOutputTimestampInstant( String value )
+    {
+        // Empty optional if null or 1 char
+        assertThat( MavenArchiver.parseBuildOutputTimestamp( value ) 
).isEmpty();
+    }
+
+    @ParameterizedTest
+    @CsvSource( { "0,0", "1,1", "9,9", "1570300662,1570300662", 
"2147483648,2147483648",
+        "2019-10-05T18:37:42Z,1570300662", 
"2019-10-05T20:37:42+02:00,1570300662",
+        "2019-10-05T16:37:42-02:00,1570300662", 
"1988-02-22T15:23:47.76598Z,572541827",
+        "2011-12-03T10:15:30+01:00[Europe/Paris],1322903730",
+        "1980-01-01T00:00:02Z,315532802", "2099-12-31T23:59:59Z,4102444799" } )
+    public void testParseOutputTimestampInstant( String value, long expected )
+    {
+        assertThat( MavenArchiver.parseBuildOutputTimestamp( value ) )
+            .contains( Instant.ofEpochSecond( expected ) );
+    }
+
+    @ParameterizedTest
+    @ValueSource( strings = { "2019-10-05T20:37:42+0200", 
"2019-10-05T20:37:42-0200", "2019-10-05T25:00:00Z",
+        "2019-10-05", "XYZ", "Tue, 3 Jun 2008 11:05:30 GMT" } )
+    public void testThrownParseOutputTimestampInstant( String outputTimestamp )
+    {
+        // Invalid parsing
+        assertThatExceptionOfType( IllegalArgumentException.class )
+            .isThrownBy( () -> MavenArchiver.parseBuildOutputTimestamp( 
outputTimestamp ) )
+            .withCauseInstanceOf( DateTimeParseException.class );
+    }
+
+    @ParameterizedTest
+    @ValueSource( strings = { "1980-01-01T00:00:01Z", "2100-01-01T00:00Z", 
"2100-02-28T23:59:59Z",
+        "2099-12-31T23:59:59-01:00", 
"1980-01-01T00:15:35+01:00[Europe/Madrid]" } )

Review Comment:
   Same for the last.



##########
src/main/java/org/apache/maven/archiver/MavenArchiver.java:
##########
@@ -812,28 +820,78 @@ public void setBuildJdkSpecDefaultEntry( boolean 
buildJdkSpecDefaultEntry )
      * @return the parsed timestamp, may be <code>null</code> if 
<code>null</code> input or input contains only 1
      *         character
      * @since 3.5.0
-     * @throws java.lang.IllegalArgumentException if the outputTimestamp is 
neither ISO 8601 nor an integer
+     * @throws IllegalArgumentException if the outputTimestamp is neither ISO 
8601 nor an integer
+     * @deprecated Use {@link #parseBuildOutputTimestamp(String)} instead.
      */
+    @Deprecated
     public Date parseOutputTimestamp( String outputTimestamp )
     {
-        if ( StringUtils.isNumeric( outputTimestamp ) && 
StringUtils.isNotEmpty( outputTimestamp ) )
+        return parseBuildOutputTimestamp( outputTimestamp ).map( Date::from 
).orElse( null );
+    }
+
+    /**
+     * Configure Reproducible Builds archive creation if a timestamp is 
provided.
+     *
+     * @param outputTimestamp the value of {@code 
${project.build.outputTimestamp}} (may be {@code null})
+     * @return the parsed timestamp as {@link java.util.Date}
+     * @since 3.5.0
+     * @see #parseOutputTimestamp
+     * @deprecated Use {@link #configureReproducibleBuild(String)} instead.
+     */
+    @Deprecated
+    public Date configureReproducible( String outputTimestamp )
+    {
+        configureReproducibleBuild( outputTimestamp );
+        return parseOutputTimestamp( outputTimestamp );
+    }
+
+    /**
+     * Parse output timestamp configured for Reproducible Builds' archive 
entries.
+     *
+     * <p>Either as {@link 
java.time.format.DateTimeFormatter#ISO_ZONED_DATE_TIME} or as a number 
representing seconds
+     * since the epoch (like <a 
href="https://reproducible-builds.org/docs/source-date-epoch/";>SOURCE_DATE_EPOCH</a>).
+     *
+     * @param outputTimestamp the value of {@code 
${project.build.outputTimestamp}} (may be {@code null})
+     * @return the parsed timestamp as an {@code Optional<Instant>}, {@code 
empty} if input is {@code null} or input
+     *         contains only 1 character (not a number)
+     * @since 3.6.0
+     * @throws IllegalArgumentException if the outputTimestamp is neither ISO 
8601 nor an integer
+     */
+    public static Optional<Instant> parseBuildOutputTimestamp( String 
outputTimestamp )
+    {

Review Comment:
   I have the feeling that such parsing is done over and over again in many 
components. We need to consider to have ths at as central place.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@maven.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to