diff --git a/api/maven-api-core/pom.xml b/api/maven-api-core/pom.xml
index ae950fbb48..f713e2050f 100644
--- a/api/maven-api-core/pom.xml
+++ b/api/maven-api-core/pom.xml
@@ -59,6 +59,12 @@
org.apache.mavenmaven-api-di
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java
index dacf1ec6a2..a80fbd68f6 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java
@@ -437,5 +437,13 @@ public final class Constants {
@Config(type = "java.lang.Integer")
public static final String MAVEN_DEPLOY_SNAPSHOT_BUILD_NUMBER = "maven.deploy.snapshot.buildNumber";
+ /**
+ * User property used to store the build timestamp.
+ *
+ * @since 4.1.0
+ */
+ @Config(type = "java.time.Instant")
+ public static final String MAVEN_START_INSTANT = "maven.startInstant";
+
private Constants() {}
}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/MonotonicClock.java b/api/maven-api-core/src/main/java/org/apache/maven/api/MonotonicClock.java
new file mode 100644
index 0000000000..5fa3de655c
--- /dev/null
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/MonotonicClock.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.api;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+
+/**
+ * A Clock implementation that combines monotonic timing with wall-clock time.
+ *
+ * This class provides precise time measurements using {@link System#nanoTime()}
+ * while maintaining wall-clock time information in UTC. The wall-clock time
+ * is computed from the monotonic duration since system start to ensure consistency
+ * between time measurements.
+ *
+ * This implementation is singleton-based and always uses UTC timezone. The clock
+ * cannot be adjusted to different timezones to maintain consistent monotonic behavior.
+ * Users needing local time representation should convert the result of {@link #instant()}
+ * to their desired timezone:
+ *
{@code
+ * Instant now = MonotonicClock.now();
+ * ZonedDateTime local = now.atZone(ZoneId.systemDefault());
+ * }
+ *
+ * @see System#nanoTime()
+ * @see Clock
+ */
+public class MonotonicClock extends Clock {
+ private static final MonotonicClock CLOCK = new MonotonicClock();
+
+ private final long startNanos;
+ private final Instant startInstant;
+
+ /**
+ * Private constructor to enforce singleton pattern.
+ * Initializes the clock with the current system time and nanoTime.
+ */
+ private MonotonicClock() {
+ this.startNanos = System.nanoTime();
+ this.startInstant = Clock.systemUTC().instant();
+ }
+
+ /**
+ * Returns the singleton instance of MonotonicClock.
+ *
+ * @return the monotonic clock instance
+ */
+ public static MonotonicClock get() {
+ return CLOCK;
+ }
+
+ /**
+ * Returns the current instant from the monotonic clock.
+ * This is a convenience method equivalent to {@code get().instant()}.
+ *
+ * @return the current instant using monotonic timing
+ */
+ public static Instant now() {
+ return get().instant();
+ }
+
+ /**
+ * Returns a monotonically increasing instant.
+ *
+ * The returned instant is calculated by adding the elapsed nanoseconds
+ * since clock creation to the initial wall clock time. This ensures that
+ * the time never goes backwards and maintains a consistent relationship
+ * with the wall clock time.
+ *
+ * @return the current instant using monotonic timing
+ */
+ @Override
+ public Instant instant() {
+ long elapsedNanos = System.nanoTime() - startNanos;
+ return startInstant.plusNanos(elapsedNanos);
+ }
+
+ /**
+ * Returns the zone ID of this clock, which is always UTC.
+ *
+ * @return the UTC zone ID
+ */
+ @Override
+ public ZoneId getZone() {
+ return ZoneOffset.UTC;
+ }
+
+ /**
+ * Returns this clock since timezone adjustments are not supported.
+ *
+ * This implementation maintains UTC time to ensure monotonic behavior.
+ * The provided zone parameter is ignored.
+ *
+ * @param zone the target timezone (ignored)
+ * @return this clock instance
+ */
+ @Override
+ public Clock withZone(ZoneId zone) {
+ // Monotonic clock is always UTC-based
+ return this;
+ }
+}
diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/ProtoSession.java b/api/maven-api-core/src/main/java/org/apache/maven/api/ProtoSession.java
index 97ac59cb24..4b986da8d3 100644
--- a/api/maven-api-core/src/main/java/org/apache/maven/api/ProtoSession.java
+++ b/api/maven-api-core/src/main/java/org/apache/maven/api/ProtoSession.java
@@ -106,7 +106,7 @@ public interface ProtoSession {
* Returns new builder from scratch.
*/
static Builder newBuilder() {
- return new Builder().withStartTime(Instant.now());
+ return new Builder().withStartTime(MonotonicClock.now());
}
class Builder {
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/event/ExecutionEventLogger.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/event/ExecutionEventLogger.java
index 5c401ba7e0..791f81a15e 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/event/ExecutionEventLogger.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/event/ExecutionEventLogger.java
@@ -20,9 +20,13 @@ package org.apache.maven.cling.event;
import java.io.File;
import java.nio.file.Path;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
import java.util.List;
import java.util.Objects;
+import org.apache.maven.api.MonotonicClock;
import org.apache.maven.api.services.MessageBuilder;
import org.apache.maven.api.services.MessageBuilderFactory;
import org.apache.maven.execution.AbstractExecutionListener;
@@ -223,7 +227,7 @@ public class ExecutionEventLogger extends AbstractExecutionListener {
} else if (buildSummary instanceof BuildSuccess) {
buffer.append(builder().success("SUCCESS"));
buffer.append(" [");
- String buildTimeDuration = formatDuration(buildSummary.getTime());
+ String buildTimeDuration = formatDuration(buildSummary.getExecTime());
int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length();
if (padSize > 0) {
buffer.append(chars(' ', padSize));
@@ -233,7 +237,7 @@ public class ExecutionEventLogger extends AbstractExecutionListener {
} else if (buildSummary instanceof BuildFailure) {
buffer.append(builder().failure("FAILURE"));
buffer.append(" [");
- String buildTimeDuration = formatDuration(buildSummary.getTime());
+ String buildTimeDuration = formatDuration(buildSummary.getExecTime());
int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length();
if (padSize > 0) {
buffer.append(chars(' ', padSize));
@@ -266,15 +270,15 @@ public class ExecutionEventLogger extends AbstractExecutionListener {
private void logStats(MavenSession session) {
infoLine('-');
- long finish = System.currentTimeMillis();
+ Instant finish = MonotonicClock.now();
- long time = finish - session.getRequest().getStartTime().getTime();
+ Duration time = Duration.between(session.getRequest().getStartInstant(), finish);
String wallClock = session.getRequest().getDegreeOfConcurrency() > 1 ? " (Wall Clock)" : "";
logger.info("Total time: {}{}", formatDuration(time), wallClock);
- logger.info("Finished at: {}", formatTimestamp(finish));
+ logger.info("Finished at: {}", formatTimestamp(finish.atZone(ZoneId.systemDefault())));
}
@Override
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java
index 667fc55623..6a6c2a9d27 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java
@@ -22,7 +22,6 @@ import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@@ -33,6 +32,7 @@ import java.util.regex.Pattern;
import org.apache.maven.InternalErrorException;
import org.apache.maven.Maven;
import org.apache.maven.api.Constants;
+import org.apache.maven.api.MonotonicClock;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.Logger;
import org.apache.maven.api.cli.mvn.MavenOptions;
@@ -105,7 +105,7 @@ public abstract class MavenInvoker extends LookupInvoker
mavenExecutionRequest.setIgnoreMissingArtifactDescriptor(true);
mavenExecutionRequest.setRecursive(true);
mavenExecutionRequest.setReactorFailureBehavior(MavenExecutionRequest.REACTOR_FAIL_FAST);
- mavenExecutionRequest.setStartTime(new Date());
+ mavenExecutionRequest.setStartInstant(MonotonicClock.now());
mavenExecutionRequest.setLoggingLevel(MavenExecutionRequest.LOGGING_LEVEL_INFO);
mavenExecutionRequest.setDegreeOfConcurrency(1);
mavenExecutionRequest.setBuilderId("singlethreaded");
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java
index b07abab37e..4cba621f55 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java
@@ -19,7 +19,10 @@
package org.apache.maven.cling.transfer;
import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
+import org.apache.maven.api.MonotonicClock;
import org.apache.maven.api.services.MessageBuilder;
import org.apache.maven.api.services.MessageBuilderFactory;
import org.eclipse.aether.transfer.AbstractTransferListener;
@@ -80,11 +83,12 @@ public abstract class AbstractMavenTransferListener extends AbstractTransferList
message.resetStyle().append(resource.getResourceName());
message.style(STYLE).append(" (").append(format.format(contentLength));
- long duration = System.currentTimeMillis() - resource.getTransferStartTime();
- if (duration > 0L) {
- double bytesPerSecond = contentLength / (duration / 1000.0);
+ Duration duration =
+ Duration.between(Instant.ofEpochMilli(resource.getTransferStartTime()), MonotonicClock.now());
+ if ((duration.getSeconds() | duration.getNano()) > 0) { // duration.isPositive()
+ long bytesPerSecond = Math.round(contentLength / (double) duration.toSeconds());
message.append(" at ");
- format.format(message, (long) bytesPerSecond);
+ format.format(message, bytesPerSecond);
message.append("/s");
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/Slf4jMavenTransferListener.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/Slf4jMavenTransferListener.java
index b5ab1b6574..7db7403e91 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/Slf4jMavenTransferListener.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/Slf4jMavenTransferListener.java
@@ -18,6 +18,10 @@
*/
package org.apache.maven.cling.transfer;
+import java.time.Duration;
+import java.time.Instant;
+
+import org.apache.maven.api.MonotonicClock;
import org.eclipse.aether.transfer.AbstractTransferListener;
import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.transfer.TransferEvent;
@@ -83,11 +87,12 @@ public class Slf4jMavenTransferListener extends AbstractTransferListener {
.append(" (");
format.format(message, contentLength);
- long duration = System.currentTimeMillis() - resource.getTransferStartTime();
- if (duration > 0L) {
- double bytesPerSecond = contentLength / (duration / 1000.0);
+ Duration duration =
+ Duration.between(Instant.ofEpochMilli(resource.getTransferStartTime()), MonotonicClock.now());
+ if ((duration.getSeconds() | duration.getNano()) > 0) { // duration.isPositive()
+ long bytesPerSecond = Math.round(contentLength / (double) duration.toSeconds());
message.append(" at ");
- format.format(message, (long) bytesPerSecond);
+ format.format(message, bytesPerSecond);
message.append("/s");
}
diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/utils/CLIReportingUtils.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/utils/CLIReportingUtils.java
index d18f0a7c18..3af1415497 100644
--- a/impl/maven-cli/src/main/java/org/apache/maven/cling/utils/CLIReportingUtils.java
+++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/utils/CLIReportingUtils.java
@@ -20,8 +20,9 @@ package org.apache.maven.cling.utils;
import java.io.IOException;
import java.io.InputStream;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Duration;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.TemporalAccessor;
import java.util.Locale;
import java.util.Properties;
@@ -105,23 +106,13 @@ public final class CLIReportingUtils {
* @return Readable build info
*/
public static String createMavenVersionString(Properties buildProperties) {
- String timestamp = reduce(buildProperties.getProperty("timestamp"));
String version = reduce(buildProperties.getProperty(BUILD_VERSION_PROPERTY));
String rev = reduce(buildProperties.getProperty("buildNumber"));
String distributionName = reduce(buildProperties.getProperty("distributionName"));
- String msg = distributionName + " ";
- msg += (version != null ? version : "");
- if (rev != null || timestamp != null) {
- msg += " (";
- msg += (rev != null ? rev : "");
- if (timestamp != null && !timestamp.isEmpty()) {
- String ts = formatTimestamp(Long.parseLong(timestamp));
- msg += (rev != null ? "; " : "") + ts;
- }
- msg += ")";
- }
- return msg;
+ return distributionName + " "
+ + (version != null ? version : "")
+ + (rev != null ? " (" + rev + ")" : "");
}
private static String reduce(String s) {
@@ -169,35 +160,25 @@ public final class CLIReportingUtils {
}
}
- public static String formatTimestamp(long timestamp) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
- return sdf.format(new Date(timestamp));
+ public static String formatTimestamp(TemporalAccessor instant) {
+ return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(instant);
}
- public static String formatDuration(long duration) {
- // CHECKSTYLE_OFF: MagicNumber
- long ms = duration % 1000;
- long s = (duration / ONE_SECOND) % 60;
- long m = (duration / ONE_MINUTE) % 60;
- long h = (duration / ONE_HOUR) % 24;
- long d = duration / ONE_DAY;
- // CHECKSTYLE_ON: MagicNumber
+ public static String formatDuration(Duration duration) {
+ long days = duration.toDays();
+ long hours = duration.toHoursPart();
+ long minutes = duration.toMinutesPart();
+ long seconds = duration.toSecondsPart();
+ long millis = duration.toMillisPart();
- String format;
- if (d > 0) {
- // Length 11+ chars
- format = "%d d %02d:%02d h";
- } else if (h > 0) {
- // Length 7 chars
- format = "%2$02d:%3$02d h";
- } else if (m > 0) {
- // Length 9 chars
- format = "%3$02d:%4$02d min";
+ if (days > 0) {
+ return String.format("%d d %02d:%02d h", days, hours, minutes);
+ } else if (hours > 0) {
+ return String.format("%02d:%02d h", hours, minutes);
+ } else if (minutes > 0) {
+ return String.format("%02d:%02d min", minutes, seconds);
} else {
- // Length 7-8 chars
- format = "%4$d.%5$03d s";
+ return String.format("%d.%03d s", seconds, millis);
}
-
- return String.format(format, d, h, m, s, ms);
}
}
diff --git a/impl/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/impl/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
index 0c24c22c02..41d87dd85e 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
@@ -28,7 +28,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -39,6 +38,7 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
+import org.apache.maven.api.MonotonicClock;
import org.apache.maven.api.Session;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.Prerequisites;
@@ -194,7 +194,7 @@ public class DefaultMaven implements Maven {
//
@SuppressWarnings("checkstyle:methodlength")
private MavenExecutionResult doExecute(MavenExecutionRequest request) {
- request.setStartTime(new Date());
+ request.setStartInstant(MonotonicClock.now());
MavenExecutionResult result = new DefaultMavenExecutionResult();
diff --git a/impl/maven-core/src/main/java/org/apache/maven/execution/BuildFailure.java b/impl/maven-core/src/main/java/org/apache/maven/execution/BuildFailure.java
index 2c43f1e943..dcde525f58 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/execution/BuildFailure.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/execution/BuildFailure.java
@@ -18,6 +18,8 @@
*/
package org.apache.maven.execution;
+import java.time.Duration;
+
import org.apache.maven.project.MavenProject;
/**
@@ -39,6 +41,17 @@ public class BuildFailure extends BuildSummary {
* @param cause The cause of the build failure, may be {@code null}.
*/
public BuildFailure(MavenProject project, long time, Throwable cause) {
+ this(project, Duration.ofMillis(time), cause);
+ }
+
+ /**
+ * Creates a new build summary for the specified project.
+ *
+ * @param project The project being summarized, must not be {@code null}.
+ * @param time The build time of the project in milliseconds.
+ * @param cause The cause of the build failure, may be {@code null}.
+ */
+ public BuildFailure(MavenProject project, Duration time, Throwable cause) {
this(project, time, time, cause);
}
@@ -50,7 +63,7 @@ public class BuildFailure extends BuildSummary {
* @param wallTime The wall time of the project in milliseconds.
* @param cause The cause of the build failure, may be {@code null}.
*/
- public BuildFailure(MavenProject project, long execTime, long wallTime, Throwable cause) {
+ public BuildFailure(MavenProject project, Duration execTime, Duration wallTime, Throwable cause) {
super(project, execTime, wallTime);
this.cause = cause;
}
diff --git a/impl/maven-core/src/main/java/org/apache/maven/execution/BuildSuccess.java b/impl/maven-core/src/main/java/org/apache/maven/execution/BuildSuccess.java
index a2a4546b23..34dc8dbdd3 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/execution/BuildSuccess.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/execution/BuildSuccess.java
@@ -18,6 +18,8 @@
*/
package org.apache.maven.execution;
+import java.time.Duration;
+
import org.apache.maven.project.MavenProject;
/**
@@ -33,7 +35,17 @@ public class BuildSuccess extends BuildSummary {
* @param time The build time of the project in milliseconds.
*/
public BuildSuccess(MavenProject project, long time) {
- super(project, time, time);
+ this(project, Duration.ofMillis(time));
+ }
+
+ /**
+ * Creates a new build summary for the specified project.
+ *
+ * @param project The project being summarized, must not be {@code null}.
+ * @param time The build time of the project in milliseconds.
+ */
+ public BuildSuccess(MavenProject project, Duration time) {
+ this(project, time, time);
}
/**
@@ -43,7 +55,7 @@ public class BuildSuccess extends BuildSummary {
* @param wallTime The wall time of the project in milliseconds.
* @param execTime The exec time of the project in milliseconds.
*/
- public BuildSuccess(MavenProject project, long wallTime, long execTime) {
+ public BuildSuccess(MavenProject project, Duration wallTime, Duration execTime) {
super(project, wallTime, execTime);
}
}
diff --git a/impl/maven-core/src/main/java/org/apache/maven/execution/BuildSummary.java b/impl/maven-core/src/main/java/org/apache/maven/execution/BuildSummary.java
index efc0da1d8b..0f6c37bedc 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/execution/BuildSummary.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/execution/BuildSummary.java
@@ -18,6 +18,7 @@
*/
package org.apache.maven.execution;
+import java.time.Duration;
import java.util.Objects;
import org.apache.maven.project.MavenProject;
@@ -36,12 +37,12 @@ public abstract class BuildSummary {
/**
* The build time of the project in milliseconds.
*/
- private final long wallTime;
+ private final Duration wallTime;
/**
* The total amount of time spent for to run mojos in milliseconds.
*/
- private final long execTime;
+ private final Duration execTime;
/**
* Creates a new build summary for the specified project.
@@ -50,7 +51,7 @@ public abstract class BuildSummary {
* @param time The build time of the project in milliseconds.
*/
protected BuildSummary(MavenProject project, long time) {
- this(project, time, time);
+ this(project, Duration.ofMillis(time), Duration.ofMillis(time));
}
/**
@@ -60,7 +61,7 @@ public abstract class BuildSummary {
* @param execTime The exec time of the project in milliseconds.
* @param wallTime The wall time of the project in milliseconds.
*/
- protected BuildSummary(MavenProject project, long execTime, long wallTime) {
+ protected BuildSummary(MavenProject project, Duration execTime, Duration wallTime) {
this.project = Objects.requireNonNull(project, "project cannot be null");
// TODO Validate for < 0?
this.execTime = execTime;
@@ -82,15 +83,24 @@ public abstract class BuildSummary {
* @return The wall time of the project in milliseconds.
*/
public long getTime() {
- return execTime;
+ return execTime.toMillis();
}
/**
- * Gets the exec time of the project in milliseconds.
+ * Gets the wall time of the project.
*
- * @return The exec time of the project in milliseconds.
+ * @return The wall time of the project.
*/
- public long getWallTime() {
+ public Duration getWallTime() {
return wallTime;
}
+
+ /**
+ * Gets the exec time of the project.
+ *
+ * @return The exec time of the project.
+ */
+ public Duration getExecTime() {
+ return execTime;
+ }
}
diff --git a/impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java b/impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
index aa9748ca51..eedfe23016 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/execution/DefaultMavenExecutionRequest.java
@@ -20,6 +20,7 @@ package org.apache.maven.execution;
import java.io.File;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@@ -28,6 +29,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Properties;
+import org.apache.maven.api.MonotonicClock;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.model.Profile;
@@ -135,7 +137,7 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
private Properties userProperties;
- private Date startTime = new Date();
+ private Instant startTime = MonotonicClock.now();
private boolean showErrors = false;
@@ -214,7 +216,7 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
copy.setExecutionListener(original.getExecutionListener());
copy.setUseLegacyLocalRepository(original.isUseLegacyLocalRepository());
copy.setBuilderId(original.getBuilderId());
- copy.setStartTime(original.getStartTime());
+ copy.setStartInstant(original.getStartInstant());
return copy;
}
@@ -299,10 +301,22 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
}
@Override
+ @Deprecated
public Date getStartTime() {
+ return new Date(startTime.toEpochMilli());
+ }
+
+ @Override
+ public Instant getStartInstant() {
return startTime;
}
+ @Override
+ public MavenExecutionRequest setStartInstant(Instant startTime) {
+ this.startTime = startTime;
+ return this;
+ }
+
@Override
public boolean isShowErrors() {
return showErrors;
@@ -423,9 +437,10 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
return this;
}
+ @Deprecated
@Override
public MavenExecutionRequest setStartTime(Date startTime) {
- this.startTime = startTime;
+ this.startTime = Instant.ofEpochMilli(startTime.getTime());
return this;
}
@@ -981,7 +996,7 @@ public class DefaultMavenExecutionRequest implements MavenExecutionRequest {
projectBuildingRequest.setInactiveProfileIds(getInactiveProfiles());
projectBuildingRequest.setProfiles(getProfiles());
projectBuildingRequest.setProcessPlugins(true);
- projectBuildingRequest.setBuildStartTime(getStartTime());
+ projectBuildingRequest.setBuildStartInstant(getStartInstant());
}
return projectBuildingRequest;
diff --git a/impl/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java b/impl/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
index f95f48b26c..baf0080177 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/execution/MavenExecutionRequest.java
@@ -20,6 +20,7 @@ package org.apache.maven.execution;
import java.io.File;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -105,10 +106,16 @@ public interface MavenExecutionRequest {
String getBaseDirectory();
// Timing (remove this)
+ @Deprecated
MavenExecutionRequest setStartTime(Date start);
+ @Deprecated
Date getStartTime();
+ MavenExecutionRequest setStartInstant(Instant start);
+
+ Instant getStartInstant();
+
// Goals
MavenExecutionRequest setGoals(List goals);
diff --git a/impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java b/impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
index 5d118ff7dd..8fa6e4098a 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
@@ -20,6 +20,7 @@ package org.apache.maven.execution;
import java.io.File;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@@ -245,10 +246,15 @@ public class MavenSession implements Cloneable {
}
}
+ @Deprecated
public Date getStartTime() {
return request.getStartTime();
}
+ public Instant getStartInstant() {
+ return request.getStartInstant();
+ }
+
public boolean isParallel() {
return parallel;
}
diff --git a/impl/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java b/impl/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java
index 2ac0643135..bc02255c4f 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/execution/ReactorManager.java
@@ -18,6 +18,7 @@
*/
package org.apache.maven.execution;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -123,6 +124,10 @@ public class ReactorManager {
buildFailuresByProject.put(getProjectKey(project), new BuildFailure(project, time, error));
}
+ public void registerBuildFailure(MavenProject project, Exception error, String task, Duration time) {
+ buildFailuresByProject.put(getProjectKey(project), new BuildFailure(project, time, error));
+ }
+
public boolean hasBuildFailures() {
return !buildFailuresByProject.isEmpty();
}
@@ -147,6 +152,10 @@ public class ReactorManager {
buildSuccessesByProject.put(getProjectKey(project), new BuildSuccess(project, time));
}
+ public void registerBuildSuccess(MavenProject project, Duration time) {
+ buildSuccessesByProject.put(getProjectKey(project), new BuildSuccess(project, time));
+ }
+
public BuildFailure getBuildFailure(MavenProject project) {
return buildFailuresByProject.get(getProjectKey(project));
}
diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
index 235b68d222..371c345f3d 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
@@ -168,6 +168,7 @@ public class DefaultRepositorySystemSessionFactory implements RepositorySystemSe
configProps.put(ConfigurationProperties.USER_AGENT, getUserAgent());
configProps.put(ConfigurationProperties.INTERACTIVE, request.isInteractiveMode());
configProps.put("maven.startTime", request.getStartTime());
+ configProps.put(Constants.MAVEN_START_INSTANT, request.getStartInstant());
sessionBuilder.setOffline(request.isOffline());
sessionBuilder.setChecksumPolicy(request.getGlobalChecksumPolicy());
diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultSession.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultSession.java
index a931b9874b..27c30927f0 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultSession.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultSession.java
@@ -146,7 +146,7 @@ public class DefaultSession extends AbstractSession implements InternalMavenSess
@Nonnull
@Override
public Instant getStartTime() {
- return getMavenSession().getRequest().getStartTime().toInstant();
+ return getMavenSession().getRequest().getStartInstant();
}
@Override
diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java
index 5450e16ea5..0994baf006 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/LifecycleModuleBuilder.java
@@ -22,9 +22,12 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
+import java.time.Duration;
+import java.time.Instant;
import java.util.HashSet;
import java.util.List;
+import org.apache.maven.api.MonotonicClock;
import org.apache.maven.execution.BuildSuccess;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.MavenSession;
@@ -81,7 +84,7 @@ public class LifecycleModuleBuilder {
TaskSegment taskSegment) {
session.setCurrentProject(currentProject);
- long buildStartTime = System.currentTimeMillis();
+ Instant buildStartTime = MonotonicClock.now();
try {
@@ -106,12 +109,14 @@ public class LifecycleModuleBuilder {
new ProjectExecutionEvent(session, currentProject, mojoExecutions));
mojoExecutor.execute(session, mojoExecutions);
- long buildEndTime = System.currentTimeMillis();
+ Instant buildEndTime = MonotonicClock.now();
projectExecutionListener.afterProjectExecutionSuccess(
new ProjectExecutionEvent(session, currentProject, mojoExecutions));
- reactorContext.getResult().addBuildSummary(new BuildSuccess(currentProject, buildEndTime - buildStartTime));
+ reactorContext
+ .getResult()
+ .addBuildSummary(new BuildSuccess(currentProject, Duration.between(buildStartTime, buildEndTime)));
eventCatapult.fire(ExecutionEvent.Type.ProjectSucceeded, session, null);
} catch (Throwable t) {
diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java
index e1c2b5b195..b683a06cdb 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/BuilderCommon.java
@@ -22,9 +22,12 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
+import java.time.Duration;
+import java.time.Instant;
import java.util.List;
import java.util.Set;
+import org.apache.maven.api.MonotonicClock;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.BuildFailure;
import org.apache.maven.execution.ExecutionEvent;
@@ -164,11 +167,13 @@ public class BuilderCommon {
final MavenSession currentSession,
final MavenProject mavenProject,
Throwable t,
- final long buildStartTime) {
+ final Instant buildStartTime) {
// record the error and mark the project as failed
- long buildEndTime = System.currentTimeMillis();
+ Instant buildEndTime = MonotonicClock.now();
buildContext.getResult().addException(t);
- buildContext.getResult().addBuildSummary(new BuildFailure(mavenProject, buildEndTime - buildStartTime, t));
+ buildContext
+ .getResult()
+ .addBuildSummary(new BuildFailure(mavenProject, Duration.between(buildStartTime, buildEndTime), t));
// notify listeners about "soft" project build failures only
if (t instanceof Exception && !(t instanceof RuntimeException)) {
diff --git a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
index 3134ac94e7..ccc16724a1 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java
@@ -23,6 +23,8 @@ import javax.inject.Named;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -34,13 +36,13 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.maven.api.Lifecycle;
+import org.apache.maven.api.MonotonicClock;
import org.apache.maven.api.services.LifecycleRegistry;
import org.apache.maven.api.services.MavenException;
import org.apache.maven.api.xml.XmlNode;
@@ -905,31 +907,31 @@ public class BuildPlanExecutor {
}
protected static class Clock {
- long start;
- long end;
- long resumed;
- long exec;
+ Instant start;
+ Instant end;
+ Instant resumed;
+ Duration exec = Duration.ZERO;
protected void start() {
- if (start == 0) {
- start = System.nanoTime();
+ if (start == null) {
+ start = MonotonicClock.now();
resumed = start;
} else {
- resumed = System.nanoTime();
+ resumed = MonotonicClock.now();
}
}
protected void stop() {
- end = System.nanoTime();
- exec += end - resumed;
+ end = MonotonicClock.now();
+ exec = exec.plus(Duration.between(resumed, end));
}
- protected long wallTime() {
- return TimeUnit.NANOSECONDS.toMillis(end - start);
+ protected Duration wallTime() {
+ return Duration.between(start, end);
}
- protected long execTime() {
- return TimeUnit.NANOSECONDS.toMillis(exec);
+ protected Duration execTime() {
+ return exec;
}
}
}
diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingRequest.java b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingRequest.java
index 8f18435478..dbf6611b0f 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingRequest.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuildingRequest.java
@@ -18,6 +18,7 @@
*/
package org.apache.maven.project;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -59,7 +60,7 @@ public class DefaultProjectBuildingRequest implements ProjectBuildingRequest {
private Properties userProperties;
- private Date buildStartTime;
+ private Instant buildStartTime;
private boolean resolveDependencies;
@@ -261,11 +262,21 @@ public class DefaultProjectBuildingRequest implements ProjectBuildingRequest {
return profiles;
}
+ @Deprecated
public Date getBuildStartTime() {
- return buildStartTime;
+ return buildStartTime != null ? new Date(buildStartTime.toEpochMilli()) : null;
}
+ @Deprecated
public void setBuildStartTime(Date buildStartTime) {
+ setBuildStartInstant(buildStartTime != null ? Instant.ofEpochMilli(buildStartTime.getTime()) : null);
+ }
+
+ public Instant getBuildStartInstant() {
+ return this.buildStartTime;
+ }
+
+ public void setBuildStartInstant(Instant buildStartTime) {
this.buildStartTime = buildStartTime;
}
diff --git a/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingRequest.java b/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingRequest.java
index 7d3cae86af..c2f8fb3bf3 100644
--- a/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingRequest.java
+++ b/impl/maven-core/src/main/java/org/apache/maven/project/ProjectBuildingRequest.java
@@ -18,6 +18,7 @@
*/
package org.apache.maven.project;
+import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Properties;
@@ -138,6 +139,7 @@ public interface ProjectBuildingRequest {
*
* @return The start time of the build or {@code null} if unknown.
*/
+ @Deprecated
Date getBuildStartTime();
/**
@@ -145,8 +147,23 @@ public interface ProjectBuildingRequest {
*
* @param buildStartTime The start time of the build, may be {@code null}.
*/
+ @Deprecated
void setBuildStartTime(Date buildStartTime);
+ /**
+ * Gets the start time of the build.
+ *
+ * @return The start time of the build or {@code null} if unknown.
+ */
+ Instant getBuildStartInstant();
+
+ /**
+ * Sets the start time of the build.
+ *
+ * @param buildStartInstant The start time of the build, may be {@code null}.
+ */
+ void setBuildStartInstant(Instant buildStartInstant);
+
RepositorySystemSession getRepositorySession();
ProjectBuildingRequest setRepositorySession(RepositorySystemSession repositorySession);
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/MavenBuildTimestamp.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/MavenBuildTimestamp.java
index 3ee89cedd8..412a8b53a9 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/MavenBuildTimestamp.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/model/MavenBuildTimestamp.java
@@ -18,15 +18,14 @@
*/
package org.apache.maven.internal.impl.model;
-import java.text.SimpleDateFormat;
import java.time.Instant;
-import java.util.Date;
-import java.util.GregorianCalendar;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Properties;
-import java.util.TimeZone;
import org.apache.maven.api.Constants;
+import org.apache.maven.api.MonotonicClock;
/**
* MavenBuildTimestamp
@@ -35,12 +34,10 @@ public class MavenBuildTimestamp {
// ISO 8601-compliant timestamp for machine readability
public static final String DEFAULT_BUILD_TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX";
- public static final TimeZone DEFAULT_BUILD_TIME_ZONE = TimeZone.getTimeZone("Etc/UTC");
-
private final String formattedTimestamp;
public MavenBuildTimestamp() {
- this(Instant.now());
+ this(MonotonicClock.now());
}
public MavenBuildTimestamp(Instant time) {
@@ -66,12 +63,11 @@ public class MavenBuildTimestamp {
timestampFormat = DEFAULT_BUILD_TIMESTAMP_FORMAT;
}
if (time == null) {
- time = Instant.now();
+ time = MonotonicClock.now();
}
- SimpleDateFormat dateFormat = new SimpleDateFormat(timestampFormat);
- dateFormat.setCalendar(new GregorianCalendar());
- dateFormat.setTimeZone(DEFAULT_BUILD_TIME_ZONE);
- formattedTimestamp = dateFormat.format(new Date(time.toEpochMilli()));
+ DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern(timestampFormat).withZone(ZoneId.of("UTC"));
+ formattedTimestamp = formatter.format(time);
}
public String formattedTimestamp() {
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadata.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadata.java
index 511a2246b4..51e71f9f65 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadata.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadata.java
@@ -20,9 +20,9 @@ package org.apache.maven.internal.impl.resolver;
import java.io.File;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -39,11 +39,11 @@ final class LocalSnapshotMetadata extends MavenMetadata {
private final Collection artifacts = new ArrayList<>();
- LocalSnapshotMetadata(Artifact artifact, Date timestamp) {
+ LocalSnapshotMetadata(Artifact artifact, Instant timestamp) {
super(createMetadata(artifact), (Path) null, timestamp);
}
- LocalSnapshotMetadata(Metadata metadata, Path path, Date timestamp) {
+ LocalSnapshotMetadata(Metadata metadata, Path path, Instant timestamp) {
super(metadata, path, timestamp);
}
diff --git a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadataGenerator.java b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadataGenerator.java
index e122812e2e..9d8eca5133 100644
--- a/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadataGenerator.java
+++ b/impl/maven-impl/src/main/java/org/apache/maven/internal/impl/resolver/LocalSnapshotMetadataGenerator.java
@@ -18,12 +18,14 @@
*/
package org.apache.maven.internal.impl.resolver;
+import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
-import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
+import org.apache.maven.api.Constants;
+import org.apache.maven.api.MonotonicClock;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.impl.MetadataGenerator;
@@ -40,10 +42,10 @@ class LocalSnapshotMetadataGenerator implements MetadataGenerator {
private final Map