https://bz.apache.org/bugzilla/show_bug.cgi?id=61079master
| @@ -41,10 +41,12 @@ to indicate, for example, the release date. The best place for this task is | |||
| probably in an initialization target.</p> | |||
| <p><em>Since Ant 1.10.2</em> the magic | |||
| property <code>ant.tstamp.now</code> can be used to specify a fixed | |||
| date value in order to create reproducible builds. Its value must be | |||
| a number and is interpreted as seconds since the epoch (midnight | |||
| 1970-01-01).</p> | |||
| property <code>ant.tstamp.now</code> can be used to specify a fixed | |||
| date value in order to create reproducible builds. Its value must be | |||
| a number and is interpreted as seconds since the epoch (midnight | |||
| 1970-01-01). With <code>ant.tstamp.now.iso</code> you could also specify that | |||
| value in ISO-8601 format (<code>1972-04-17T08:07:00Z</code>). If you specify a value | |||
| in an invalid format an INFO message will be logged and the value will be ignored.</p> | |||
| <h3>Parameters</h3> | |||
| <table border="1" cellpadding="2" cellspacing="0"> | |||
| @@ -490,6 +490,10 @@ org.apache.tools.ant.Executor implementation specified here. | |||
| <td>number, seconds since the epoch (midnight 1970-01-01)</td> | |||
| <td>The value to use as current time and date for <tstamp></td> | |||
| </tr> | |||
| <tr> | |||
| <td><code>ant.tstamp.now.iso</code></td> | |||
| <td>ISO-8601 timestamp string like <code>1972-04-17T08:07:00Z</code></td> | |||
| </tr> | |||
| </table> | |||
| <p> | |||
| @@ -308,5 +308,18 @@ public final class MagicNames { | |||
| * @since Ant 1.10.2 | |||
| */ | |||
| public static final String TSTAMP_NOW = "ant.tstamp.now"; | |||
| /** | |||
| * Magic property that can be set to contain a value for tstamp's | |||
| * "now" in order to make builds that use the task create | |||
| * reproducible results. | |||
| * | |||
| * <p>The value is expected to be in ISO time format | |||
| * (<i>1972-04-17T08:07</i>)</p> | |||
| * | |||
| * Value: {@value} | |||
| * @since Ant 1.10.2 | |||
| */ | |||
| public static final String TSTAMP_NOW_ISO = "ant.tstamp.now.iso"; | |||
| } | |||
| @@ -19,6 +19,7 @@ | |||
| package org.apache.tools.ant.taskdefs; | |||
| import java.text.SimpleDateFormat; | |||
| import java.time.Instant; | |||
| import java.util.Calendar; | |||
| import java.util.Date; | |||
| import java.util.HashMap; | |||
| @@ -26,9 +27,12 @@ import java.util.List; | |||
| import java.util.Locale; | |||
| import java.util.Map; | |||
| import java.util.NoSuchElementException; | |||
| import java.util.Optional; | |||
| import java.util.StringTokenizer; | |||
| import java.util.TimeZone; | |||
| import java.util.Vector; | |||
| import java.util.function.BiFunction; | |||
| import java.util.function.Function; | |||
| import org.apache.tools.ant.BuildException; | |||
| import org.apache.tools.ant.Location; | |||
| @@ -111,16 +115,45 @@ public class Tstamp extends Task { | |||
| * Return the {@link Date} instance to use as base for DSTAMP, TSTAMP and TODAY. | |||
| */ | |||
| protected Date getNow() { | |||
| String magicNow = getProject().getProperty(MagicNames.TSTAMP_NOW); | |||
| if (magicNow != null && magicNow.length() > 0) { | |||
| Optional<Date> now = getNow( | |||
| MagicNames.TSTAMP_NOW_ISO, | |||
| s -> Date.from(Instant.parse(s)), | |||
| (k, v) -> "magic property " + k + " ignored as '" + v + "' is not in valid ISO pattern" | |||
| ); | |||
| if (now.isPresent()) { | |||
| return now.get(); | |||
| } | |||
| now = getNow( | |||
| MagicNames.TSTAMP_NOW, | |||
| s -> new Date(1000 * Long.parseLong(s)), | |||
| (k, v) -> "magic property " + k + " ignored as " + v + " is not a valid number" | |||
| ); | |||
| if (now.isPresent()) { | |||
| return now.get(); | |||
| } | |||
| return new Date(); | |||
| } | |||
| /** | |||
| * Checks and returns a Date if the specified property is set. | |||
| * @param propertyName name of the property to check | |||
| * @param map convertion of the property value as string to Date | |||
| * @param log supplier of the log message containg the property name and value if | |||
| * the convertion fails | |||
| * @return Optional containing the Date or null | |||
| */ | |||
| protected Optional<Date> getNow(String propertyName, Function<String, Date> map, BiFunction<String, String, String> log) { | |||
| String property = getProject().getProperty(propertyName); | |||
| if (property != null && property.length() > 0) { | |||
| try { | |||
| return new Date(1000 * Long.parseLong(magicNow)); | |||
| } catch (NumberFormatException ex) { | |||
| log("magic property " + MagicNames.TSTAMP_NOW + " ignored as " | |||
| + magicNow + " is not a valid number"); | |||
| return Optional.ofNullable(map.apply(property)); | |||
| } catch (Exception e) { | |||
| log(log.apply(propertyName, property)); | |||
| } | |||
| } | |||
| return new Date(); | |||
| return Optional.empty(); | |||
| } | |||
| /** | |||
| @@ -24,4 +24,21 @@ | |||
| <tstamp/> | |||
| <au:assertPropertyEquals name="DSTAMP" value="19700102"/> | |||
| </target> | |||
| <target name="testMagicPropertyIso"> | |||
| <local name="ant.tstamp.now.iso"/> | |||
| <property name="ant.tstamp.now.iso" value="1972-04-17T08:07:00Z"/> | |||
| <tstamp/> | |||
| <au:assertPropertyEquals name="DSTAMP" value="19720417"/> | |||
| </target> | |||
| <target name="testMagicPropertyBoth"> | |||
| <local name="ant.tstamp.now"/> | |||
| <local name="ant.tstamp.now.iso"/> | |||
| <property name="ant.tstamp.now" value="100000"/> | |||
| <property name="ant.tstamp.now.iso" value="1972-04-17T08:07:22Z"/> | |||
| <tstamp/> | |||
| <!-- 'iso' overrides 'simple' --> | |||
| <au:assertPropertyEquals name="DSTAMP" value="19720417"/> | |||
| </target> | |||
| </project> | |||