多语言环境日期解析

时间:2015-12-29 15:16:10

标签: scala jodatime

我正在尝试编写一个类,它能够将多格式和多语言环境字符串解析为DateTime

multi-format表示日期可能是:dd/MM/yyyyMMM dd yyyy,...(最多10种格式)

multi-locale表示日期可能是:29 Dec 201529 Dez 2015dice 29 2015 ...(最多10个区域设置,例如en,{{1 }},grit

使用我写的答案Using Joda Date & Time API to parse multiple formats

jp

但它不起作用:

val locales = List(
  Locale.ENGLISH,
  Locale.GERMAN,
  ...
)

val patterns = List(
  "yyyy/MM/dd",
  "yyyy-MM-dd",
  "MMMM dd, yyyy",
  "dd MMMM yyyy",
  "dd MMM yyyy"
)

val parsers = patterns.flatMap(patt => locales.map(locale => DateTimeFormat.forPattern(patt).withLocale(locale).getParser)).toArray
val birthDateFormatter = new DateTimeFormatterBuilder().append(null, parsers).toFormatter

我发现所有birthDateFormatter.parseDateTime("29 Dec 2015") // ok birthDateFormatter.parseDateTime("29 Dez 2015") // exception below Invalid format: "29 Dez 2015" is malformed at "Dez 2015" java.lang.IllegalArgumentException: Invalid format: "29 Dez 2015" is malformed at "Dez 2015" 都失去了#34;附加到parsers: List[DateTimeParser]后的语言环境。 birthDateFormatter: DateTimeFormatter只有一个区域设置 - birthDateFormatter

我可以写:

en

并使用它:

val birthDateFormatter = locales.map(new DateTimeFormatterBuilder().append(null, parsers).toFormatter.withLocale(_))

但它会引发很多例外。太可怕了。

如何使用joda-time解析多格式和多语言环境字符串? 我怎么能以其他方式做到这一点?

2 个答案:

答案 0 :(得分:3)

调查很有意思。这是一个帮助我的测试套件(在Java中,但我希望你能得到这个想法):

birthDateFormatter.parseDateTime("29 Dec 2015")

首先,你的第一个例子

DateTimeFormat.forPattern("dd MMM yyyy").withLocale(locale).getParser()

仅仅因为您的计算机的默认语言环境是英语而通过。如果不同,这种情况也会失败。这就是我在使用英语语言环境的机器上运行时使用法语和德语的原因。就我而言,两个断言都失败了。

事实证明,语言环境不存储在解析器中,而只存储在格式化程序中。所以当你这样做时

// DateTimeFormatter#withLocale:
public DateTimeFormatter withLocale(Locale locale) {
    if (locale == getLocale() || (locale != null && locale.equals(getLocale()))) {
        return this;
    }
    // Notice how locale does not affect the parser
    return new DateTimeFormatter(iPrinter, iParser, locale,
            iOffsetParsed, iChrono, iZone, iPivotYear, iDefaultYear);
}

语言环境在格式化程序上设置,但在创建解析器时会丢失:

new DateTimeFormatterBuilder().append(null, parsers).toFormatter()

接下来,当您创建新的格式化程序

withLocale()

它是使用系统的默认语言环境创建的(除非用// DateTimeFormatter#parseDateTime public DateTime parseDateTime(String text) { InternalParser parser = requireParser(); Chronology chrono = selectChronology(null); // Notice how the formatter's locale is used DateTimeParserBucket bucket = new DateTimeParserBucket(0, chrono, iLocale, iPivotYear, iDefaultYear); int newPos = parser.parseInto(bucket, text, 0); // ... snipped } 覆盖它)。在解析过程中使用该语言环境:

{{1}}

事实证明,尽管您可以使用多个解析器来支持多种格式,但每个格式化程序实例仍然只能使用一个语言环境。

答案 1 :(得分:1)

回答问题1(如何使用joda-time解析多格式和多语言环境的字符串?):

不,这不可能以你想要的方式,请参阅@Adam Michalik的好答案。因此,唯一的方法就是编写一个包含多个Joda格式化程序的列表,并针对给定的输入尝试每个格式化程序 - 可能会捕获异常。您已找到正确的解决方法,因此我不会在此处描述详细信息。

回答问题2(我怎么能以其他方式做到?):

我的图书馆Time4J从v4.11开始有一个新的MultiFormatParser - 类。但是,我发现它的格式引擎存在一些性能问题(主要是由于Java的自动装箱功能)所以我决定等到这个答案,直到发布v4.12,我已经改进了性能。 根据我的第一个基准测试,Time4J-4.12似乎比Joda-Time(v2.9.1)更快,因为内部异常大大减少了。因此,我认为您可以尝试使用最新版本的Time4J,然后报告一些反馈,如果它适合您。

private static final MultiFormatParser<PlainDate> TIME4J;

static {
    ChronoFormatter<PlainDate> f1 = 
      ChronoFormatter.ofDatePattern("dd.MM.uuuu", PatternType.CLDR, Locale.ROOT);
    ChronoFormatter<PlainDate> f2 = 
      ChronoFormatter.ofDatePattern("MM/dd/uuuu", PatternType.CLDR, Locale.ROOT);
    ChronoFormatter<PlainDate> f3 = 
      ChronoFormatter.ofDatePattern("uuuu-MM-dd", PatternType.CLDR, Locale.ROOT);
    ChronoFormatter<PlainDate> f4 = 
      ChronoFormatter.ofDatePattern("uuuuMMdd", PatternType.CLDR, Locale.ROOT);
    ChronoFormatter<PlainDate> f5 = 
      ChronoFormatter.ofDatePattern("d. MMMM uuuu", PatternType.CLDR, Locale.GERMAN);
    ChronoFormatter<PlainDate> f6 = 
      ChronoFormatter.ofDatePattern("d. MMMM uuuu", PatternType.CLDR, Locale.FRENCH);
    ChronoFormatter<PlainDate> f7 = 
      ChronoFormatter.ofDatePattern("MMMM d, uuuu", PatternType.CLDR, Locale.US);
    TIME4J = MultiFormatParser.of(f1, f2, f3, f4, f5, f6, f7);
}

...

static List<PlainDate> parse(List<String> input) {
    ParseLog plog = new ParseLog();
    int n = input.size();
    List<PlainDate> result = new ArrayList<>(n);

    for (int i = 0; i < n; i++){
        String s = input.get(i);
        plog.reset();
        PlainDate date = TIME4J.parse(s, plog);
        if (!plog.isError()) {
            result.add(date);
        } else {
            // log or report error
        }
    }
    return result;
}
  • MultiFormatParser中的每个解析器都保留自己的区域设置。
  • 解析器组件的顺序在性能方面很重要。优先选择输入中最常见的第一个位置的模式和区域设置。
  • 我强烈建议对MultiFormatParser使用静态常量,因为a)它是不可变的,b)构建格式化程序在每个库中都很昂贵(而且Time4J对此细节也不例外) )。
  • 对于与Joda-Time的互操作性,您可以考虑进行此转换:LocalDate joda = new LocalDate(plainDate.getYear(), plainDate.getMonth(), plainDate.getDayOfMonth());但请注意,每次转化都会产生一些额外费用。另一方面,Joda-Time提供的功能少于Time4J,因此后者可以完成所有日期 - 时区相关任务的全部工作。
  • 我不是scala人但是假设跟随scala代码可能会编译: val parser = MultiFormatParser.of(patterns.flatMap(patt => locales.map(locale => ChronoFormatter.ofDatePattern(patt, PatternType.CLDR, locale))).toArray)
  • 顺便说一下:Joda-Time的表现并不是那么糟糕,因为在Time4J-v4.12中让它变得更好是一项艰巨的任务。解析如此不同的模式和语言环境始终是一项复杂的任务。让我感到惊讶的是:根据我自己的实验(显然是由于内部异常处理),使用Java-8(包java.time)构建的新时间库在性能方面是最差的。
  • 如果您不在Java-8平台上工作,那么您可以使用Time4J-v3.15(backport到Java-6平台)。