static TemporalAmount distance()

in endorsed/src/org.apache.sis.metadata/main/org/apache/sis/temporal/GeneralDuration.java [169:263]


    static TemporalAmount distance(final Instant self, final Instant other, final boolean negate, final boolean absolute) {
        /*
         * Get the temporal value, or null if "now". Other indeterminate values cause an exception to be thrown.
         * We use null for "now" instead of fetching the current time for two reasons: avoid mismatch by a few
         * nanoseconds when comparing `t1` with `t2`, and for choosing a type compatible with the other instant.
         */
        Temporal t1 = getDeterminatePosition(self);
        Temporal t2 = getDeterminatePosition(other);
        /*
         * Ensures that the given objects both have a date part, or that none of them have a date part.
         * Note that the "epoch day" field is supported by `LocalDate` as well as the dates with zone ID.
         */
        final boolean hasDate = isSupportedByBoth(ChronoField.EPOCH_DAY, t1, t2);
        /*
         * If at least one date has a timezone, then we require that both dates have a timezone.
         * It allows an unambiguous duration in number of days, without time-varying months or years.
         * If one date has a timezone and the other does not, a `DateTimeException` will be thrown.
         *
         * Note 1: we could be lenient and handle the two dates as if they were local, ignoring the timezone.
         * But we avoid false sense of accuracy for now. We may revisit this policy later if there is a need.
         */
        if (t1 != null && t1.isSupported(ChronoField.OFFSET_SECONDS)) {
            if (t2 == null) t2 = ZonedDateTime.now();
            final Duration p = Duration.between(t1, t2);
            return (absolute || p.isNegative() == negate) ? p.abs() : null;
        }
        if (t2 != null && (!hasDate || t2.isSupported(ChronoField.OFFSET_SECONDS))) {
            if (t1 == null) t1 = ZonedDateTime.now();
            final Duration p = Duration.between(t2, t1);        // Negative of the result.
            return (absolute || p.isNegative() != negate) ? p.abs() : null;
        }
        /*
         * Ensure that the given temporal objects both have a time part, or none of them have a time part.
         * If only one of them has a time part, we do not interpret the other one as an instant at midnight
         * in order to avoid false sense of accuracy.
         */
        final boolean hasTime = isSupportedByBoth(ChronoField.SECOND_OF_DAY, t1, t2);
        if (t1 == null) t1 = LocalDateTime.now();
        if (t2 == null) t2 = LocalDateTime.now();
        ChronoLocalDate d1 = null, d2 = null;
        if (hasDate) {
            d1 = ChronoLocalDate.from(t1);
            d2 = ChronoLocalDate.from(t2);
            if (!absolute && (negate ? d1.isBefore(d2) : d1.isAfter(d2))) {
                return null;        // Stop early if we can.
            }
        }
        /*
         * Compute the duration in the time part. If negative (after negation if `negate` is true),
         * then we add the necessary number of days to make it positive and remove the same number
         * of days from the date. We adjust the date instead of the period computed by `d1.until(d2)`
         * in order to have the correct adjustment for the variable number of days in each month.
         */
        Duration time = Duration.ZERO;
        if (hasTime) {
            time = Duration.between(LocalTime.from(t1), LocalTime.from(t2));
            if (hasDate) {
                final boolean isPositive = d1.isBefore(d2);
                if (isPositive || d1.isAfter(d2)) {                 // Require the period to be non-zero.
                    if (isPositive ? time.isNegative() : JDK18.isPositive(time)) {
                        long n = time.toDays();                     // Truncated toward 0.
                        if (isPositive) {
                            d2 = d2.plus (--n, ChronoUnit.DAYS);    // `n` is negative. Reduces period by decreasing the ending.
                        } else {
                            d1 = d1.minus(++n, ChronoUnit.DAYS);    // `n` is positive. Reduces period by increasing the beginning.
                        }
                        time = time.minusDays(n);                   // If negative, make positive. If positive, make negative.
                    }
                }
            }
        }
        /*
         * Get the period for the date part, then combine with the time part if non-zero.
         * The result shall be either positive or null.
         */
        if (hasDate) {
            ChronoPeriod period = d1.until(d2);
            if (period.isZero()) {
                if (time.isZero()) {
                    return period;
                }
            } else {
                if (period.isNegative()) {
                    if (!(negate | absolute)) {                 // Equivalent to (!negate && !absolute).
                        return null;
                    }
                    period = period.negated();
                } else if (negate & !absolute) {                // Equivalent to (negate && !absolute).
                    return null;
                }
                return time.isZero() ? period : new GeneralDuration(Period.from(period), time.abs());
            }
        }
        return (absolute || time.isNegative() == negate) ? time.abs() : null;
    }