View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.api;
20  
21  import java.time.Clock;
22  import java.time.Duration;
23  import java.time.Instant;
24  import java.time.ZoneId;
25  import java.time.ZoneOffset;
26  
27  /**
28   * A Clock implementation that combines monotonic timing with wall-clock time.
29   * <p>
30   * This class provides precise time measurements using {@link System#nanoTime()}
31   * while maintaining wall-clock time information in UTC. The wall-clock time
32   * is computed from the monotonic duration since system start to ensure consistency
33   * between time measurements.
34   * <p>
35   * This implementation is singleton-based and always uses UTC timezone. The clock
36   * cannot be adjusted to different timezones to maintain consistent monotonic behavior.
37   * Users needing local time representation should convert the result of {@link #instant()}
38   * to their desired timezone:
39   * <pre>{@code
40   * Instant now = MonotonicClock.now();
41   * ZonedDateTime local = now.atZone(ZoneId.systemDefault());
42   * }</pre>
43   *
44   * @see System#nanoTime()
45   * @see Clock
46   */
47  public class MonotonicClock extends Clock {
48      private static final MonotonicClock CLOCK = new MonotonicClock();
49  
50      private final long startNanos;
51      private final Instant startInstant;
52  
53      /**
54       * Private constructor to enforce singleton pattern.
55       * Initializes the clock with the current system time and nanoTime.
56       */
57      private MonotonicClock() {
58          this.startNanos = System.nanoTime();
59          this.startInstant = Clock.systemUTC().instant();
60      }
61  
62      /**
63       * Returns the singleton instance of MonotonicClock.
64       *
65       * @return the monotonic clock instance
66       */
67      public static MonotonicClock get() {
68          return CLOCK;
69      }
70  
71      /**
72       * Returns the current instant from the monotonic clock.
73       * This is a convenience method equivalent to {@code get().instant()}.
74       *
75       * @return the current instant using monotonic timing
76       */
77      public static Instant now() {
78          return get().instant();
79      }
80  
81      /**
82       * Returns the initialization time of this monotonic clock.
83       * This is a convenience method equivalent to {@code get().start()}.
84       *
85       * @return the instant when this monotonic clock was initialized
86       * @see #startInstant()
87       */
88      public static Instant start() {
89          return get().startInstant();
90      }
91  
92      /**
93       * Returns the elapsed time since clock initialization.
94       * This is a convenience method equivalent to {@code get().elapsedTime()}.
95       *
96       * @return the duration since clock initialization
97       */
98      public static Duration elapsed() {
99          return get().elapsedTime();
100     }
101 
102     /**
103      * Returns a monotonically increasing instant.
104      * <p>
105      * The returned instant is calculated by adding the elapsed nanoseconds
106      * since clock creation to the initial wall clock time. This ensures that
107      * the time never goes backwards and maintains a consistent relationship
108      * with the wall clock time.
109      *
110      * @return the current instant using monotonic timing
111      */
112     @Override
113     public Instant instant() {
114         long elapsedNanos = System.nanoTime() - startNanos;
115         return startInstant.plusNanos(elapsedNanos);
116     }
117 
118     /**
119      * Returns the wall clock time captured when this monotonic clock was initialized.
120      * <p>
121      * This instant serves as the base time from which all subsequent {@link #instant()}
122      * calls are calculated by adding the elapsed monotonic duration. This ensures
123      * consistency between the monotonic measurements and wall clock time.
124      *
125      * @return the initial wall clock instant when this clock was created
126      * @see #instant()
127      */
128     public Instant startInstant() {
129         return startInstant;
130     }
131 
132     /**
133      * Returns the duration elapsed since this clock was initialized.
134      * <p>
135      * The returned duration is calculated using {@link System#nanoTime()}
136      * to ensure monotonic behavior. This duration represents the exact time
137      * span between clock initialization and the current instant.
138      *
139      * @return the duration since clock initialization
140      * @see #startInstant()
141      * @see #instant()
142      */
143     public Duration elapsedTime() {
144         long elapsedNanos = System.nanoTime() - startNanos;
145         return Duration.ofNanos(elapsedNanos);
146     }
147 
148     /**
149      * Returns the zone ID of this clock, which is always UTC.
150      *
151      * @return the UTC zone ID
152      */
153     @Override
154     public ZoneId getZone() {
155         return ZoneOffset.UTC;
156     }
157 
158     /**
159      * Returns this clock since timezone adjustments are not supported.
160      * <p>
161      * This implementation maintains UTC time to ensure monotonic behavior.
162      * The provided zone parameter is ignored.
163      *
164      * @param zone the target timezone (ignored)
165      * @return this clock instance
166      */
167     @Override
168     public Clock withZone(ZoneId zone) {
169         // Monotonic clock is always UTC-based
170         return this;
171     }
172 }