From 9d4b432840dc5db015b8b3f049875430a5545230 Mon Sep 17 00:00:00 2001 From: George Bateman Date: Mon, 28 Aug 2017 12:20:38 +0100 Subject: [PATCH] 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); + } }