Java英文日期格式解析

9

我的问题是这样的。我正在阅读一个文件,它包含一堆CSV行。每行都包含一些日期,格式为22-mar-2010或类似格式,即dd-MMM-yyyy格式。我想将其转换为ISO格式,因此变成2010-03-22

我手头有的代码如下:

  private String convertDate(String date) {
    DateTimeFormatter oldFormat = DateTimeFormatter.ofPattern("dd-MMM-yyyy", new Locale("en"));
    LocalDate parsedDate = LocalDate.parse(date, oldFormat);

    DateTimeFormatter newFormat = DateTimeFormatter.ISO_DATE;
    String newDate = parsedDate.format(newFormat);
    return newDate;
  }

输入看起来像这样:

sdfdsfslk 28-mar-2007 dfdsljs
sdfdsfslk 20-apr-2014 dfdsljs
sdfdsfslk 13-oct-2005 dfdsljs
sdfdsfslk 20-may-2014 dfdsljs
sdfdsfslk 20-jan-2014 dfdsljs
sdfdsfslk 20-feb-2014 dfdsljs

如果按照上述方式包含区域设置或使用 withLocale(Locale.ENGLISH),那么在第一行日期字符串处会出现错误。异常信息如下:
java.time.format.DateTimeParseException: Text '28-mar-2007' could not be parsed at index 3

如果我只去掉语言环境部分,只留下:

DateTimeFormatter.ofPattern("dd-MMM-yyyy");

然后它能正常工作,直到遇到像 13-oct-2005 这样的日期。它不喜欢英文中的 'oct' 并在 LocalDate.parse 行失败。如果我将 oct 转换为 okt(瑞典语),则可以解析它。
我需要完全更改我的 Locale 吗?出了什么问题?如何使其解析英语月份的日期,即使我身在瑞典?

一个Locale总是会涉及其中。如果您没有明确指定Locale,那么您的JVM当前默认的Locale就会隐式地应用。因此,okt被成功解析,因为代表您的瑞典JVM当前默认的Locale已被应用。 - Basil Bourque
是的,但问题在于我提供了一个Locale,但它没有批准小写月份。因此将其转换为大写的Mar。 - Souciance Eqdam Rashti
不,问题实际上是输入日期使用了(a)错误的英文月份名称和(b)不合适的日期格式。作为解决方法,请参见我的答案,以创建一个不区分大小写的解析器来适应错误的英文。并且向源数据的程序员介绍ISO 8601。我上面的评论是为了解释为什么删除Locale后,瑞典文本可以成功运行,因为隐式的瑞典Locale期望小写月份名称,而英语则期望首字母大写。 - Basil Bourque
@BasilBourque,是的,输入日期是一个大问题,我没有想到英语区域设置在大小写敏感性方面与瑞典语不同。谢谢你下面的答案。我已经投票支持了它,因为这是一种非常好的方法,可以避免解决问题。不幸的是,后端系统已经有20年历史了,没有人接近那个系统。我们正在努力摆脱它 :) - Souciance Eqdam Rashti
3个回答

7
我认为问题出在月份的第一个字母是小写的。当你将代码运行于28-Mar-2007而不是28-mar-2007时,一切正常。

一种快速且简单的解决方案是:

private String convertDate(String mydate) {

        String date = mydate;
        String firstLetter = date.substring(0,4).toUpperCase();
        String restLetters = date.substring(4).toLowerCase();
        date = firstLetter+restLetters;

        DateTimeFormatter oldFormat = DateTimeFormatter.ofPattern("dd-MMM-yyyy", new Locale("en"));
    LocalDate parsedDate = LocalDate.parse(date, oldFormat);

    DateTimeFormatter newFormat = DateTimeFormatter.ISO_DATE;
    String newDate = parsedDate.format(newFormat);
   return newDate;
  }

正确答案。这是一个例子,说明我们应该始终使用标准的ISO 8601格式(如YYYY-MM-DD)将日期时间值持久化为文本。 - Basil Bourque
@Plirkee,确实有效了,正如你所说,可能是大小写问题导致的。不幸的是,它是一个遗留后端,很难改变内部格式。 - Souciance Eqdam Rashti

4

简而言之

LocalDate.parse ( 
    "13-oct-2005" , 
    new DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .appendPattern( "dd-MMM-uuuu" )
        .toFormatter( Locale.US ) 
)

详细信息

Plirkee的答案是正确的:英文语言环境期望缩写月份名称以大写字母开头(大写)。

DateTimeFormatterBuilder

考虑到这个错误的输入数据,更容易的解决方法是构建一个大小写不敏感的格式化程序。 DateTimeFormatterBuilder类使您能够构建更精细定制的格式化程序,而不仅仅是一个格式化代码字符串模式。

包括DateTimeFormatterDateTimeFormatterBuilder在内的java.time类是线程安全的。 因此,您可以保留一个实例以供重复使用。

构造者模式

如果不熟悉,请阅读构造者设计模式。与使用多个参数调用构造函数不同,构造一个Builder对象,并使用各种方法的调用链来满足您的需求。最后,请Builder实例化您真正想要的对象,例如本例中的DateTimeFormatter

.parseCaseInsensitive()

我们所需要的技巧是调用.parseCaseInsensitive()。 通过将其与省略此调用的已注释行进行交换,可以验证此调用是关键成分。

//  DateTimeFormatterBuilder fbuilder = new DateTimeFormatterBuilder ().appendPattern ( "dd-MMM-uuuu" );  // Case-sensitive by default.
DateTimeFormatterBuilder fbuilder = new DateTimeFormatterBuilder ().parseCaseInsensitive ().appendPattern ( "dd-MMM-uuuu" );  // Case-insensitive to handle improper English.

String input = "13-oct-2005"; // Incorrect English. Should be uppercase 'Oct'.
DateTimeFormatter f = fbuilder.toFormatter ( Locale.US );
LocalDate ld = LocalDate.parse ( input , f );

ld.toString() → 2005-10-13

ISO 8601

提示:在文本交换日期时间值时,请始终使用标准的ISO 8601格式,而不是自己设计的奇怪格式,例如问题中看到的那种。当解析/生成字符串时,java.time类默认使用这些标准格式。


感谢您提供使用DateTimeFormatterBuilder的提示。看起来非常方便。我在Java文档中看到示例是六月,但我没有想到它是区分大小写的。但是,使用.parseCaseInsensitive()解析它确实更整洁。 - Souciance Eqdam Rashti

1
private static String convertDate(String daterec) {
        String date = daterec;
        String firstLetter = date.substring(0,4).toUpperCase();
        String restLetters = date.substring(4).toLowerCase();
        date = firstLetter+restLetters;
        DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd-MMM-yyyy", new Locale("en"));
        LocalDate lds = LocalDate.parse((date), dTF);
    return lds.toString();
  }

输出与接受的答案相同:

2007-03-28
2014-04-20
2005-10-13
2007-03-28
2014-01-20
2014-02-20

如前所述,我们需要保持格式,尽管有可以避免的可忽略的两行代码,因为我们正在传递“dd-MMM-yyyy”,这也意味着ISO标准,我有点晚了,所以只发布少量代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接