Java 8 Vavr onFailure сцепление - PullRequest
       52

Java 8 Vavr onFailure сцепление

1 голос
/ 28 апреля 2020

Я пытаюсь разобрать строку, содержащую дату, в неизвестном формате, и я выбираю (не самый лучший) способ попробовать все возможные форматы до правильного разбора. Для этого я использую библиотеку 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]

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

Ответы [ 3 ]

2 голосов
/ 28 апреля 2020

Поместите ваш форматер в 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 является ленивым, поэтому, как только найдено одно совпадение, он остановит выполнение остальных.

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


Редактировать: добавление кейса для специального FORMATTER

2 голосов
/ 29 апреля 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, если он не пустой или возвратите a None

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

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

1 голос
/ 28 апреля 2020

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

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

I не думаю, что вам нужно 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 анализирует ваш формат по умолчанию без какого-либо явного средства форматирования. Метод one-arg 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 в конце - это смещение от UT C, а не время дня, поэтому +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 для UT C, например 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 доля секунды является необязательной, поэтому one-arg LocalDateTime.parse() принимает строки как с, так и без. Оставьте эту часть.

Наконец:

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

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

Ссылка

...