Skip to content

Commit 6c838c5

Browse files
Stephen ColebourneRoger Riggs
authored andcommitted
8266846: Add java.time.InstantSource
Reviewed-by: rriggs, naoto, darcy
1 parent 7f55dc1 commit 6c838c5

6 files changed

Lines changed: 559 additions & 87 deletions

File tree

src/java.base/share/classes/java/time/Clock.java

Lines changed: 178 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -63,6 +63,8 @@
6363

6464
import java.io.IOException;
6565
import java.io.ObjectInputStream;
66+
import java.io.ObjectStreamException;
67+
6668
import static java.time.LocalTime.NANOS_PER_MINUTE;
6769
import static java.time.LocalTime.NANOS_PER_SECOND;
6870
import static java.time.LocalTime.NANOS_PER_MILLI;
@@ -74,9 +76,10 @@
7476
/**
7577
* A clock providing access to the current instant, date and time using a time-zone.
7678
* <p>
77-
* Instances of this class are used to find the current instant, which can be
78-
* interpreted using the stored time-zone to find the current date and time.
79-
* As such, a clock can be used instead of {@link System#currentTimeMillis()}
79+
* Instances of this abstract class are used to access a pluggable representation of the
80+
* current instant, which can be interpreted using the stored time-zone to find the
81+
* current date and time.
82+
* For example, {@code Clock} can be used instead of {@link System#currentTimeMillis()}
8083
* and {@link TimeZone#getDefault()}.
8184
* <p>
8285
* Use of a {@code Clock} is optional. All key date-time classes also have a
@@ -85,9 +88,13 @@
8588
* plugged in as and when required. Applications use an object to obtain the
8689
* current time rather than a static method. This can simplify testing.
8790
* <p>
91+
* As such, this abstract class does not guarantee the result actually represents the current instant
92+
* on the time-line. Instead, it allows the application to provide a controlled view as to what
93+
* the current instant and time-zone are.
94+
* <p>
8895
* Best practice for applications is to pass a {@code Clock} into any method
89-
* that requires the current instant. A dependency injection framework is one
90-
* way to achieve this:
96+
* that requires the current instant and time-zone. A dependency injection framework
97+
* is one way to achieve this:
9198
* <pre>
9299
* public class MyBean {
93100
* private Clock clock; // dependency inject
@@ -99,16 +106,17 @@
99106
* }
100107
* }
101108
* </pre>
102-
* This approach allows an alternate clock, such as {@link #fixed(Instant, ZoneId) fixed}
109+
* This approach allows an alternative clock, such as {@link #fixed(Instant, ZoneId) fixed}
103110
* or {@link #offset(Clock, Duration) offset} to be used during testing.
104111
* <p>
105112
* The {@code system} factory methods provide clocks based on the best available
106-
* system clock This may use {@link System#currentTimeMillis()}, or a higher
113+
* system clock. This may use {@link System#currentTimeMillis()}, or a higher
107114
* resolution clock if one is available.
108115
*
109116
* @implSpec
110117
* This abstract class must be implemented with care to ensure other classes operate correctly.
111-
* All implementations that can be instantiated must be final, immutable and thread-safe.
118+
* All implementations must be thread-safe - a single instance must be capable of be invoked
119+
* from multiple threads without negative consequences such as race conditions.
112120
* <p>
113121
* The principal methods are defined to allow the throwing of an exception.
114122
* In normal use, no exceptions will be thrown, however one possible implementation would be to
@@ -126,18 +134,11 @@
126134
* Implementations should implement {@code Serializable} wherever possible and must
127135
* document whether or not they do support serialization.
128136
*
129-
* @implNote
130-
* The clock implementation provided here is based on the same underlying clock
131-
* as {@link System#currentTimeMillis()}, but may have a precision finer than
132-
* milliseconds if available.
133-
* However, little to no guarantee is provided about the accuracy of the
134-
* underlying clock. Applications requiring a more accurate clock must implement
135-
* this abstract class themselves using a different external clock, such as an
136-
* NTP server.
137+
* @see InstantSource
137138
*
138139
* @since 1.8
139140
*/
140-
public abstract class Clock {
141+
public abstract class Clock implements InstantSource {
141142

142143
/**
143144
* Obtains a clock that returns the current instant using the best available
@@ -354,7 +355,7 @@ public static Clock fixed(Instant fixedInstant, ZoneId zone) {
354355
//-------------------------------------------------------------------------
355356
/**
356357
* Obtains a clock that returns instants from the specified clock with the
357-
* specified duration added
358+
* specified duration added.
358359
* <p>
359360
* This clock wraps another clock, returning instants that are later by the
360361
* specified duration. If the duration is negative, the instants will be
@@ -408,6 +409,7 @@ protected Clock() {
408409
* @param zone the time-zone to change to, not null
409410
* @return a clock based on this clock with the specified time-zone, not null
410411
*/
412+
@Override
411413
public abstract Clock withZone(ZoneId zone);
412414

413415
//-------------------------------------------------------------------------
@@ -428,6 +430,7 @@ protected Clock() {
428430
* the Java epoch of 1970-01-01T00:00Z (UTC), not null
429431
* @throws DateTimeException if the instant cannot be obtained, not thrown by most implementations
430432
*/
433+
@Override
431434
public long millis() {
432435
return instant().toEpochMilli();
433436
}
@@ -441,6 +444,7 @@ public long millis() {
441444
* @return the current instant from this clock, not null
442445
* @throws DateTimeException if the instant cannot be obtained, not thrown by most implementations
443446
*/
447+
@Override
444448
public abstract Instant instant();
445449

446450
//-----------------------------------------------------------------------
@@ -473,32 +477,120 @@ public int hashCode() {
473477
return super.hashCode();
474478
}
475479

480+
//-----------------------------------------------------------------------
481+
// initial offset
482+
private static final long OFFSET_SEED = System.currentTimeMillis() / 1000 - 1024;
483+
// We don't actually need a volatile here.
484+
// We don't care if offset is set or read concurrently by multiple
485+
// threads - we just need a value which is 'recent enough' - in other
486+
// words something that has been updated at least once in the last
487+
// 2^32 secs (~136 years). And even if we by chance see an invalid
488+
// offset, the worst that can happen is that we will get a -1 value
489+
// from getNanoTimeAdjustment, forcing us to update the offset
490+
// once again.
491+
private static long offset = OFFSET_SEED;
492+
493+
static Instant currentInstant() {
494+
// Take a local copy of offset. offset can be updated concurrently
495+
// by other threads (even if we haven't made it volatile) so we will
496+
// work with a local copy.
497+
long localOffset = offset;
498+
long adjustment = VM.getNanoTimeAdjustment(localOffset);
499+
500+
if (adjustment == -1) {
501+
// -1 is a sentinel value returned by VM.getNanoTimeAdjustment
502+
// when the offset it is given is too far off the current UTC
503+
// time. In principle, this should not happen unless the
504+
// JVM has run for more than ~136 years (not likely) or
505+
// someone is fiddling with the system time, or the offset is
506+
// by chance at 1ns in the future (very unlikely).
507+
// We can easily recover from all these conditions by bringing
508+
// back the offset in range and retry.
509+
510+
// bring back the offset in range. We use -1024 to make
511+
// it more unlikely to hit the 1ns in the future condition.
512+
localOffset = System.currentTimeMillis() / 1000 - 1024;
513+
514+
// retry
515+
adjustment = VM.getNanoTimeAdjustment(localOffset);
516+
517+
if (adjustment == -1) {
518+
// Should not happen: we just recomputed a new offset.
519+
// It should have fixed the issue.
520+
throw new InternalError("Offset " + localOffset + " is not in range");
521+
} else {
522+
// OK - recovery succeeded. Update the offset for the
523+
// next call...
524+
offset = localOffset;
525+
}
526+
}
527+
return Instant.ofEpochSecond(localOffset, adjustment);
528+
}
529+
530+
//-----------------------------------------------------------------------
531+
/**
532+
* An instant source that always returns the latest time from
533+
* {@link System#currentTimeMillis()} or equivalent.
534+
*/
535+
static final class SystemInstantSource implements InstantSource, Serializable {
536+
@java.io.Serial
537+
private static final long serialVersionUID = 3232399674412L;
538+
// this is a singleton, but the class is coded such that it is not a
539+
// problem if someone hacks around and creates another instance
540+
static final SystemInstantSource INSTANCE = new SystemInstantSource();
541+
542+
SystemInstantSource() {
543+
}
544+
@Override
545+
public Clock withZone(ZoneId zone) {
546+
return Clock.system(zone);
547+
}
548+
@Override
549+
public long millis() {
550+
// System.currentTimeMillis() and VM.getNanoTimeAdjustment(offset)
551+
// use the same time source - System.currentTimeMillis() simply
552+
// limits the resolution to milliseconds.
553+
// So we take the faster path and call System.currentTimeMillis()
554+
// directly - in order to avoid the performance penalty of
555+
// VM.getNanoTimeAdjustment(offset) which is less efficient.
556+
return System.currentTimeMillis();
557+
}
558+
@Override
559+
public Instant instant() {
560+
return currentInstant();
561+
}
562+
@Override
563+
public boolean equals(Object obj) {
564+
return obj instanceof SystemInstantSource;
565+
}
566+
@Override
567+
public int hashCode() {
568+
return SystemInstantSource.class.hashCode();
569+
}
570+
@Override
571+
public String toString() {
572+
return "SystemInstantSource";
573+
}
574+
@java.io.Serial
575+
private Object readResolve() throws ObjectStreamException {
576+
return SystemInstantSource.INSTANCE;
577+
}
578+
}
579+
476580
//-----------------------------------------------------------------------
477581
/**
478582
* Implementation of a clock that always returns the latest time from
479-
* {@link System#currentTimeMillis()}.
583+
* {@code SystemInstantSource.INSTANCE}.
480584
*/
481585
static final class SystemClock extends Clock implements Serializable {
482586
@java.io.Serial
483587
private static final long serialVersionUID = 6740630888130243051L;
484-
private static final long OFFSET_SEED =
485-
System.currentTimeMillis()/1000 - 1024; // initial offest
486588
static final SystemClock UTC = new SystemClock(ZoneOffset.UTC);
487589

488590
private final ZoneId zone;
489-
// We don't actually need a volatile here.
490-
// We don't care if offset is set or read concurrently by multiple
491-
// threads - we just need a value which is 'recent enough' - in other
492-
// words something that has been updated at least once in the last
493-
// 2^32 secs (~136 years). And even if we by chance see an invalid
494-
// offset, the worst that can happen is that we will get a -1 value
495-
// from getNanoTimeAdjustment, forcing us to update the offset
496-
// once again.
497-
private transient long offset;
498591

499592
SystemClock(ZoneId zone) {
500593
this.zone = zone;
501-
this.offset = OFFSET_SEED;
502594
}
503595
@Override
504596
public ZoneId getZone() {
@@ -513,50 +605,13 @@ public Clock withZone(ZoneId zone) {
513605
}
514606
@Override
515607
public long millis() {
516-
// System.currentTimeMillis() and VM.getNanoTimeAdjustment(offset)
517-
// use the same time source - System.currentTimeMillis() simply
518-
// limits the resolution to milliseconds.
519-
// So we take the faster path and call System.currentTimeMillis()
520-
// directly - in order to avoid the performance penalty of
521-
// VM.getNanoTimeAdjustment(offset) which is less efficient.
608+
// inline of SystemInstantSource.INSTANCE.millis()
522609
return System.currentTimeMillis();
523610
}
524611
@Override
525612
public Instant instant() {
526-
// Take a local copy of offset. offset can be updated concurrently
527-
// by other threads (even if we haven't made it volatile) so we will
528-
// work with a local copy.
529-
long localOffset = offset;
530-
long adjustment = VM.getNanoTimeAdjustment(localOffset);
531-
532-
if (adjustment == -1) {
533-
// -1 is a sentinel value returned by VM.getNanoTimeAdjustment
534-
// when the offset it is given is too far off the current UTC
535-
// time. In principle, this should not happen unless the
536-
// JVM has run for more than ~136 years (not likely) or
537-
// someone is fiddling with the system time, or the offset is
538-
// by chance at 1ns in the future (very unlikely).
539-
// We can easily recover from all these conditions by bringing
540-
// back the offset in range and retry.
541-
542-
// bring back the offset in range. We use -1024 to make
543-
// it more unlikely to hit the 1ns in the future condition.
544-
localOffset = System.currentTimeMillis()/1000 - 1024;
545-
546-
// retry
547-
adjustment = VM.getNanoTimeAdjustment(localOffset);
548-
549-
if (adjustment == -1) {
550-
// Should not happen: we just recomputed a new offset.
551-
// It should have fixed the issue.
552-
throw new InternalError("Offset " + localOffset + " is not in range");
553-
} else {
554-
// OK - recovery succeeded. Update the offset for the
555-
// next call...
556-
offset = localOffset;
557-
}
558-
}
559-
return Instant.ofEpochSecond(localOffset, adjustment);
613+
// inline of SystemInstantSource.INSTANCE.instant()
614+
return currentInstant();
560615
}
561616
@Override
562617
public boolean equals(Object obj) {
@@ -573,13 +628,6 @@ public int hashCode() {
573628
public String toString() {
574629
return "SystemClock[" + zone + "]";
575630
}
576-
@java.io.Serial
577-
private void readObject(ObjectInputStream is)
578-
throws IOException, ClassNotFoundException {
579-
// ensure that offset is initialized
580-
is.defaultReadObject();
581-
offset = OFFSET_SEED;
582-
}
583631
}
584632

585633
//-----------------------------------------------------------------------
@@ -684,7 +732,7 @@ public String toString() {
684732

685733
//-----------------------------------------------------------------------
686734
/**
687-
* Implementation of a clock that adds an offset to an underlying clock.
735+
* Implementation of a clock that reduces the tick frequency of an underlying clock.
688736
*/
689737
static final class TickClock extends Clock implements Serializable {
690738
@java.io.Serial
@@ -740,4 +788,54 @@ public String toString() {
740788
}
741789
}
742790

791+
//-----------------------------------------------------------------------
792+
/**
793+
* Implementation of a clock based on an {@code InstantSource}.
794+
*/
795+
static final class SourceClock extends Clock implements Serializable {
796+
@java.io.Serial
797+
private static final long serialVersionUID = 235386528762398L;
798+
@SuppressWarnings("serial") // Not statically typed as Serializable
799+
private final InstantSource baseSource;
800+
private final ZoneId zone;
801+
802+
SourceClock(InstantSource baseSource, ZoneId zone) {
803+
this.baseSource = baseSource;
804+
this.zone = zone;
805+
}
806+
@Override
807+
public ZoneId getZone() {
808+
return zone;
809+
}
810+
@Override
811+
public Clock withZone(ZoneId zone) {
812+
if (zone.equals(this.zone)) { // intentional NPE
813+
return this;
814+
}
815+
return new SourceClock(baseSource, zone);
816+
}
817+
@Override
818+
public long millis() {
819+
return baseSource.millis();
820+
}
821+
@Override
822+
public Instant instant() {
823+
return baseSource.instant();
824+
}
825+
@Override
826+
public boolean equals(Object obj) {
827+
return (obj instanceof SourceClock other)
828+
&& zone.equals(other.zone)
829+
&& baseSource.equals(other.baseSource);
830+
}
831+
@Override
832+
public int hashCode() {
833+
return baseSource.hashCode() ^ zone.hashCode();
834+
}
835+
@Override
836+
public String toString() {
837+
return "SourceClock[" + baseSource + "," + zone + "]";
838+
}
839+
}
840+
743841
}

0 commit comments

Comments
 (0)