Cover image source: Sticker Mania
1. Understand Time-scales:
Before diving deep to Instant, let’s discover some time-scales.
UT (Universal Time) is a time standard based on Earth’s rotation, essentially measuring time as observed from the planet. It reflects the natural cycle of day and night but can vary slightly due to irregularities in Earth’s rotation.
TAI (Temps Atomique International or International Atomic Time) is a highly precise time standard based entirely on atomic clocks, specifically using the vibrations of Cesium-133 atoms.
UTC (Coordinated Universal Time) is the global time standard and is the combination of UT and TAI. It is designed to be more consistent than UT by relying on precise atomic clocks (same as TAI).
Because of tidal acceleration, the Earth’s rotation is gradually slowing down. As a result, at some point some time, the day in UT (solar day) will get longer than today by one atomic second, which means some day’s length will be 86400 + 1 atomic second. The day that will last 1-atomic-second-longer than today. That day is not predictable and can only be figured out by measurement.
To deal with the issue, UTC is synchronized with UT through the use of leap seconds, ensuring it stays within ±0.9 seconds of UT, which helps keep it in line with the natural day. As the Earth’s rotation is slowing down, UTC will only add a leap second to be consistent with UT.
As an observation, the earth’s rotational acceleration is on average 0.002 atomic seconds per day, which means 500 days or 1.5 years will end up with 1 atomic second.
Let’s take a example:
- Today, Tuesday, January 20, 2024, which is exactly 86400 atomic seconds.
- 1.5 years from now, Friday, January 02, 2026, a day will be 86400 + 1 atomic seconds.
But the difference between UT and UTC should be within ±0.9 seconds, then at some point of time between today and Friday, January 02, 2026, a leap second will be added at the end of that day, which introduces the 61st second of a minute: yyyy-MM-ddT23:59:60Z.
While UTC adds a leap second to be synchronized with UT, TAI, on the other hand, does not. As a result, time in UTC and TAI will be further and further apart. At this moment, TAI is 37 seconds ahead of UTC:
- UTC: 2024-08-20 14:47:55Z
- TAI: 2024-08-20 14:48:32Z
- See real-time gap here: http://leapsecond.com/java/gpsclock.htm
Java Time-scale
Although Instant represents a point in time in UTC, it follows a 99%-like UTC time-scale, called Java Time-scale.
In Java Time-scale, one day includes exactly 86,400 seconds without a leap second. If a day in UTC contains a leap second, that leap second will be spread equally over the last 1000 seconds of the day, maintaining the appearance of exactly 86400 seconds per day.
2. How Instant is stored in Java Application
Instant is an object that:
- represents a point in time in UTC since 1970-01-01T00:00:00Z, we call 1970-01-01T00:00:00Z an EPOCH.
- with nanosecond precision.
To represent a point in time, Instant introduces:
/**
* The number of seconds from the epoch of 1970-01-01T00:00:00Z.
*/
private final long seconds;
- seconds is the number of seconds from the EPOCH.
If seconds are negative, the instant is before the EPOCH.
If seconds are positive, the instant is after the EPOCH.
Despite Long.MIN_VALUE is -9,223,372,036,854,775,808, the minimum supported epoch second is -31,557,014,167,219,200, which is -1000000000-01-01T00:00Z (one billion year in the past) and the maximum supported epoch second is 31,556,889,864,403,199, which is 1000000000-12-31T23:59:59Z (the end of 1 billion years in the future).
To represent a point in time with nanosecond precision, Instant introduces:
/**
* The number of nanoseconds, later along the time-line, from the seconds field.
* This is always positive, and never exceeds 999,999,999.
*/
private final int nanos;
- nanos is always positive, and never bigger than 999,999,999
Some reasons why min/max supported epoch seconds is not Long.MAX/MIN_VALUE:
From a usage perspective:
- The universe is said to be aged 13.7 billion years old; Long.MIN_VALUE will introduce the time since more than 292 billion years. -> Long.MIN_VALUE is not practical.
- 292 billion years in the future (which is Long.MAX_VALUE) is an extreme value (compared to human lifetime), making it not practical either.
- According to research, the first humans emerged in Africa around 2 million years ago, so there should not be much historical data that modern-universal applications should deal with.
- ±1 billion of year make it have a good buffer in long type and make the year (1 billion) fit in an int.
From a technical perspective:
- Storing extreme value will easily cause exceptions. I asked ChatGPT to calculate the instant of Long.MIN_VALUE, ChatGPT could not answer due to an Overflow issue.
Limiting the range of value to be far away from the edge will help operations like addition, subtraction, and comparison can be performed safely.
Storing extremely large values can pose challenges in serialization, database storage, and data transmission.
3. Instant.now()
Returns the current moment in UTC with precision in:
- Millisecond: Java 8
- Microsecond: Java 9, Java 11
- Nanosecond: Java 17+
Use the below code snippet to run on https://www.jdoodle.com/online-java-compiler with each JDK, you’ll see the difference.
import java.time.Instant;
public class MyClass {
public static void main(String args[]) {
Instant instant = Instant.now();
System.out.println(instant);
//2024-08-19T15:47:28.487729962Z
}
}
The precision of Instant.now() can vary between different JVMs and OS/Hardware. On some platforms, you might observe higher precision, while on others, it may be lower.
Behind the Scenes of Instant.now()
The Instant.now() method provides a seemingly straightforward way to retrieve the current moment in time. But what happens beneath the surface? Let’s delve into the intricate workings of this method:
//This is simplified implementation from Clock.java without retry
static Instant currentInstant() {
long localOffset = System.currentTimeMillis() / 1000 - 1024;
long adjustment = VM.getNanoTimeAdjustment(localOffset);
return Instant.ofEpochSecond(localOffset, adjustment);
}
Setting the Local Offset:
The code starts by calculating a preliminary offset from the current system time. This offset, represented by localOffset, is obtained by dividing the system time in milliseconds (System.currentTimeMillis()) by 1000 (to convert milliseconds to seconds) and then subtracting 1024 seconds (approximately 17 minutes and 4 seconds).
Fine-Tuning with Nanosecond Adjustment:
The VM.getNanoTimeAdjustment(localOffset) method is called. This method interacts with the underlying system to determine the precise nanosecond adjustment needed to convert the localOffset to the current UTC time. The returned value, nanoTimeAdjustment, represents the time difference (in nanoseconds) between localOffset and the actual UTC time.
Constructing the Instant:
Finally, an Instant object is created using the localOffset (seconds since the epoch) and the nanoTimeAdjustment (nanoseconds within the second).
Example Breakdown:
Imagine the current UTC time is 2024-08-20T15:59:50.123Z, which translates to epoch milliseconds of 1724144390123. Below is how currentInstant() would run:
long localOffset = System.currentTimeMillis() / 1000 - 1024;
// localOffset = 1724144390123 / 1000 - 1024 = 1724143366
long adjustment = VM.getNanoTimeAdjustment(localOffset);
// 1024044595000
return Instant.ofEpochSecond(localOffset, adjustment);
// Instant.ofEpochSecond(1724143366, 1024044595000);
// 2024-08-20T09:12:18.044595Z
Nanosecond Adjustment:
Suppose VM.getNanoTimeAdjustment(1724143366) returns 1024044595000, which equates to 1024.044595 seconds.
The Catch:
While the returned value appears to provide nanosecond precision (due to the decimal component), closer inspection reveals that the last three digits are zeros. This implies that the underlying system can only guarantee accuracy up to milliseconds.
The Resulting Instant:
Instant.ofEpochSecond(1724143366, 1024044595000)
This translates to an Instant object representing 2024-08-20T09:12:18.044595Z. However, it’s crucial to remember that the nanosecond precision might not be entirely reliable due to the system limitations.
In essence, Instant.now() provides a convenient way to access the current time with nanosecond precision (in theory). However, the underlying system might restrict the actual accuracy to milliseconds. Remember, this explanation is for educational purposes and might not reflect the exact implementation details in all Java environments.
4. Conclusion
In conclusion, java.time.Instant is a powerful class that represents a specific point on the timeline, measured to the nanosecond. It offers a reliable way to work with timestamps in UTC, making it ideal for applications that require precise time measurements. With its immutable nature and comprehensive API, Instant simplifies handling of time data in Java, whether you’re recording events, synchronizing across time zones, or performing time-based calculations. By leveraging Instant, developers can ensure accurate and consistent time management in their applications, paving the way for robust and future-proof solutions.
Thank you all for reading all the way down to this. This article is just the beginning of java.time, let’s wait for upcoming posts!
Reference: