Конкатенация Java 8 Vavr onFailure

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

// My unknown date
    String date = "2020-11-12T15:15:15.345";


    // Date format that works for my unknown date (just for testing)
    DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
            .appendPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]")
            .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
            .toFormatter();
    OffsetDateTime value = OffsetDateTime.parse(date, FORMATTER);                   // PARSE CORRECTLY 


// Try all possible formats until one works
    Try<OffsetDateTime> myParsedDate = Try.of(()->date)
            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss.SSS"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss.SSSZ"))

            .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")))
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss"))

            .map(x->OffsetDateTime.parse(date, FORMATTER))                          // DOSENT WORK
            .onFailure(x->System.out.println("NO yyyy-MM-dd'T'HH:mm:ss[.SSS]"));
    if(myParsedDate.isSuccess()) {
        System.out.println("OK");
    }else {
        System.out.println("KO");
    }

Выход:

NO yyyy-MM-dd
NO yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm
NO yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm
NO yyyy-MM-dd'T'HH:mm:ss.SSS
NO yyyy-MM-dd'T'HH:mm:ss.SSSZ
NO yyyy-MM-dd'T'HH:mm:ss
NO yyyy-MM-dd'T'HH:mm:ss[.SSS]

Вопрос в том, как объединить множество попыток/поймать или, в этом случае, используя VAVR, множество действий, чтобы при сбое одного действия попробовать следующее и так далее? Спасибо


person Ares91    schedule 28.04.2020    source источник
comment
Почему бы вам просто не использовать форматтер, который работает?   -  person Ole V.V.    schedule 28.04.2020


Ответы (3)


Поместите свой форматтер в Java Stream и попробуйте каждый из них, пока один из них не увенчается успехом:

import io.vavr.control.Try;

import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Optional;
import java.util.stream.Stream;

public class Test {
    public static void main(String[] args) {
        String date = "2020-11-12T15:15:15.345";

        DateTimeFormatter FORMATTER = new DateTimeFormatterBuilder()
                .appendPattern("yyyy-MM-dd'T'HH:mm:ss[.SSS]")
                .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
                .toFormatter();
        OffsetDateTime value = OffsetDateTime.parse(date, FORMATTER);

        Optional<OffsetDateTime> res = Stream.concat(Stream.of(
                "yyyy-MM-dd",
                "yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm",
                "yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm",
                "yyyy-MM-dd'T'HH:mm:ss.SSS",
                "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
                "yyyy-MM-dd'T'HH:mm:ss")
                .map(p -> DateTimeFormatter.ofPattern(p)), Stream.of(FORMATTER))
                .map(fmt -> Try.of(() -> OffsetDateTime.parse(date, fmt)))
                .filter(Try::isSuccess)
                .map(Try::get)
                .findFirst();

        System.out.println(res);  //prints Optional[2020-11-12T15:15:15.345Z]
    }
}

Stream.concat используется для добавления FORMATTER с остальными форматтерами.

В итоге вы получите Optional<OffsetDateTime>. Это будет None, если все не удалось, или Some, если одно из них удалось. Java Stream ленив, поэтому, как только одно совпадение будет найдено, выполнение остальных будет остановлено.

Если вы хотите также распечатать все неудачные случаи, вы можете добавить onFailure перед filter.


Изменить: добавление случая для специального FORMATTER

person texasbruce    schedule 28.04.2020

Ответ с vavr

import io.vavr.collection.Iterator;

String[] patterns = new String[] {
   "yyyy-MM-dd",
   "yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm",
   "yyyy-MM-dd'T'HH:mm:ss.SSS",
   "yyyy-MM-dd'T'HH:mm:ss.SSSZ",
   "yyyy-MM-dd'T'HH:mm:ss"
};

final Option<OffsetDateTime> offsetDateTimeOption =
   Iterator.of(patterns)                                                     // 1
      .map(DateTimeFormatter::ofPattern)                                     // 2
      .concat(Iterator.of(FORMATTER))                                        // 3
      .map(formatter -> Try.of(() -> OffsetDateTime.parse(date, formatter))) // 4
      .flatMap(Try::iterator)                                                // 5
      .headOption();                                                         // 6

Шаги

  1. Начните с ленивого Iterator над массивом шаблонов
  2. Преобразовать в форматтеры
  3. Добавьте резервный модуль форматирования к Iterator
  4. Разобрать дату с помощью средства форматирования, обернув результат в Try
  5. Сгладить Iterator<Try<OffsetDateTime>> до Iterator<OffsetDateTime>, создав итератор из каждого Try. Итератор при попытке будет итератором с одним элементом, если он успешен, или пустым итератором, если это сбой.
  6. Возьмите первый элемент результирующего итератора и верните его как Some, если он не пуст, или верните None

Приведенный выше конвейер является ленивым, то есть он пробует столько шаблонов/модулей форматирования, сколько необходимо, чтобы найти первый успешный, потому что сам vavr Iterator ленив.

Мой ответ сосредоточен только на том, как выполнить ленивую оценку до первого успеха с vavr, я не пытался исправить другие аспекты вашего вопроса, которые привели бы к тому, что ваши шаблоны не соответствуют строкам даты, которые явно соответствуют к некоторым из этих шаблонов. Другие ответы на ваш вопрос содержат подробные сведения о том, что я не хочу повторять здесь.

person Nándor Előd Fekete    schedule 28.04.2020

Я не знаю Вавр. Мой ответ состоит из двух частей:

  1. Как разобрать вашу строку без Vavr.
  2. Как исправить решение Vavr, чтобы оно заработало.

Я не думаю, что тебе нужен Вавр

    String date = "2020-11-12T15:15:15.345";

    DateTimeFormatter flexibleFormatter = new DateTimeFormatterBuilder()
            .append(DateTimeFormatter.ISO_LOCAL_DATE)
            .optionalStart()
            .appendLiteral('T')
            .append(DateTimeFormatter.ISO_LOCAL_TIME)
            .optionalStart()
            .appendOffsetId()
            .optionalEnd()
            .optionalEnd()
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
            .toFormatter();

    OffsetDateTime value = OffsetDateTime.parse(date, flexibleFormatter);

    System.out.println(value);

Выход:

2020-11-12T15:15:15.345Z

Я попытался создать средство форматирования, которое обрабатывает все варианты формата, которые вы пытаетесь учесть в своей конструкции Vavr Try.

Если вы предпочитаете исправить свое решение Vavr

Есть некоторые проблемы с форматировщиками, которые вы пытаетесь использовать в конструкции Vavr. Давайте исправим их по очереди.

        .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd")))

Анализ с использованием формата yyyy-MM-dd не даст вам достаточно информации для OffsetDateTime. Вы получите дату, но ни время суток, ни смещение. Вместо этого я бы разобрал в LocalDate и впоследствии преобразовал:

        .map(x->LocalDate.parse(date).atStartOfDay(ZoneOffset.UTC).toOffsetDateTime())

Я использую тот факт, что LocalDate анализирует ваш формат по умолчанию без какого-либо явного форматирования. Метод с одним аргументом atStartOfDay дает нам ZonedDateTime, поэтому после этого нам нужен еще один шаг преобразования. Другим решением было бы настроить средство форматирования со временем суток по умолчанию и смещением по умолчанию. Это было бы похоже на то, что вы делаете в своем форматтере, который работает, только с двумя вызовами parseDefaulting().

Следующий выпуск:

        .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm")))

Кажется, вы пытались разобрать строку вроде 2020-11-12T15:15:15.34+01:00. +HH:mm не подходит для этого. HH для часа дня и mm для минуты часа. Но +01:00 в конце — это смещение от UTC, а не время суток, поэтому +HH:mm не сработает. Также + является знаком, смещение также могло быть отрицательным, например -04:00. Опять же, формат по умолчанию спасает нас, OffsetDateTime анализирует строки, подобные упомянутой, без какого-либо явного форматирования:

        .map(x->OffsetDateTime.parse(date))

Формат по умолчанию — ISO 8601. Ссылка внизу.

        .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm")))

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

        .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")))

Это должно было соответствовать вашей «неизвестной» строке. Вы анализируете дату и время, но отсутствует смещение. Мое решение состояло бы в том, чтобы разобрать LocalDateTime, а затем преобразовать. Опять же, формат ISO 8601 по умолчанию избавляет меня от создания форматтера.

        .map(x->LocalDateTime.parse(date).atOffset(ZoneOffset.UTC))

Следующий:

        .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")))

Хотя я не знаю, я подозреваю, что вы пытались сопоставить строку с конечным Z для UTC, например 2020-11-12T15:15:15.345Z. Используемый ранее OffsetDateTime.parse() с одним аргументом также принимает этот вариант, поэтому вы можете опустить эту часть. Кстати, одна буква шаблона Z предназначена для смещения без двоеточия, например +0000, и не может анализировать Z.

Следующий:

        .map(x->OffsetDateTime.parse(date, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")))

Снова нам не хватает смещения, и снова оно анализируется LocalDateTIme ранее. В ISO 8601 доля секунды не является обязательной, поэтому LocalDateTime.parse() с одним аргументом принимает строки с ним и без него. Опустите эту часть.

Окончательно:

        .map(x->OffsetDateTime.parse(date, FORMATTER))                          // DOSENT WORK

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

Связь

person Ole V.V.    schedule 28.04.2020