Determine precision of an instant in terms of minutes, seconds, milliseconds, etc?

Viewed 446

In java.time you can query the precision of a time unit:

java.time.Instant.now().query(java.time.temporal.TemporalQueries.precision())

When I do this I get a response that the instant has precision: java.time.temporal.ChronoUnit/NANOS.

Is it possible to know whether an instant has only enough information to be: java.time.temporal.ChronoUnit/MINUTES (or another time unit that can be packed into an instant, e.g. seconds, hours, etc)?

2 Answers

tl;dr

instant.truncatedTo( someChronoUnit ).equals( instant )

Truncate

As you described, TemporalQueries.precision returns a TemporalQuery<TemporalUnit> that represents the capability of the data type, not the contents of an instance of that type.

I do not know of a direct way to determine the significant (non-zero) granularity of the content of an Instant. But you could hack a way by truncating for each level of granularity until finding a result that no longer matches the original Instant instance.

The ChronoUnit enum in Java defines the levels of granularity in java.time, and implements TemporalUnit interface. Loop through the relevant ChronoUnit enum objects (hours, minutes, seconds, nanos), truncating the target Instant object to each.

for ( ChronoUnit unit : List.of( ChronoUnit.HOURS , ChronoUnit.MINUTES , ChronoUnit.SECONDS , ChronoUnit.NANOS ) )
{
    if ( instant.truncatedTo( unit ).equals( instant ) ) { return unit; }
}

Full example code.

package work.basil.example.datetime;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Objects;

public class Granularity
{
    private final static List < java.time.temporal.ChronoUnit > CHRONO_UNITS = List.of( ChronoUnit.HOURS , ChronoUnit.MINUTES , ChronoUnit.SECONDS , ChronoUnit.NANOS );

    public static ChronoUnit resolve ( final Instant instant )
    {
        Objects.requireNonNull( instant );
        for ( ChronoUnit unit : Granularity.CHRONO_UNITS )
        {
            if ( instant.truncatedTo( unit ).equals( instant ) ) { return unit; }
        }
        throw new IllegalStateException();  // Should never reach this point.
    }

    public static void main ( String[] args )
    {
        Instant instant = Instant.parse( "2021-01-23T12:00:00.000000123Z" );
        ChronoUnit unit = Granularity.resolve( instant );
        System.out.println( "unit = " + unit );
    }
}

You could also do something similar by calling Instant#get for each part (each ChronoField) to test for a non-zero value.

One approach would be to compare the instant with another instant using the truncatedTo method.

For example

Instant one = Instant.now();
Instant two = Instant.ofEpochSecond(one.getEpochSecond());
System.out.println(one);
System.out.println(two);
Instant three = one.truncatedTo(ChronoUnit.SECONDS);
System.out.println(three);

This prints

2021-03-11T07:43:43.579704700Z
2021-03-11T07:43:43Z
2021-03-11T07:43:43Z

Two and three are equal.

In the general case, if a "raw" instant is equal to its truncated self based on a chrono unit (seconds, minutes, etc) then that instant has only enough information for the chrono unit specified.

Something like

boolean onlyEnoughFor(Instant instant, ChronoUnit unit) {
    return instant.truncatedTo(unit).equals(instant);
}
Related