This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ant.git
The following commit(s) were added to refs/heads/master by this push:
new c13ddfd5a move DateUtils#getNow to Project and make it only do one
thing
c13ddfd5a is described below
commit c13ddfd5a7e621c03f396569326de3e2aca6afb5
Author: Stefan Bodewig <[email protected]>
AuthorDate: Sun Aug 9 20:01:41 2026 +0200
move DateUtils#getNow to Project and make it only do one thing
---
WHATSNEW | 6 ++
src/main/org/apache/tools/ant/Project.java | 66 ++++++++++++++++
.../tools/ant/taskdefs/optional/PropertyFile.java | 5 +-
src/main/org/apache/tools/ant/util/DateUtils.java | 88 +++-------------------
.../dateutils-test.xml => core/project-test.xml} | 13 +---
5 files changed, 86 insertions(+), 92 deletions(-)
diff --git a/WHATSNEW b/WHATSNEW
index 1f719b63c..79f2c221d 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -22,6 +22,12 @@ Other changes:
Based on a patch used by the Debian Ant package maintainers.
Part of Bugzilla Report 61269
+ * a new method getNow in Project can now be used to calculate a
+ notion of "now" that consults the SOURCE_DATE_EPOCH environment
+ variable as well as the magic ant.tstamp.now and ant.tstamp.now.iso
+ properties in this order which you can use if you want to obtain a
+ reproducible timestamp in task you write yourself.
+
* The "record" task now has a new "relativeToBaseDir" attribute,
which can be set to "yes" or "no", to control where the
recorder's file gets created. In the absence of this attribute,
diff --git a/src/main/org/apache/tools/ant/Project.java
b/src/main/org/apache/tools/ant/Project.java
index 2cd2ba9c5..274b94de7 100644
--- a/src/main/org/apache/tools/ant/Project.java
+++ b/src/main/org/apache/tools/ant/Project.java
@@ -23,8 +23,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
@@ -35,6 +37,8 @@ import java.util.Set;
import java.util.Stack;
import java.util.Vector;
import java.util.WeakHashMap;
+import java.util.function.BiFunction;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.tools.ant.helper.DefaultExecutor;
@@ -48,6 +52,7 @@ import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceFactory;
import org.apache.tools.ant.types.resources.FileResource;
+import org.apache.tools.ant.util.DateUtils;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.JavaEnvUtils;
import org.apache.tools.ant.util.VectorSet;
@@ -2484,4 +2489,65 @@ public class Project implements ResourceFactory {
public Resource getResource(final String name) {
return new FileResource(getBaseDir(), name);
}
+
+ /**
+ * Consults {@linkplain #ENV_SOURCE_DATE_EPOCH SOURCE_DATE_EPOCH}
environment variable and
+ * the magic properties {@link MagicNames#TSTAMP_NOW_ISO} and {@link
MagicNames#TSTAMP_NOW}
+ * for predefined values of "now" and falls back to {@code new Date()} if
neither is set.
+ *
+ * <p>{@code SOURCE_DATE_EPOCH} takes precedence over {@link
MagicNames#TSTAMP_NOW_ISO} which
+ * in turn takes precedence over {@link MagicNames#TSTAMP_NOW}.</p>
+ *
+ * @return "now" as explained above
+ * @since Ant 1.10.18
+ */
+ public Date getNow() {
+ final String epoch = System.getenv(DateUtils.ENV_SOURCE_DATE_EPOCH);
+ if (epoch != null) {
+ // Value of SOURCE_DATE_EPOCH will be an integer, representing
seconds.
+ try {
+ Date d = new Date(Long.parseLong(epoch) * 1000L);
+ log("Honouring environment variable " +
DateUtils.ENV_SOURCE_DATE_EPOCH
+ + " which has been set to " + epoch);
+ return d;
+ } catch(NumberFormatException e) {
+ // ignore
+ log("Ignoring invalid value '" + epoch + "' for " +
DateUtils.ENV_SOURCE_DATE_EPOCH
+ + " environment variable", Project.MSG_DEBUG);
+ }
+ }
+ return getNowAsDate();
+ }
+
+ private Date getNowAsDate() {
+ Optional<Date> now = getNowAsDate(
+ 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 = getNowAsDate(
+ MagicNames.TSTAMP_NOW,
+ s -> new Date(1000 * Long.parseLong(s)),
+ (k, v) -> "magic property " + k + " ignored as " + v + " is not a
valid number"
+ );
+ return now.orElseGet(Date::new);
+ }
+
+ private Optional<Date> getNowAsDate(String propertyName, Function<String,
Date> map,
+ BiFunction<String, String, String>
log) {
+ String property = getProperty(propertyName);
+ if (property != null && !property.isEmpty()) {
+ try {
+ return Optional.ofNullable(map.apply(property));
+ } catch (Exception e) {
+ log(log.apply(propertyName, property));
+ }
+ }
+ return Optional.empty();
+ }
+
}
diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java
b/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java
index fef3af613..a6cfdadce 100644
--- a/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java
+++ b/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java
@@ -193,10 +193,9 @@ public class PropertyFile extends Task {
properties = new Properties();
} else {
LayoutPreservingProperties p = new LayoutPreservingProperties();
- Map.Entry<Date, Boolean> now = DateUtils.getNow(getProject());
Calendar c = Calendar.getInstance();
- c.setTime(now.getKey());
- TimeZone tz = Boolean.TRUE.equals(now.getValue())
+ c.setTime(getProject().getNow());
+ TimeZone tz = System.getenv(DateUtils.ENV_SOURCE_DATE_EPOCH) !=
null
? TimeZone.getTimeZone("UTC") : null;
p.setDateComment(c, tz);
properties = p;
diff --git a/src/main/org/apache/tools/ant/util/DateUtils.java
b/src/main/org/apache/tools/ant/util/DateUtils.java
index 9e2221749..abc338456 100644
--- a/src/main/org/apache/tools/ant/util/DateUtils.java
+++ b/src/main/org/apache/tools/ant/util/DateUtils.java
@@ -22,22 +22,13 @@ import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
-import java.util.AbstractMap;
import java.util.Calendar;
-import java.time.Instant;
import java.util.Date;
import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
import java.util.TimeZone;
-import java.util.function.BiFunction;
-import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.apache.tools.ant.MagicNames;
-import org.apache.tools.ant.Project;
-
/**
* Helper methods to deal with date/time formatting with a specific
* defined format (<a href="https://www.w3.org/TR/NOTE-datetime">ISO8601</a>)
@@ -52,6 +43,15 @@ public final class DateUtils {
private static final int ONE_MINUTE = 60;
private static final int ONE_HOUR = 60;
private static final int TEN = 10;
+
+ /**
+ * Name of the environment variable used to set timestamps for
reproducible builds.
+ *
+ * @see "https://reproducible-builds.org/docs/source-date-epoch/"
+ * @since Ant 1.10.18
+ */
+ public static final String ENV_SOURCE_DATE_EPOCH = "SOURCE_DATE_EPOCH";
+
/**
* ISO8601-like pattern for date-time. It does not support timezone.
* <code>yyyy-MM-ddTHH:mm:ss</code>
@@ -395,74 +395,4 @@ public final class DateUtils {
+ (m.group(6) == null ? "00" : m.group(6));
return iso8601WithTimeZone.get().parse(normISO);
}
-
- /**
- * Name of the environment variable used to set timestamps for
reproducible builds.
- *
- * @see "https://reproducible-builds.org/docs/source-date-epoch/"
- * @since Ant 1.10.18
- */
- public static final String ENV_SOURCE_DATE_EPOCH = "SOURCE_DATE_EPOCH";
-
- /**
- * Consults {@linkplain #ENV_SOURCE_DATE_EPOCH SOURCE_DATE_EPOCH}
environment variable and
- * the magic properties {@link MagicNames#TSTAMP_NOW_ISO} and {@link
MagicNames#TSTAMP_NOW}
- * for predefined values of "now" and falls back to {@code new Date()} if
neither is set.
- *
- * <p>{@code SOURCE_DATE_EPOCH} takes precedence over {@link
MagicNames#TSTAMP_NOW_ISO} which
- * in turn takes precedence over {@link MagicNames#TSTAMP_NOW}.</p>
- *
- * @param project Project instance to use when looking up the magic
properties.
- * @return a tuple of "now" and a boolean flag that indicates whether
{@code SOURCE_DATE_EPOCH} has been set.
- * @since Ant 1.10.18
- */
- public static Map.Entry<Date, Boolean> getNow(Project project) {
- final String epoch = System.getenv(ENV_SOURCE_DATE_EPOCH);
- if (epoch != null) {
- // Value of SOURCE_DATE_EPOCH will be an integer, representing
seconds.
- try {
- Date d = new Date(Long.parseLong(epoch) * 1000L);
- project.log("Honouring environment variable " +
ENV_SOURCE_DATE_EPOCH + " which has been set to " + epoch);
- return new AbstractMap.SimpleImmutableEntry(d, true);
- } catch(NumberFormatException e) {
- // ignore
- project.log("Ignoring invalid value '" + epoch + "' for " +
ENV_SOURCE_DATE_EPOCH
- + " environment variable", Project.MSG_DEBUG);
- }
- }
- return new AbstractMap.SimpleImmutableEntry(getNowAsDate(project),
false);
- }
-
- private static Date getNowAsDate(Project p) {
- Optional<Date> now = getNowAsDate(
- p,
- 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 = getNowAsDate(
- p,
- MagicNames.TSTAMP_NOW,
- s -> new Date(1000 * Long.parseLong(s)),
- (k, v) -> "magic property " + k + " ignored as " + v + " is not a
valid number"
- );
- return now.orElseGet(Date::new);
- }
-
- private static Optional<Date> getNowAsDate(Project p, String propertyName,
Function<String, Date> map,
- BiFunction<String, String,
String> log) {
- String property = p.getProperty(propertyName);
- if (property != null && !property.isEmpty()) {
- try {
- return Optional.ofNullable(map.apply(property));
- } catch (Exception e) {
- p.log(log.apply(propertyName, property));
- }
- }
- return Optional.empty();
- }
}
diff --git a/src/tests/antunit/util/dateutils-test.xml
b/src/tests/antunit/core/project-test.xml
similarity index 86%
rename from src/tests/antunit/util/dateutils-test.xml
rename to src/tests/antunit/core/project-test.xml
index f488e4fb0..09c663fc3 100644
--- a/src/tests/antunit/util/dateutils-test.xml
+++ b/src/tests/antunit/core/project-test.xml
@@ -15,7 +15,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<project default="antunit" xmlns:au="antlib:org.apache.ant.antunit">
+<project default="antunit" name="project-test"
xmlns:au="antlib:org.apache.ant.antunit">
<import file="../antunit-base.xml" />
<target name="-createNowPrinter">
@@ -24,7 +24,6 @@
<echo file="${input}/NowPrinter.java"><![CDATA[
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
- import org.apache.tools.ant.util.DateUtils;
import java.text.SimpleDateFormat;
import java.util.*;
public class NowPrinter {
@@ -37,11 +36,10 @@
for (int i = 0; i < argsLen; i += 2) {
p.setProperty(args[i], args[i + 1]);
}
- Map.Entry<Date, Boolean> now = DateUtils.getNow(p);
+ Date now = p.getNow();
SimpleDateFormat format = new
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
format.setTimeZone(TimeZone.getTimeZone("UTC"));
- System.out.println("NOW is " + format.format(now.getKey()));
- System.out.println("REPRODUCIBLE_BUILDS is " + now.getValue());
+ System.out.println("NOW is " + format.format(now));
}
}
]]></echo>
@@ -62,7 +60,6 @@
<arg value="86400"/>
</java>
<au:assertPropertyContains name="testout" value="NOW is
1970-01-02T00:00:00"/>
- <au:assertPropertyContains name="testout" value="REPRODUCIBLE_BUILDS is
false"/>
</target>
<target name="testGetNowMagicPropertyIso" depends="-createNowPrinter">
@@ -79,7 +76,6 @@
<arg value="1972-04-17T08:07:00Z"/>
</java>
<au:assertPropertyContains name="testout" value="NOW is
1972-04-17T08:07:00"/>
- <au:assertPropertyContains name="testout" value="REPRODUCIBLE_BUILDS is
false"/>
</target>
<target name="testGetNowMagicPropertyBoth" depends="-createNowPrinter">
@@ -98,7 +94,6 @@
<arg value="1972-04-17T08:07:00Z"/>
</java>
<au:assertPropertyContains name="testout" value="NOW is
1972-04-17T08:07:00"/>
- <au:assertPropertyContains name="testout" value="REPRODUCIBLE_BUILDS is
false"/>
</target>
<target name="testGetNowSourceDateEpoch" depends="-createNowPrinter">
@@ -118,7 +113,6 @@
<env key="SOURCE_DATE_EPOCH" value="1650585600"/>
</java>
<au:assertPropertyContains name="testout" value="NOW is
2022-04-22T00:00:00"/>
- <au:assertPropertyContains name="testout" value="REPRODUCIBLE_BUILDS is
true"/>
</target>
<target name="testGetNowRealNow" depends="-createNowPrinter">
@@ -139,6 +133,5 @@
</classpath>
</java>
<au:assertPropertyContains name="testout" value="NOW is ${dstamp}T"/>
- <au:assertPropertyContains name="testout" value="REPRODUCIBLE_BUILDS is
false"/>
</target>
</project>