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> | probably in an initialization target.</p> | ||||
| <p><em>Since Ant 1.10.2</em> the magic | <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> | <h3>Parameters</h3> | ||||
| <table border="1" cellpadding="2" cellspacing="0"> | <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>number, seconds since the epoch (midnight 1970-01-01)</td> | ||||
| <td>The value to use as current time and date for <tstamp></td> | <td>The value to use as current time and date for <tstamp></td> | ||||
| </tr> | </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> | </table> | ||||
| <p> | <p> | ||||
| @@ -308,5 +308,18 @@ public final class MagicNames { | |||||
| * @since Ant 1.10.2 | * @since Ant 1.10.2 | ||||
| */ | */ | ||||
| public static final String TSTAMP_NOW = "ant.tstamp.now"; | 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; | package org.apache.tools.ant.taskdefs; | ||||
| import java.text.SimpleDateFormat; | import java.text.SimpleDateFormat; | ||||
| import java.time.Instant; | |||||
| import java.util.Calendar; | import java.util.Calendar; | ||||
| import java.util.Date; | import java.util.Date; | ||||
| import java.util.HashMap; | import java.util.HashMap; | ||||
| @@ -26,9 +27,12 @@ import java.util.List; | |||||
| import java.util.Locale; | import java.util.Locale; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| import java.util.NoSuchElementException; | import java.util.NoSuchElementException; | ||||
| import java.util.Optional; | |||||
| import java.util.StringTokenizer; | import java.util.StringTokenizer; | ||||
| import java.util.TimeZone; | import java.util.TimeZone; | ||||
| import java.util.Vector; | 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.BuildException; | ||||
| import org.apache.tools.ant.Location; | 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. | * Return the {@link Date} instance to use as base for DSTAMP, TSTAMP and TODAY. | ||||
| */ | */ | ||||
| protected Date getNow() { | 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 { | 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/> | <tstamp/> | ||||
| <au:assertPropertyEquals name="DSTAMP" value="19700102"/> | <au:assertPropertyEquals name="DSTAMP" value="19700102"/> | ||||
| </target> | </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> | </project> | ||||