diff --git a/src/java.base/share/classes/java/time/Clock.java b/src/java.base/share/classes/java/time/Clock.java index ec8577de164..4eddfb54a4e 100644 --- a/src/java.base/share/classes/java/time/Clock.java +++ b/src/java.base/share/classes/java/time/Clock.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -63,6 +63,8 @@ import java.io.IOException; import java.io.ObjectInputStream; +import java.io.ObjectStreamException; + import static java.time.LocalTime.NANOS_PER_MINUTE; import static java.time.LocalTime.NANOS_PER_SECOND; import static java.time.LocalTime.NANOS_PER_MILLI; @@ -86,8 +88,8 @@ * current time rather than a static method. This can simplify testing. *

* Best practice for applications is to pass a {@code Clock} into any method - * that requires the current instant. A dependency injection framework is one - * way to achieve this: + * that requires the current instant and time-zone. A dependency injection framework + * is one way to achieve this: *

  *  public class MyBean {
  *    private Clock clock;  // dependency inject
@@ -99,45 +101,18 @@
  *    }
  *  }
  * 
- * This approach allows an alternate clock, such as {@link #fixed(Instant, ZoneId) fixed} + * This approach allows an alternative clock, such as {@link #fixed(Instant, ZoneId) fixed} * or {@link #offset(Clock, Duration) offset} to be used during testing. *

* The {@code system} factory methods provide clocks based on the best available - * system clock This may use {@link System#currentTimeMillis()}, or a higher + * system clock. This may use {@link System#currentTimeMillis()}, or a higher * resolution clock if one is available. * - * @implSpec - * This abstract class must be implemented with care to ensure other classes operate correctly. - * All implementations that can be instantiated must be final, immutable and thread-safe. - *

- * The principal methods are defined to allow the throwing of an exception. - * In normal use, no exceptions will be thrown, however one possible implementation would be to - * obtain the time from a central time server across the network. Obviously, in this case the - * lookup could fail, and so the method is permitted to throw an exception. - *

- * The returned instants from {@code Clock} work on a time-scale that ignores leap seconds, - * as described in {@link Instant}. If the implementation wraps a source that provides leap - * second information, then a mechanism should be used to "smooth" the leap second. - * The Java Time-Scale mandates the use of UTC-SLS, however clock implementations may choose - * how accurate they are with the time-scale so long as they document how they work. - * Implementations are therefore not required to actually perform the UTC-SLS slew or to - * otherwise be aware of leap seconds. - *

- * Implementations should implement {@code Serializable} wherever possible and must - * document whether or not they do support serialization. - * - * @implNote - * The clock implementation provided here is based on the same underlying clock - * as {@link System#currentTimeMillis()}, but may have a precision finer than - * milliseconds if available. - * However, little to no guarantee is provided about the accuracy of the - * underlying clock. Applications requiring a more accurate clock must implement - * this abstract class themselves using a different external clock, such as an - * NTP server. + * @see InstantSource * * @since 1.8 */ -public abstract class Clock { +public abstract class Clock implements InstantSource { /** * Obtains a clock that returns the current instant using the best available @@ -354,7 +329,7 @@ public static Clock fixed(Instant fixedInstant, ZoneId zone) { //------------------------------------------------------------------------- /** * Obtains a clock that returns instants from the specified clock with the - * specified duration added + * specified duration added. *

* This clock wraps another clock, returning instants that are later by the * specified duration. If the duration is negative, the instants will be @@ -408,6 +383,7 @@ protected Clock() { * @param zone the time-zone to change to, not null * @return a clock based on this clock with the specified time-zone, not null */ + @Override public abstract Clock withZone(ZoneId zone); //------------------------------------------------------------------------- @@ -428,6 +404,7 @@ protected Clock() { * the Java epoch of 1970-01-01T00:00Z (UTC), not null * @throws DateTimeException if the instant cannot be obtained, not thrown by most implementations */ + @Override public long millis() { return instant().toEpochMilli(); } @@ -441,6 +418,7 @@ public long millis() { * @return the current instant from this clock, not null * @throws DateTimeException if the instant cannot be obtained, not thrown by most implementations */ + @Override public abstract Instant instant(); //----------------------------------------------------------------------- @@ -473,32 +451,120 @@ public int hashCode() { return super.hashCode(); } + //----------------------------------------------------------------------- + // initial offset + private static final long OFFSET_SEED = System.currentTimeMillis() / 1000 - 1024; + // We don't actually need a volatile here. + // We don't care if offset is set or read concurrently by multiple + // threads - we just need a value which is 'recent enough' - in other + // words something that has been updated at least once in the last + // 2^32 secs (~136 years). And even if we by chance see an invalid + // offset, the worst that can happen is that we will get a -1 value + // from getNanoTimeAdjustment, forcing us to update the offset + // once again. + private static long offset = OFFSET_SEED; + + static Instant currentInstant() { + // Take a local copy of offset. offset can be updated concurrently + // by other threads (even if we haven't made it volatile) so we will + // work with a local copy. + long localOffset = offset; + long adjustment = VM.getNanoTimeAdjustment(localOffset); + + if (adjustment == -1) { + // -1 is a sentinel value returned by VM.getNanoTimeAdjustment + // when the offset it is given is too far off the current UTC + // time. In principle, this should not happen unless the + // JVM has run for more than ~136 years (not likely) or + // someone is fiddling with the system time, or the offset is + // by chance at 1ns in the future (very unlikely). + // We can easily recover from all these conditions by bringing + // back the offset in range and retry. + + // bring back the offset in range. We use -1024 to make + // it more unlikely to hit the 1ns in the future condition. + localOffset = System.currentTimeMillis()/1000 - 1024; + + // retry + adjustment = VM.getNanoTimeAdjustment(localOffset); + + if (adjustment == -1) { + // Should not happen: we just recomputed a new offset. + // It should have fixed the issue. + throw new InternalError("Offset " + localOffset + " is not in range"); + } else { + // OK - recovery succeeded. Update the offset for the + // next call... + offset = localOffset; + } + } + return Instant.ofEpochSecond(localOffset, adjustment); + } + + //----------------------------------------------------------------------- + /** + * An instant source that always returns the latest time from + * {@link System#currentTimeMillis()} or equivalent. + */ + static final class SystemInstantSource implements InstantSource, Serializable { + @java.io.Serial + private static final long serialVersionUID = 3232399674412L; + // this is a singleton, but the class is coded such that it is not a + // problem if someone hacks around and creates another instance + static final SystemInstantSource INSTANCE = new SystemInstantSource(); + + SystemInstantSource() { + } + @Override + public Clock withZone(ZoneId zone) { + return Clock.system(zone); + } + @Override + public long millis() { + // System.currentTimeMillis() and VM.getNanoTimeAdjustment(offset) + // use the same time source - System.currentTimeMillis() simply + // limits the resolution to milliseconds. + // So we take the faster path and call System.currentTimeMillis() + // directly - in order to avoid the performance penalty of + // VM.getNanoTimeAdjustment(offset) which is less efficient. + return System.currentTimeMillis(); + } + @Override + public Instant instant() { + return currentInstant(); + } + @Override + public boolean equals(Object obj) { + return obj instanceof SystemInstantSource; + } + @Override + public int hashCode() { + return SystemInstantSource.class.hashCode(); + } + @Override + public String toString() { + return "SystemInstantSource"; + } + @java.io.Serial + private Object readResolve() throws ObjectStreamException { + return SystemInstantSource.INSTANCE; + } + } + //----------------------------------------------------------------------- /** * Implementation of a clock that always returns the latest time from - * {@link System#currentTimeMillis()}. + * {@code SystemInstantSource.INSTANCE}. */ static final class SystemClock extends Clock implements Serializable { @java.io.Serial private static final long serialVersionUID = 6740630888130243051L; - private static final long OFFSET_SEED = - System.currentTimeMillis()/1000 - 1024; // initial offest static final SystemClock UTC = new SystemClock(ZoneOffset.UTC); private final ZoneId zone; - // We don't actually need a volatile here. - // We don't care if offset is set or read concurrently by multiple - // threads - we just need a value which is 'recent enough' - in other - // words something that has been updated at least once in the last - // 2^32 secs (~136 years). And even if we by chance see an invalid - // offset, the worst that can happen is that we will get a -1 value - // from getNanoTimeAdjustment, forcing us to update the offset - // once again. - private transient long offset; SystemClock(ZoneId zone) { this.zone = zone; - this.offset = OFFSET_SEED; } @Override public ZoneId getZone() { @@ -513,50 +579,13 @@ public Clock withZone(ZoneId zone) { } @Override public long millis() { - // System.currentTimeMillis() and VM.getNanoTimeAdjustment(offset) - // use the same time source - System.currentTimeMillis() simply - // limits the resolution to milliseconds. - // So we take the faster path and call System.currentTimeMillis() - // directly - in order to avoid the performance penalty of - // VM.getNanoTimeAdjustment(offset) which is less efficient. + // inline of SystemInstantSource.INSTANCE.millis() return System.currentTimeMillis(); } @Override public Instant instant() { - // Take a local copy of offset. offset can be updated concurrently - // by other threads (even if we haven't made it volatile) so we will - // work with a local copy. - long localOffset = offset; - long adjustment = VM.getNanoTimeAdjustment(localOffset); - - if (adjustment == -1) { - // -1 is a sentinel value returned by VM.getNanoTimeAdjustment - // when the offset it is given is too far off the current UTC - // time. In principle, this should not happen unless the - // JVM has run for more than ~136 years (not likely) or - // someone is fiddling with the system time, or the offset is - // by chance at 1ns in the future (very unlikely). - // We can easily recover from all these conditions by bringing - // back the offset in range and retry. - - // bring back the offset in range. We use -1024 to make - // it more unlikely to hit the 1ns in the future condition. - localOffset = System.currentTimeMillis()/1000 - 1024; - - // retry - adjustment = VM.getNanoTimeAdjustment(localOffset); - - if (adjustment == -1) { - // Should not happen: we just recomputed a new offset. - // It should have fixed the issue. - throw new InternalError("Offset " + localOffset + " is not in range"); - } else { - // OK - recovery succeeded. Update the offset for the - // next call... - offset = localOffset; - } - } - return Instant.ofEpochSecond(localOffset, adjustment); + // inline of SystemInstantSource.INSTANCE.instant() + return currentInstant(); } @Override public boolean equals(Object obj) { @@ -573,13 +602,6 @@ public int hashCode() { public String toString() { return "SystemClock[" + zone + "]"; } - @java.io.Serial - private void readObject(ObjectInputStream is) - throws IOException, ClassNotFoundException { - // ensure that offset is initialized - is.defaultReadObject(); - offset = OFFSET_SEED; - } } //----------------------------------------------------------------------- @@ -740,4 +762,54 @@ public String toString() { } } + //----------------------------------------------------------------------- + /** + * Implementation of a clock based on an {@code InstantSource}. + */ + static final class SourceClock extends Clock implements Serializable { + @java.io.Serial + private static final long serialVersionUID = 235386528762398L; + @SuppressWarnings("serial") // Not statically typed as Serializable + private final InstantSource baseSource; + private final ZoneId zone; + + SourceClock(InstantSource baseSource, ZoneId zone) { + this.baseSource = baseSource; + this.zone = zone; + } + @Override + public ZoneId getZone() { + return zone; + } + @Override + public Clock withZone(ZoneId zone) { + if (zone.equals(this.zone)) { // intentional NPE + return this; + } + return new SourceClock(baseSource, zone); + } + @Override + public long millis() { + return baseSource.millis(); + } + @Override + public Instant instant() { + return baseSource.instant(); + } + @Override + public boolean equals(Object obj) { + return (obj instanceof SourceClock other) + && zone.equals(other.zone) + && baseSource.equals(other.baseSource); + } + @Override + public int hashCode() { + return baseSource.hashCode() ^ zone.hashCode(); + } + @Override + public String toString() { + return "SourceClock[" + baseSource + "," + zone + "]"; + } + } + } diff --git a/src/java.base/share/classes/java/time/Instant.java b/src/java.base/share/classes/java/time/Instant.java index 9b059205b0d..af864aa4cf7 100644 --- a/src/java.base/share/classes/java/time/Instant.java +++ b/src/java.base/share/classes/java/time/Instant.java @@ -272,7 +272,7 @@ * @return the current instant using the system clock, not null */ public static Instant now() { - return Clock.systemUTC().instant(); + return Clock.currentInstant(); } /** diff --git a/src/java.base/share/classes/java/time/InstantSource.java b/src/java.base/share/classes/java/time/InstantSource.java new file mode 100644 index 00000000000..1f1d062e5ec --- /dev/null +++ b/src/java.base/share/classes/java/time/InstantSource.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.time; + +import java.time.Clock.SourceClock; +import java.time.Clock.SystemInstantSource; +import java.util.Objects; + +/** + * Provides access to the current instant. + *

+ * Instances of this interface are used to find the current instant. + * As such, an {@code InstantSource} can be used instead of {@link System#currentTimeMillis()}. + *

+ * The primary purpose of this abstraction is to allow alternate instant sources to be + * plugged in as and when required. Applications use an object to obtain the + * current time rather than a static method. This can simplify testing. + *

+ * Best practice for applications is to pass an {@code InstantSource} into any method + * that requires the current instant. A dependency injection framework is one + * way to achieve this: + *

+ *  public class MyBean {
+ *    private InstantSource source;  // dependency inject
+ *    ...
+ *    public void process(Instant endInstant) {
+ *      if (source.instant().isAfter(endInstant) {
+ *        ...
+ *      }
+ *    }
+ *  }
+ * 
+ * This approach allows an alternative source, such as {@link #fixed(Instant) fixed} + * or {@link #offset(InstantSource, Duration) offset} to be used during testing. + *

+ * The {@code system} factory method provides a source based on the best available + * system clock. This may use {@link System#currentTimeMillis()}, or a higher + * resolution clock if one is available. + * + * @implSpec + * This interface must be implemented with care to ensure other classes operate correctly. + * All implementations must be thread-safe. + *

+ * The principal methods are defined to allow the throwing of an exception. + * In normal use, no exceptions will be thrown, however one possible implementation would be to + * obtain the time from a central time server across the network. Obviously, in this case the + * lookup could fail, and so the method is permitted to throw an exception. + *

+ * The returned instants from {@code InstantSource} work on a time-scale that ignores leap seconds, + * as described in {@link Instant}. If the implementation wraps a source that provides leap + * second information, then a mechanism should be used to "smooth" the leap second. + * The Java Time-Scale mandates the use of UTC-SLS, however implementations may choose + * how accurate they are with the time-scale so long as they document how they work. + * Implementations are therefore not required to actually perform the UTC-SLS slew or to + * otherwise be aware of leap seconds. + *

+ * Implementations should implement {@code Serializable} wherever possible and must + * document whether or not they do support serialization. + * + * @implNote + * The implementation provided here is based on the same underlying system clock + * as {@link System#currentTimeMillis()}, but may have a precision finer than + * milliseconds if available. + * However, little to no guarantee is provided about the accuracy of the + * underlying system clock. Applications requiring a more accurate system clock must + * implement this abstract class themselves using a different external system clock, + * such as an NTP server. + * + * @since 17 + */ +public interface InstantSource { + + /** + * Obtains a source that returns the current instant using the best available + * system clock. + *

+ * This source is based on the best available system clock. This may use + * {@link System#currentTimeMillis()}, or a higher resolution system clock if + * one is available. + *

+ * The returned implementation is immutable, thread-safe and + * {@code Serializable}. + * + * @return a source that uses the best available system clock, not null + */ + static InstantSource system() { + return SystemInstantSource.INSTANCE; + } + + //------------------------------------------------------------------------- + /** + * Obtains a source that returns instants from the specified source truncated to + * the nearest occurrence of the specified duration. + *

+ * This source will only tick as per the specified duration. Thus, if the + * duration is half a second, the source will return instants truncated to the + * half second. + *

+ * The tick duration must be positive. If it has a part smaller than a whole + * millisecond, then the whole duration must divide into one second without + * leaving a remainder. All normal tick durations will match these criteria, + * including any multiple of hours, minutes, seconds and milliseconds, and + * sensible nanosecond durations, such as 20ns, 250,000ns and 500,000ns. + *

+ * A duration of zero or one nanosecond would have no truncation effect. Passing + * one of these will return the underlying source. + *

+ * Implementations may use a caching strategy for performance reasons. As such, + * it is possible that the start of the requested duration observed via this + * source will be later than that observed directly via the underlying source. + *

+ * The returned implementation is immutable, thread-safe and + * {@code Serializable} providing that the base source is. + * + * @param baseSource the base source to base the ticking source on, not null + * @param tickDuration the duration of each visible tick, not negative, not null + * @return a source that ticks in whole units of the duration, not null + * @throws IllegalArgumentException if the duration is negative, or has a + * part smaller than a whole millisecond such that the whole duration is not + * divisible into one second + * @throws ArithmeticException if the duration is too large to be represented as nanos + */ + static InstantSource tick(InstantSource baseSource, Duration tickDuration) { + Objects.requireNonNull(baseSource, "baseSource"); + return Clock.tick(baseSource.withZone(ZoneOffset.UTC), tickDuration); + } + + //----------------------------------------------------------------------- + /** + * Obtains a source that always returns the same instant. + *

+ * This source simply returns the specified instant. + * As such, it is not a source that represents the current instant. + * The main use case for this is in testing, where the fixed source ensures + * tests are not dependent on the current source. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable}. + * + * @param fixedInstant the instant to use, not null + * @return a source that always returns the same instant, not null + */ + static InstantSource fixed(Instant fixedInstant) { + return Clock.fixed(fixedInstant, ZoneOffset.UTC); + } + + //------------------------------------------------------------------------- + /** + * Obtains a source that returns instants from the specified source with the + * specified duration added. + *

+ * This source wraps another source, returning instants that are later by the + * specified duration. If the duration is negative, the instants will be + * earlier than the current date and time. + * The main use case for this is to simulate running in the future or in the past. + *

+ * A duration of zero would have no offsetting effect. + * Passing zero will return the underlying source. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable} + * providing that the base source is. + * + * @param baseSource the base source to add the duration to, not null + * @param offsetDuration the duration to add, not null + * @return a source based on the base source with the duration added, not null + */ + static InstantSource offset(InstantSource baseSource, Duration offsetDuration) { + Objects.requireNonNull(baseSource, "baseSource"); + return Clock.offset(baseSource.withZone(ZoneOffset.UTC), offsetDuration); + } + + //----------------------------------------------------------------------- + /** + * Gets the current instant of the source. + *

+ * This returns an instant representing the current instant as defined by the source. + * + * @return the current instant from this source, not null + * @throws DateTimeException if the instant cannot be obtained, not thrown by most implementations + */ + Instant instant(); + + //------------------------------------------------------------------------- + /** + * Gets the current millisecond instant of the source. + *

+ * This returns the millisecond-based instant, measured from 1970-01-01T00:00Z (UTC). + * This is equivalent to the definition of {@link System#currentTimeMillis()}. + *

+ * Most applications should avoid this method and use {@link Instant} to represent + * an instant on the time-line rather than a raw millisecond value. + * This method is provided to allow the use of the source in high performance use cases + * where the creation of an object would be unacceptable. + *

+ * The default implementation calls {@link #instant()}. + * + * @return the current millisecond instant from this source, measured from + * the Java epoch of 1970-01-01T00:00Z (UTC), not null + * @throws DateTimeException if the instant cannot be obtained, not thrown by most implementations + */ + default long millis() { + return instant().toEpochMilli(); + } + + //----------------------------------------------------------------------- + /** + * Returns a clock with the specified time-zone. + *

+ * This returns a {@link Clock}, which is an extension of this interface + * that combines this source and the specified time-zone. + *

+ * The returned implementation is immutable, thread-safe and {@code Serializable} + * providing that this source is. + * + * @param zone the time-zone to use, not null + * @return a clock based on this source with the specified time-zone, not null + */ + default Clock withZone(ZoneId zone) { + return new SourceClock(this, zone); + } + +} diff --git a/test/jdk/java/time/test/java/time/TestInstantSource.java b/test/jdk/java/time/test/java/time/TestInstantSource.java new file mode 100644 index 00000000000..62cd9028a9c --- /dev/null +++ b/test/jdk/java/time/test/java/time/TestInstantSource.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package test.java.time; + +import static java.time.temporal.ChronoUnit.SECONDS; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +import java.lang.reflect.Field; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.InstantSource; +import java.time.ZoneId; +import java.time.ZoneOffset; + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * Test instant source. + */ +@Test +public class TestInstantSource { + + private static final ZoneId PARIS = ZoneId.of("Europe/Paris"); + + public void test_system() { + // main tests for Clock.currentInstant() are in TestClock_System + var test = InstantSource.system(); + assertSame(test.withZone(ZoneOffset.UTC), Clock.systemUTC()); + assertEquals(test.withZone(PARIS), Clock.system(PARIS)); + var millis = System.currentTimeMillis(); + var testMillis = test.millis(); + var testInstantMillis = test.instant().toEpochMilli(); + assertTrue(Math.abs(testMillis - millis) < 1000); + assertTrue(Math.abs(testInstantMillis - millis) < 1000); + assertSame(test.equals(InstantSource.system()); + assertEquals(test.hashCode(), InstantSource.system().hashCode()); + assertEquals(test.toString(), "SystemInstantSource"); + } + + public void test_tick() { + var millis = 257265861691L; + var instant = Instant.ofEpochMilli(millis); + var duration = Duration.ofSeconds(1); + var test = InstantSource.tick(InstantSource.fixed(instant), duration); + assertEquals(test.withZone(ZoneOffset.UTC), Clock.tick(Clock.fixed(instant, ZoneOffset.UTC), duration)); + assertEquals(test.withZone(PARIS), Clock.tick(Clock.fixed(instant, PARIS), duration)); + assertEquals(test.millis(), (millis / 1000) * 1000); + assertEquals(test.instant(), instant.truncatedTo(SECONDS)); + assertSame(test.equals(InstantSource.tick(InstantSource.fixed(instant), duration)); + assertEquals(test.hashCode(), InstantSource.tick(InstantSource.fixed(instant), duration).hashCode()); + } + + public void test_fixed() { + var millis = 257265861691L; + var instant = Instant.ofEpochMilli(millis); + var test = InstantSource.fixed(instant); + assertEquals(test.withZone(ZoneOffset.UTC), Clock.fixed(instant, ZoneOffset.UTC)); + assertEquals(test.withZone(PARIS), Clock.fixed(instant, PARIS)); + assertEquals(test.millis(), millis); + assertEquals(test.instant(), instant); + assertSame(test.equals(InstantSource..fixed(instant)); + assertEquals(test.hashCode(), InstantSource..fixed(instant).hashCode()); + } + + public void test_offset() { + var millis = 257265861691L; + var instant = Instant.ofEpochMilli(millis); + var duration = Duration.ofSeconds(120); + var test = InstantSource.offset(InstantSource.fixed(instant), duration); + assertEquals(test.withZone(ZoneOffset.UTC), Clock.offset(Clock.fixed(instant, ZoneOffset.UTC), duration)); + assertEquals(test.withZone(PARIS), Clock.offset(Clock.fixed(instant, PARIS), duration)); + assertEquals(test.millis(), millis + 120_000); + assertEquals(test.instant(), instant.plusSeconds(120)); + assertSame(test.equals(InstantSource.offset(InstantSource.fixed(instant), duration)); + assertEquals(test.hashCode(), InstantSource.offset(InstantSource.fixed(instant), duration).hashCode()); + } + + static class MockInstantSource implements InstantSource { + static final Instant FIXED = Instant.now(); + + public Instant instant() { + return FIXED; + } + } + + public void test_mock() { + var test = new MockInstantSource(); + assertEquals(test.withZone(ZoneOffset.UTC).getZone(), ZoneOffset.UTC); + assertEquals(test.withZone(PARIS).getZone(), PARIS); + assertEquals(test.withZone(ZoneOffset.UTC).withZone(PARIS).getZone(), PARIS); + assertEquals(test.millis(), MockInstantSource.FIXED.toEpochMilli()); + assertEquals(test.instant(), MockInstantSource.FIXED); + assertSame(test.withZone(ZoneOffset.UTC).equals(test.withZone(ZoneOffset.UTC))); + assertEquals(test.withZone(ZoneOffset.UTC).hashCode(), test.withZone(ZoneOffset.UTC).hashCode()); + } + +}