From 9d4b432840dc5db015b8b3f049875430a5545230 Mon Sep 17 00:00:00 2001 From: George Bateman Date: Mon, 28 Aug 2017 12:20:38 +0100 Subject: [PATCH 1/4] Allow faking of zip entry modification times. Adds DateUtils.parseLenientDateTime. --- .../org/apache/tools/ant/taskdefs/Zip.java | 43 +++++++++++++++- .../org/apache/tools/ant/util/DateUtils.java | 50 +++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/main/org/apache/tools/ant/taskdefs/Zip.java b/src/main/org/apache/tools/ant/taskdefs/Zip.java index 5d68b2f97..00ba91289 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Zip.java +++ b/src/main/org/apache/tools/ant/taskdefs/Zip.java @@ -25,6 +25,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -55,6 +56,7 @@ import org.apache.tools.ant.types.resources.FileResource; import org.apache.tools.ant.types.resources.Union; import org.apache.tools.ant.types.resources.ZipResource; import org.apache.tools.ant.types.resources.selectors.ResourceSelector; +import org.apache.tools.ant.util.DateUtils; import org.apache.tools.ant.util.FileNameMapper; import org.apache.tools.ant.util.FileUtils; import org.apache.tools.ant.util.GlobPatternMapper; @@ -122,6 +124,9 @@ public class Zip extends MatchingTask { } }; + private String fixedModTime = null; // User-provided. + protected long modTimeMillis = 0; // Calculated. + /** * If this flag is true, execute() will run most operations twice, * the first time with {@link #skipWriting skipWriting} set to @@ -584,6 +589,27 @@ public class Zip extends MatchingTask { return zip64Mode; } + /** + * Set all stored file modification times to {@code time}. + * @param time Milliseconds since 1970-01-01 00:00, or + * YYYY-MM-DD{T/ }HH:MM[:SS[.SSS]][ ][±ZZ[[:]ZZ]], or + * MM/DD/YYYY HH:MM[:SS] {AM/PM}, where {a/b} indicates + * that you must choose one of a or b, and [c] indicates that you + * may use or omit c. ±ZZZZ is the timezone offset, and may be + * literally "Z" to mean GMT. + */ + public void setModificationtime(String time) { + fixedModTime = time; + } + + /** + * The file modification time previously provided to + * {@link #setModificationtime(String)} or {@code null} if unset. + */ + public String getModificationtime() { + return fixedModTime; + } + /** * validate and build * @throws BuildException on error @@ -836,6 +862,17 @@ public class Zip extends MatchingTask { + archiveType + " file to create!"); } + if (fixedModTime != null) { + try { + modTimeMillis = DateUtils.parseLenientDateTime(fixedModTime).getTime(); + } catch (ParseException pe) { + throw new BuildException("Failed to parse date string " + fixedModTime + "."); + } + if (roundUp) { + modTimeMillis += ROUNDUP_MILLIS; + } + } + if (zipFile.exists() && !zipFile.isFile()) { throw new BuildException(zipFile + " is not a file."); } @@ -1716,7 +1753,9 @@ public class Zip extends MatchingTask { // ZIPs store time with a granularity of 2 seconds, round up final int millisToAdd = roundUp ? ROUNDUP_MILLIS : 0; - if (dir != null && dir.isExists()) { + if (fixedModTime != null) { + ze.setTime(modTimeMillis); + } else if (dir != null && dir.isExists()) { ze.setTime(dir.getLastModified() + millisToAdd); } else { ze.setTime(System.currentTimeMillis() + millisToAdd); @@ -1803,7 +1842,7 @@ public class Zip extends MatchingTask { if (!skipWriting) { final ZipEntry ze = new ZipEntry(vPath); - ze.setTime(lastModified); + ze.setTime(fixedModTime != null ? modTimeMillis : lastModified); ze.setMethod(doCompress ? ZipEntry.DEFLATED : ZipEntry.STORED); /* diff --git a/src/main/org/apache/tools/ant/util/DateUtils.java b/src/main/org/apache/tools/ant/util/DateUtils.java index 9ce737b0a..37874eed4 100644 --- a/src/main/org/apache/tools/ant/util/DateUtils.java +++ b/src/main/org/apache/tools/ant/util/DateUtils.java @@ -26,6 +26,10 @@ import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.tools.ant.taskdefs.Touch; /** * Helper methods to deal with date/time formatting with a specific @@ -298,4 +302,50 @@ public final class DateUtils { return parseIso8601Date(datestr); } } + + final private static ThreadLocal iso8601WithTimeZone = + new ThreadLocal() { + @Override protected DateFormat initialValue() { + // An arbitrary easy-to-read format to normalize to. + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS Z"); + } + }; + final private static Pattern iso8601normalizer = Pattern.compile( + "^(\\d{4,}-\\d{2}-\\d{2})[Tt ]" + // yyyy-MM-dd + "(\\d{2}:\\d{2}(:\\d{2}(\\.\\d{3})?)?) ?" + // HH:mm:ss.SSS + "(?:Z|([+-]\\d{2})(?::?(\\d{2}))?)?$"); // Z + + /** + * Parse a lenient ISO 8601, ms since epoch, or {@code }-style date. + * That is: + *
    + *
  • Milliseconds since 1970-01-01 00:00
  • + *
  • YYYY-MM-DD{T| }HH:MM[:SS[.SSS]][ ][±ZZ[[:]ZZ]]
  • + *
  • MM/DD/YYYY HH:MM[:SS] {AM|PM}
+ * where {a|b} indicates that you must choose one of a or b, and [c] + * indicates that you may use or omit c. ±ZZZZ is the timezone offset, and + * may be literally "Z" to mean GMT. + */ + public static Date parseLenientDateTime(String dateStr) throws ParseException { + try { + return new Date(Long.parseLong(dateStr)); + } catch (NumberFormatException nfe) {} + + try { + return Touch.DEFAULT_DF_FACTORY.getPrimaryFormat().parse(dateStr); + } catch (ParseException pe) {} + + try { + return Touch.DEFAULT_DF_FACTORY.getFallbackFormat().parse(dateStr); + } catch (ParseException pe) {} + + Matcher m = iso8601normalizer.matcher(dateStr); + if (!m.find()) throw new ParseException(dateStr, 0); + String normISO = m.group(1) + " " + + (m.group(3) == null ? m.group(2) + ":00" : m.group(2)) + + (m.group(4) == null ? ".000 " : " ") + + (m.group(5) == null ? "+00" : m.group(5)) + + (m.group(6) == null ? "00" : m.group(6)); + return iso8601WithTimeZone.get().parse(normISO); + } } From a0cb1fa8434e6385fb115951bf74ea8c78a6fd07 Mon Sep 17 00:00:00 2001 From: George Bateman Date: Mon, 25 Sep 2017 12:08:18 +0100 Subject: [PATCH 2/4] Reverse dependancy of DateUtils on Touch Also add test --- .../org/apache/tools/ant/taskdefs/Touch.java | 26 ++++----------- .../org/apache/tools/ant/util/DateUtils.java | 32 ++++++++++++++++--- src/tests/antunit/taskdefs/zip-test.xml | 20 ++++++++++++ 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/main/org/apache/tools/ant/taskdefs/Touch.java b/src/main/org/apache/tools/ant/taskdefs/Touch.java index c6d79b7bd..5b9722bdd 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Touch.java +++ b/src/main/org/apache/tools/ant/taskdefs/Touch.java @@ -39,6 +39,7 @@ import org.apache.tools.ant.types.resources.FileProvider; import org.apache.tools.ant.types.resources.FileResource; import org.apache.tools.ant.types.resources.Touchable; import org.apache.tools.ant.types.resources.Union; +import org.apache.tools.ant.util.DateUtils; import org.apache.tools.ant.util.FileNameMapper; import org.apache.tools.ant.util.FileUtils; @@ -59,31 +60,18 @@ public class Touch extends Task { DateFormat getFallbackFormat(); } + /** + * Provides access to DateUtils.EN_US_DATE_FORMAT_MIN (primary) and + * DateUtils.EN_US_DATE_FORMAT_SEC (fallback). + */ public static final DateFormatFactory DEFAULT_DF_FACTORY = new DateFormatFactory() { - private ThreadLocal primary = - new ThreadLocal() { - @Override - protected DateFormat initialValue() { - return new SimpleDateFormat("MM/dd/yyyy hh:mm a", - Locale.US); - } - }; - private ThreadLocal fallback = - new ThreadLocal() { - @Override - protected DateFormat initialValue() { - return new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a", - Locale.US); - } - }; - public DateFormat getPrimaryFormat() { - return primary.get(); + return DateUtils.EN_US_DATE_FORMAT_MIN.get(); } public DateFormat getFallbackFormat() { - return fallback.get(); + return DateUtils.EN_US_DATE_FORMAT_SEC.get(); } }; private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); diff --git a/src/main/org/apache/tools/ant/util/DateUtils.java b/src/main/org/apache/tools/ant/util/DateUtils.java index 37874eed4..992c3fc5d 100644 --- a/src/main/org/apache/tools/ant/util/DateUtils.java +++ b/src/main/org/apache/tools/ant/util/DateUtils.java @@ -29,8 +29,6 @@ import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.apache.tools.ant.taskdefs.Touch; - /** * Helper methods to deal with date/time formatting with a specific * defined format (ISO8601) @@ -93,6 +91,32 @@ public final class DateUtils { private static final ChoiceFormat SECONDS_FORMAT = new ChoiceFormat(LIMITS, SECONDS_PART); + /** + * Provides a thread-local US-style date format. Exactly as used by + * {@code }, to minute precision: + * {@code SimpleDateFormat("MM/dd/yyyy hh:mm a", Locale.US)} + */ + public static final ThreadLocal EN_US_DATE_FORMAT_MIN = + new ThreadLocal() { + @Override + protected DateFormat initialValue() { + return new SimpleDateFormat("MM/dd/yyyy hh:mm a", Locale.US); + } + }; + + /** + * Provides a thread-local US-style date format. Exactly as used by + * {@code }, to second precision: + * {@code SimpleDateFormat("MM/dd/yyyy hh:mm:ss a", Locale.US)} + */ + public static final ThreadLocal EN_US_DATE_FORMAT_SEC = + new ThreadLocal() { + @Override + protected DateFormat initialValue() { + return new SimpleDateFormat("MM/dd/yyyy hh:mm:ss a", Locale.US); + } + }; + static { MINUTE_SECONDS.setFormat(0, MINUTES_FORMAT); MINUTE_SECONDS.setFormat(1, SECONDS_FORMAT); @@ -332,11 +356,11 @@ public final class DateUtils { } catch (NumberFormatException nfe) {} try { - return Touch.DEFAULT_DF_FACTORY.getPrimaryFormat().parse(dateStr); + return EN_US_DATE_FORMAT_MIN.get().parse(dateStr); } catch (ParseException pe) {} try { - return Touch.DEFAULT_DF_FACTORY.getFallbackFormat().parse(dateStr); + return EN_US_DATE_FORMAT_SEC.get().parse(dateStr); } catch (ParseException pe) {} Matcher m = iso8601normalizer.matcher(dateStr); diff --git a/src/tests/antunit/taskdefs/zip-test.xml b/src/tests/antunit/taskdefs/zip-test.xml index 6a054f13c..c2dfbe34b 100644 --- a/src/tests/antunit/taskdefs/zip-test.xml +++ b/src/tests/antunit/taskdefs/zip-test.xml @@ -182,4 +182,24 @@ + + + + + + + + + + + + + + + + + + + + From 2c5430b420890ac5ac68794f95c2ac83e5a27456 Mon Sep 17 00:00:00 2001 From: George Bateman Date: Tue, 26 Sep 2017 22:43:28 +0100 Subject: [PATCH 3/4] Fix zip-test --- src/tests/antunit/taskdefs/zip-test.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/tests/antunit/taskdefs/zip-test.xml b/src/tests/antunit/taskdefs/zip-test.xml index c2dfbe34b..7ff45c1ad 100644 --- a/src/tests/antunit/taskdefs/zip-test.xml +++ b/src/tests/antunit/taskdefs/zip-test.xml @@ -185,19 +185,19 @@ + + - - - + + - - - - + + + From 01613e0d8275665b850145fa02ae86a5a90a965d Mon Sep 17 00:00:00 2001 From: Stefan Bodewig Date: Thu, 28 Sep 2017 12:31:04 +0200 Subject: [PATCH 4/4] record change, add @since tags, closes #36 --- WHATSNEW | 4 ++++ src/main/org/apache/tools/ant/taskdefs/Zip.java | 4 +++- src/main/org/apache/tools/ant/util/DateUtils.java | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/WHATSNEW b/WHATSNEW index f8701a924..eeae3e868 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -35,6 +35,10 @@ Other changes: it uploads, if the preserveLastModified attribute is set to true for that task + * zip and the related tasks can now set the modfication time of all + entries to a fixed timestamp. + Github Pull Request #36 + Changes from Ant 1.9.8 TO Ant 1.9.9 =================================== diff --git a/src/main/org/apache/tools/ant/taskdefs/Zip.java b/src/main/org/apache/tools/ant/taskdefs/Zip.java index 00ba91289..d1e10654b 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Zip.java +++ b/src/main/org/apache/tools/ant/taskdefs/Zip.java @@ -125,7 +125,7 @@ public class Zip extends MatchingTask { }; private String fixedModTime = null; // User-provided. - protected long modTimeMillis = 0; // Calculated. + private long modTimeMillis = 0; // Calculated. /** * If this flag is true, execute() will run most operations twice, @@ -597,6 +597,7 @@ public class Zip extends MatchingTask { * that you must choose one of a or b, and [c] indicates that you * may use or omit c. ±ZZZZ is the timezone offset, and may be * literally "Z" to mean GMT. + * @since Ant 1.9.10 */ public void setModificationtime(String time) { fixedModTime = time; @@ -605,6 +606,7 @@ public class Zip extends MatchingTask { /** * The file modification time previously provided to * {@link #setModificationtime(String)} or {@code null} if unset. + * @since Ant 1.9.10 */ public String getModificationtime() { return fixedModTime; diff --git a/src/main/org/apache/tools/ant/util/DateUtils.java b/src/main/org/apache/tools/ant/util/DateUtils.java index 992c3fc5d..016ce9d4e 100644 --- a/src/main/org/apache/tools/ant/util/DateUtils.java +++ b/src/main/org/apache/tools/ant/util/DateUtils.java @@ -95,6 +95,7 @@ public final class DateUtils { * Provides a thread-local US-style date format. Exactly as used by * {@code }, to minute precision: * {@code SimpleDateFormat("MM/dd/yyyy hh:mm a", Locale.US)} + * @since Ant 1.9.10 */ public static final ThreadLocal EN_US_DATE_FORMAT_MIN = new ThreadLocal() { @@ -108,6 +109,7 @@ public final class DateUtils { * Provides a thread-local US-style date format. Exactly as used by * {@code }, to second precision: * {@code SimpleDateFormat("MM/dd/yyyy hh:mm:ss a", Locale.US)} + * @since Ant 1.9.10 */ public static final ThreadLocal EN_US_DATE_FORMAT_SEC = new ThreadLocal() { @@ -349,6 +351,7 @@ public final class DateUtils { * where {a|b} indicates that you must choose one of a or b, and [c] * indicates that you may use or omit c. ±ZZZZ is the timezone offset, and * may be literally "Z" to mean GMT. + * @since Ant 1.9.10 */ public static Date parseLenientDateTime(String dateStr) throws ParseException { try {