java.time.Instant.plus (длинная сумма для добавления, единица измерения TemporalUnit) Неподдерживаемая единица измерения

Я пытаюсь добавить несколько лет к текущему времени. Мой код выглядит так:

// ten yeas ago
int backYears = 10;
Instant instant = ChronoUnit.YEARS.addTo(Instant.now(), -backYears);

Но у меня есть исключение:

java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Years
at java.time.Instant.plus(Instant.java:862)

Когда я открыл метод Instant.plus, я вижу следующее:

@Override
public Instant plus(long amountToAdd, TemporalUnit unit) {
    if (unit instanceof ChronoUnit) {
        switch ((ChronoUnit) unit) {
            case NANOS: return plusNanos(amountToAdd);
            case MICROS: return plus(amountToAdd / 1000_000, (amountToAdd % 1000_000) * 1000);
            case MILLIS: return plusMillis(amountToAdd);
            case SECONDS: return plusSeconds(amountToAdd);
            case MINUTES: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_MINUTE));
            case HOURS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_HOUR));
            case HALF_DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY / 2));
            case DAYS: return plusSeconds(Math.multiplyExact(amountToAdd, SECONDS_PER_DAY));
        }
        throw new UnsupportedTemporalTypeException("Unsupported unit: " + unit);
    }
    return unit.addTo(this, amountToAdd);
}

Как видите, MONTHS и YEARS не поддерживаются. Но почему? Со старым java.util.Calendar я могу сделать это легко:

    Calendar c = Calendar.getInstance();
    c.setTime(date);
    c.add(Calendar.YEAR, amount);
    return c.getTime(); 

Единственная причина, по которой я думаю, заключается в том, что мы не знаем, сколько дней в месяце и году из-за високосного дня 29 февраля. Но, если честно, у нас также есть високосная секунда. Таким образом, я думаю, что это ошибка, и все ChronoUnit должны поддерживаться. Вопрос только один: нужно ли учитывать високосную секунду и високосный день. Что касается моих потребностей, то можно просто предположить, что в месяце 30 дней, а в году 365. Мне не нужно делать что-то вроде Calendar.roll(), но это тоже может меня удовлетворить.


person Sergey Ponomarev    schedule 10.11.2017    source источник
comment
Я не проектировал класс, поэтому не могу сказать наверняка. Я думаю, что единицы от нано до дней могут быть однозначно преобразованы в секунды и нано (игнорируя високосные секунды, потому что почти все компьютеры их игнорируют) и поэтому поддерживаются. Такие классы, как ZonedDateTime, OffsetDateTime и LocalDateTime, умеют обрабатывать месяцы и годы, поэтому используйте их для добавления таких единиц.   -  person Ole V.V.    schedule 11.11.2017


Ответы (1)


Давайте попробуем что-нибудь. Я беру момент как ZonedDateTime и вычитаю 10 лет в разных часовых поясах.

    OffsetDateTime origin = OffsetDateTime.of(2018, 3, 1, 0, 0, 0, 0, ZoneOffset.UTC);
    Instant originInstant = origin.toInstant();
    Instant tenYearsBackKyiv = origin.atZoneSameInstant(ZoneId.of("Europe/Kiev"))
            .minusYears(10)
            .toInstant();
    long hoursSubtractedKyiv = ChronoUnit.HOURS.between(tenYearsBackKyiv, originInstant);
    System.out.println("Hours subtracted in Київ: " + hoursSubtractedKyiv);
    Instant tenYearsBackSaoPaulo = origin.atZoneSameInstant(ZoneId.of("America/Sao_Paulo"))
            .minusYears(10)
            .toInstant();
    long hoursSubtractedSaoPaulo = ChronoUnit.HOURS.between(tenYearsBackSaoPaulo, originInstant);
    System.out.println("Hours subtracted in São Paulo: " + hoursSubtractedSaoPaulo);

Результат:

Hours subtracted in Київ: 87648
Hours subtracted in São Paulo: 87672

Как видите, в Сан-Паулу вычитается на 24 часа больше (на 1 день больше) по сравнению с Киевом (Киев, Киев). Вы, наверное, уже догадались, что это потому, что там мы переходим с 1 марта по 29 февраля в високосные годы три раза, а в Киеве только дважды.

Старый и ныне устаревший класс Calendar всегда имел в себе часовой пояс, поэтому знал, в каком часовом поясе вычитать годы (другое дело, он был рад выдать вам результат даже в ситуациях, когда было непонятно, какой результат вы хотите). Современные классы ZonedDateTime, OffsetDateTime и LocalDateTime могут делать то же самое. Так что используйте их. Концептуально Instant не имеет часового пояса, поэтому отказывается выполнять операции, зависящие от часового пояса (я знаю, что это реализовано с использованием UTC, но мы должны рассматривать это как не относящуюся к делу деталь реализации, а не как часть спецификации интерфейса). к классу).

Ни старые, ни современные классы не учитывают високосных секунд, и вы правы, только поэтому Instant могут складывать и вычитать дни, часы и минуты.

person Ole V.V.    schedule 11.11.2017