Java 8 Vavr onFailure 串联

Java 8 Vavr onFailure concatenation

我试图解析一个包含未知格式日期的字符串,我选择的方法(不是最好的)是尝试所有可能的格式,直到正确解析。为此,我使用 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 多个操作,当一个操作失败时尝试下一个操作等等? 谢谢

我不认识 Vavr。我的回答分为两部分:

  1. 如何在没有 Vavr 的情况下解析字符串。
  2. 如何修复您的 Vavr 解决方案以使其正常工作。

我认为你不需要 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 是与 UTC 的偏移量,而不是一天中的时间,因此 +HH:mm 不起作用。另外 + 是一个符号,偏移量也可以是负数,比如 -04:00。又一次默认格式救了我们,OffsetDateTime 解析像上面提到的那样没有任何显式格式化程序的字符串:

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

默认格式为 ISO 8601。Link 在底部。

        .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

我不知道为什么当格式化程序孤立地工作时这不起作用。我在想,这可能是我不理解的 Vavr 特有的东西。仍然让我想知道。在任何情况下,您的字符串都应该由之前的条目之一处理过,所以它可能无关紧要。

Link

将格式化程序放入 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 添加到其余的 Formatters。

最后你会得到一个Optional<OffsetDateTime>。如果一切都失败了,它将是 None,如果其中一个成功,它将是 Some。 Java 流是惰性的,因此一旦找到一个匹配项,它将停止执行其余部分。

如果你也想把所有失败的case都打印出来,可以在filter前加上onFailure。


编辑:为特殊 FORMATTER 添加大小写

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. 从对模式数组
  2. 的惰性 Iterator 开始
  3. 转换为格式化程序
  4. 将回退格式化程序附加到 Iterator
  5. 使用格式化程序解析日期,将结果包装在 Try
  6. 通过从每个 Try 创建一个迭代器,将 Iterator<Try<OffsetDateTime>> 展平为 Iterator<OffsetDateTime>。 try 中的迭代器如果成功则为单元素迭代器,如果失败则为空迭代器
  7. 获取结果迭代器的第一个元素,如果它不为空,则 return 作为 Some 或 return None

上面的管道是惰性的,也就是说,它只尝试尽可能多的 patterns/formatters 以找到第一个成功的,因为 vavr Iterator 本身就是惰性的。

我的回答只关注如何使用 vavr 惰性评估直到第一次成功,我没有尝试更正您问题的其他方面,这些方面会导致您的模式不匹配显然符合其中某些模式的日期字符串。您问题的其他答案详细介绍了我不想在这里重复的内容。