Разбор и форматирование LocalDate с ненужным временем и часовым поясом - PullRequest
0 голосов
/ 27 декабря 2018

Редактировать:

Я открыл ошибку, и она была подтверждена Oracle.Вы можете следить за разрешением здесь: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8216414


Я взаимодействую с хранилищем LDAP, в котором хранится дата рождения человека с указанием времени и часового пояса, например:

  • Если дата рождения - «27-12-2018», то строка LDAP - «20181227000000 + 0000».

Я не могу найти способ разобрать И отформатировать дату рождения , используя ту жеpattern .

Следующий код хорошо работает для форматирования, но не для разбора:

LocalDate date = LocalDate.of(2018, 12, 27);
String pattern = "yyyyMMdd'000000+0000'";
DateTimeFormatter birthdateFormat = DateTimeFormatter.ofPattern(pattern);

// Outputs correctly 20181227000000+0000
date.format(birthdateFormat);

// Throw a DatetimeParseException at index 0
date = LocalDate.parse("20181227000000+0000", birthdateFormat);

И следующий код хорошо работает для разбора, но не для форматирования

LocalDate date = LocalDate.of(2018, 12, 27);
String pattern = "yyyyMMddkkmmssxx";
DateTimeFormatter birthdateFormat = DateTimeFormatter.ofPattern(pattern);

// Throws a UnsupportedTemporalTypeException for ClockHourOfDay not supported
// Anyway I would have an unwanted string with non zero hour, minute, second, timezone
date.format(birthdateFormat);

// Parse correctly the date to 27-12-2018
date = LocalDate.parse("20181227000000+0000", birthdateFormat);

Какой шаблон может удовлетворить как синтаксический анализ, так и форматирование?

Я вынужден использовать 2 разных шаблона?

Я спрашиваю, поскольку шаблон настроен в файле свойств.Я хочу настроить 1 шаблон только в этом файле свойств.Я хотел бы вывести шаблон из-под контроля, поскольку LDAP не является частью моего проекта, это общий ресурс, и я не могу гарантировать, что формат не может измениться.

Ответы [ 3 ]

0 голосов
/ 27 декабря 2018

Поскольку ваша строка LDAP имеет зональный формат ...+0000, я бы предложил использовать ZonedDateTime или OffsetDateTime.

. Этот шаблон yyyyMMddHHmmssZZZ поможет при синтаксическом анализе и форматировании.

LocalDate date =  LocalDate.of(2018, 12, 27);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssZZZ");

Форматирование

  • Сначала преобразуйте LocalDate в ZonedDateTime / OffsetDateTime:

    ZonedDateTime zonedDateTime = date.atStartOfDay(ZoneOffset.UTC);
    // or
    OffsetDateTime offsetDateTime = date.atStartOfDay().atOffset(ZoneOffset.UTC);
    
  • Затем отформатируйте его:

    // Both output correctly 20181227000000+0000
    System.out.println(zonedDateTime.format(formatter));
    // or 
    System.out.println(offsetDateTime.format(formatter));
    

Синтаксический анализ

  • Первый анализ ZonedDateTime /OffsetDateTime:

    // Both parse correctly
    ZonedDateTime zonedDateTime = ZonedDateTime.parse("20181227000000+0000", formatter);
    // or
    OffsetDateTime offsetDateTime = OffsetDateTime.parse("20181227000000+0000", formatter);
    
  • Получив ZonedDateTime / OffsetDateTime, вы можете просто получить LocalDate следующим образом:

    LocalDate date = LocalDate.from(zonedDateTime);
    // or
    LocalDate date = LocalDate.from(offsetDateTime);
    

Обновление

Оба синтаксический анализ и форматирование могут быть упрощены до однострочных:

LocalDate date = LocalDate.from(formatter.parse(ldapString));

String ldapString = OffsetDateTime.of(date, LocalTime.MIN, ZoneOffset.UTC).format(formatter);

Если вы все еще не удовлетворены приведенным выше кодом, вы можете извлечь логику из служебных методов:

public LocalDate parseLocalDate(String ldapString) {
    return LocalDate.from(formatter.parse(ldapString));
}

public String formatLocalDate(LocalDate date) {
    return OffsetDateTime.of(date, LocalTime.MIN, ZoneOffset.UTC)
                         .format(formatter);
}
0 голосов
/ 27 декабря 2018

Я предлагаю:

    LocalDate date = LocalDate.of(2018, Month.DECEMBER, 27);
    String pattern = "yyyyMMddHHmmssxx";
    DateTimeFormatter birthdateFormat = DateTimeFormatter.ofPattern(pattern);

    // Outputs 20181227000000+0000
    String formatted = date.atStartOfDay(ZoneOffset.UTC).format(birthdateFormat);
    System.out.println(formatted);

    // Parses to 2018-12-27T00:00Z
    OffsetDateTime odt = OffsetDateTime.parse("20181227000000+0000", birthdateFormat);
    System.out.println(odt);
    // Validate
    if (! odt.toLocalTime().equals(LocalTime.MIN)) {
        System.out.println("Unexpected time of day: " + odt);
    }
    if (! odt.getOffset().equals(ZoneOffset.UTC)) {
        System.out.println("Unexpected time zone offset: " + odt);
    }
    // Converts to 2018-12-27
    date = odt.toLocalDate();
    System.out.println(date);

Строка LDAP представляет собой дату, время и смещение UTC.Хорошее решение состоит в том, чтобы уважать это и генерировать все это при форматировании (установка времени дня на 00:00 и смещение на 0) и синтаксический анализ всех их обратно (в лучшем случае также проверка их на предмет обнаружения, если возникнут какие-либо неожиданности).Преобразование между LocalDate и OffsetDateTime является простым, если вы знаете, как.

Редактировать 3: Позволяет настроить шаблон

… шаблоннастроен в файле свойств ... Я хочу настроить 1 шаблон только в этом файле свойств.

... У меня нет гарантии, что формат не может быть изменен.

Чтобы принять во внимание эту возможностьчто шаблон может когда-нибудь не содержать время суток и / или нет смещения UTC, используйте этот форматер в приведенном выше коде:

    DateTimeFormatter birthdateFormat = new DateTimeFormatterBuilder()
            .appendPattern(pattern)
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .toFormatter()
            .withZone(ZoneOffset.UTC);

Это определяет время по умолчанию дня (полночь) и смещение по умолчанию (0).Пока время и смещение определены в строке из LDAP, значения по умолчанию не используются.

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

Редактировать: Предотвращение преобразования типов

Я считаю вышеупомянутое хорошее решение.Однако, если вы настаиваете на том, чтобы избежать преобразования из LocalDate в ZonedDateTime, используя atStartOfDay, и из OffsetDateTime, используя toLocalDate, это возможно с помощью следующего хака:

    DateTimeFormatter birthdateFormat = new DateTimeFormatterBuilder()
            .appendValue(ChronoField.YEAR, 4, 4, SignStyle.NEVER)
            .appendValue(ChronoField.MONTH_OF_YEAR, 2, 2, SignStyle.NEVER)
            .appendValue(ChronoField.DAY_OF_MONTH, 2, 2, SignStyle.NEVER)
            .appendLiteral("000000+0000")
            .toFormatter();

    // Outputs 20181227000000+0000
    String formatted = date.format(birthdateFormat);
    System.out.println(formatted);

    // Parses into 2018-12-27
    date = LocalDate.parse("20181227000000+0000", birthdateFormat);
    System.out.println(date);

Яуказание точной ширины каждого поля, чтобы форматировщик мог знать, где их разделять в строке при разборе.

Редактировать 2: это ошибка при разборе?

Я бы сразу ожидал, что yyyyMMdd'000000+0000' будет работать как для форматирования, так и для синтаксического анализа.Вы можете попробовать сообщить об ошибке в Oracle и посмотреть, что они говорят, хотя я бы не был слишком оптимистичен.

0 голосов
/ 27 декабря 2018

Глупое простое решение:

    String s1 = "20181227000000+0000";
    DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd");
    LocalDate date = LocalDate.parse(s1.substring(0, 8), yyyyMMdd);
    System.out.println("date = " + date);
    String s2 = date.format(yyyyMMdd) + "000000+0000";
    System.out.println("s2 = " + s2);
    System.out.println(s1.equals(s2));
...