【问题标题】:Java 8 Vavr onFailure concatenationJava 8 Vavr onFailure 连接
【发布时间】:2020-08-12 08:33:25
【问题描述】:

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

【问题讨论】:

  • 为什么不直接使用行得通的格式化程序?

标签: datetime java-8 java-time datetime-parsing vavr


【解决方案1】:

用 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. 通过从每个Try 创建一个迭代器,将Iterator&lt;Try&lt;OffsetDateTime&gt;&gt; 扁平化为Iterator&lt;OffsetDateTime&gt;。 try 上的迭代器如果成功则为单元素迭代器,如果失败则为空迭代器
  6. 获取结果迭代器的第一个元素,如果它不为空,则将其作为Some 返回,或者返回None

上面的管道是惰性的,也就是说,它只尝试尽可能多的模式/格式化程序来找到第一个成功的,因为 vavr Iterator 本身就是惰性的。

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

【讨论】:

    【解决方案2】:

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

    最后你会得到一个Optional&lt;OffsetDateTime&gt;。如果一切都失败了,它将是一个None,或者如果其中一个成功了,它将是一个Some。 Java Stream 是惰性的,因此一旦找到一个匹配项,它将停止执行其余的匹配项。

    如果你也想打印出所有失败的案例,你可以在filter前面加上onFailure。


    编辑:为特殊格式添加大小写

    【讨论】:

      【解决方案3】:

      我不知道 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 将您的格式解析为默认格式这一事实,没有任何明确的格式化程序。单参数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
      

      当格​​式化程序单独运行时,我不知道为什么这不起作用。我在想这可能是我不理解的特定于 Vavr 的东西。仍然让我感到疑惑。在任何情况下,您的字符串都应该由前面的条目之一处理,所以它可能无关紧要。

      链接

      【讨论】:

        猜你喜欢
        • 2019-02-07
        • 1970-01-01
        • 2020-08-02
        • 2014-05-18
        • 1970-01-01
        • 2015-03-13
        • 2020-01-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多