Как преобразовать двузначный год в полный год с помощью Java 8 time API

Я хочу удалить библиотеку Joda-Time из моего проекта.

Я пытаюсь преобразовать двузначный год в полный год. Следующий код от Joda-Time может выполнить цель. Ниже приведен следующий код joda-time

DateTimeFormatter TWO_YEAR_FORMATTER = DateTimeFormat.forPattern("yy");
int year = LocalDate.parse("99"", TWO_YEAR_FORMATTER).getYear();
System.out.println(year);

Выход: 1999

Это результат, который я ожидаю, и это имеет смысл в моей ситуации. Однако, когда я пытаюсь выполнить ту же процедуру с API java.time, он создает исключение DatetimeParseException. Ниже приведен следующий код API java.time:

DateTimeFormatter TWO_YEAR_FORMATTER = DateTimeFormatter.ofPattern("yy");
int year = LocalDate.parse("99", TWO_YEAR_FORMATTER).getYear();
System.out.println(year);

Трассировки стека:

Exception in thread "main" java.time.format.DateTimeParseException: Text '99' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {Year=2099},ISO of type java.time.format.Parsed
    at java.base/java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:2017)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1952)
    at java.base/java.time.LocalDate.parse(LocalDate.java:428)
    at scratch.main(scratch.java:10)
Caused by: java.time.DateTimeException: Unable to obtain LocalDate from TemporalAccessor: {Year=2099},ISO of type java.time.format.Parsed
    at java.base/java.time.LocalDate.from(LocalDate.java:396)
    at java.base/java.time.format.Parsed.query(Parsed.java:235)
    at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1948)
    ... 2 more

Я не смог увидеть причину трассировки стека. Было бы неплохо, если бы кто-нибудь помог мне разобраться в следующем сценарии, а также объяснить, как преобразовать двузначный год в полный год, используя API времени Java 8.

5 ответов

Решение

Проблема в том, что вы не можете разобрать год самостоятельно в LocalDate. LocalDate требует больше информации, чем это.

Вы можете использовать parse метод форматирования, который даст вам TemporalAccessor, а затем получить поле год от этого:

int year = TWO_YEAR_FORMATTER.parse("99").get(ChronoField.YEAR);
System.out.println(year);

Устранение несоответствия между ними: это два разных API. Да, они очень похожи, и java.time Пакет был проинформирован дизайнерскими решениями JodaTime, но никогда не предназначался для его замены.

См. Этот ответ, если вы хотите изменить сводный год (по умолчанию "99" будет разрешать до 2099, а не 1999).

Почему это не сработало

java.time не предоставляет значения по умолчанию для месяца и дня месяца, как это делает Joda-Time. В сообщении говорится, что он не может получить LocalDate только за год или наоборот, он пропускает месяц и день (вы можете указать свои собственные значения по умолчанию, как показано в ответе Михаила Холодкова). Как правило, это поведение помогает нам: оно напоминает нам о необходимости ввода всех необходимых значений и проясняет из кода, используются ли какие-либо значения по умолчанию и какие.

Что делать вместо

Просто используйте Year класс java.time, Сначала объявить

public static final DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
        .appendValueReduced(ChronoField.YEAR, 2, 2, 1950)
        .toFormatter();

Тогда используйте

    int year = Year.parse("99", TWO_YEAR_FORMATTER).getValue();
    System.out.println(year);

Выход

1999

Укажите желаемое базовое значение, где я указал 1950, чтобы указать год в течение 99 лет (включительно) от этого года. Если вы хотите, чтобы год был в прошлом, включая этот, вы можете использовать:

private static final Year base = Year.now(ZoneId.of("Asia/Kolkata")).minusYears(99);
public static final DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
        .appendValueReduced(ChronoField.YEAR, 2, 2, base.getValue())
        .toFormatter();

Кстати, не верьте мне на слово, но я думаю, что Joda время использует текущий год минус 30 лет в качестве основы или поворота. Если это так, то использование 30 вместо 99 в последнем фрагменте будет наиболее совместимым (поэтому также даст вам ожидаемый 1999 год).

Вы не можете создать LocalDate поскольку String Вы не предоставляете информацию, необходимую для заполнения всех необходимых полей.

Вы можете создать Year хоть

Year parsed = Year.parse("99", TWO_YEAR_FORMATTER);
int year = parsed.getValue();

Вы можете разобрать год, используя formatter.parse(input, Year::from):

import java.time.*;
import java.time.format.*;
class Main {
  public static void main(String[] args) {
    DateTimeFormatter TWO_YEAR_FORMATTER = DateTimeFormatter.ofPattern("yy");
    Year year = TWO_YEAR_FORMATTER.parse("99", Year::from);
    System.out.println(year);
  }
}

Обратите внимание, что это будет выводить 2099, Для вывода 1999 года вы должны использовать специальный форматер, подобный тому, который предложил Оле В.В. в ответе.

Вы должны предоставить значения по умолчанию для DAY_OF_MONTH а также MONTH_OF_YEAR

DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
                .appendPattern("yy")
                .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                .toFormatter();

К тому же,

Java-8 использует диапазон 2000-2099 по умолчанию, а не SimpleDateFormat - диапазон от -80 до +20 лет относительно сегодняшнего дня.

Полный ответ

Поскольку вы анализируете только годы, для того, чтобы '99' было равно '1999', ваш код должен выглядеть следующим образом:

DateTimeFormatter TWO_YEAR_FORMATTER = new DateTimeFormatterBuilder()
                .appendPattern("")
                .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1)
                .parseDefaulting(ChronoField.DAY_OF_MONTH, 1)
                .appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2, LocalDate.now().minusYears(80))
                .toFormatter();
Другие вопросы по тегам