如何在Java 8中使用一个DateTimeFormatter处理所有时区偏移量

3
我需要创建一个DateTimeFormatter,用于以下有效日期。
  String date1 = "2017-06-20T17:25:28";
  String date2 = "2017-06-20T17:25:28.477777";
  String date3 = "2017-06-20T17:25:28.477777Z";
  String date4 = "2017-06-20T17:25:28.477777UTC";
  String date5 = "2017-06-20T17:25:28.477777-05";
  String date6 = "2017-06-20T17:25:28.477777+05";
  String date7 = "2017-06-20T17:25:28.477777+05:30";
  String date8 = "2017-06-20T17:25:28.477777-05:30";
  String date9 = "2017-06-20T17:25:28.477777+0530";
  String date10 = "2017-06-20T17:25:28.477777-0530";

我尝试了以下日期时间格式化程序,但对于最后两个日期(date9date10)失败了。

private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
    .appendPattern("yyyy-MM-dd'T'HH:mm:ss")
    .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
                        .optionalStart().appendZoneId().optionalEnd()
                        .optionalStart().appendOffset("+HH", "+00").optionalEnd()
                        .optionalStart().appendOffset("+HH:mm", "+00:00").optionalEnd()
                        .optionalStart().appendOffset("+HHmm", "+0000").optionalEnd().toFormatter();

所有从date1date8的日期都可以正常工作,但是在尝试解析最后两个日期时出现了DateTimeParseException

Exception in thread "main" java.time.format.DateTimeParseException: Text '2017-06-20T17:25:28.477777+0530' could not be parsed, unparsed text found at index 29

我正在使用以下代码解析日期。

LocalDateTime.parse(date1, DATE_TIME_FORMATTER);

OffsetIdPrinterParser 的有效偏移量模式:

static final class OffsetIdPrinterParser implements DateTimePrinterParser {
        static final String[] PATTERNS = new String[] {
            "+HH", "+HHmm", "+HH:mm", "+HHMM", "+HH:MM", "+HHMMss", "+HH:MM:ss", "+HHMMSS", "+HH:MM:SS",
        };  // order used in pattern builder

我无法理解为什么在使用有效的ZoneOffset模式时,我的最后两个日期失败了。

2个回答

5

只需反转您的可选部分的顺序:

private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
        .appendPattern("yyyy-MM-dd'T'HH:mm:ss")
        .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
        .optionalStart().appendZoneId().optionalEnd()
        .optionalStart().appendOffset("+HHmm", "+0000").optionalEnd()
        .optionalStart().appendOffset("+HH:mm", "+00:00").optionalEnd()
        .optionalStart().appendOffset("+HH", "+00").optionalEnd()
        .toFormatter();

这将解析您的10个样本日期时间字符串。

我不太确定为什么它有效。我猜想现在它会先尝试+HHmm而不是+HH,这可以确保在有四个数字时获取所有四个数字,而不是留下最后两个未解析。


@Eugene 但是我们仍然需要找出Java为什么这样做的原因。 - Amit Garg
1
顺序上有一些逻辑性。正则表达式不能直接比较。尽管如此,"cats".replaceFirst("cat|cats", "bird")会得到birds,而相反的顺序"cats".replaceFirst("cats|cat", "bird")只会得到bird。日期时间格式化程序甚至没有提供正则表达式中的OR,只是一系列可选部分的序列,因此我认为没有替代方案,只能按顺序尝试它们。 - Ole V.V.

4

另一个选择是使用可选部分,用[]来界定,并使用相应的偏移模式VVx):

DATE_TIME_FORMATTER = DateTimeFormatter
                         // pattern with optional sections: fraction of seconds and offsets
                         .ofPattern("yyyy-MM-dd'T'HH:mm:ss[.SSSSSS][VV][x][xx][xxx]");

每一对方括号[]等效于一个optionalStartoptionalEnd区域。请注意,我还必须将大写字母S(秒的分数)作为可选项包含在内,以解析不存在此字段的情况。
其他模式(VVx)对应于您需要的各种偏移量。从javadoc中得知:
Pattern  Count  Equivalent builder methods
-------  -----  --------------------------
  VV      2      appendZoneId()
  x       1      appendOffset("+HHmm","+00")
  xx      2      appendOffset("+HHMM","+0000")
  xxx     3      appendOffset("+HH:MM","+00:00")

这适用于您所有的输入日期。


唯一的区别在于[.SSSSSS]仅接受毫秒字段中的6位数字(或零位数字,因为它是一个可选部分),而appendFraction接受0到6位任意数量。要获得完全相同的行为,您必须使用DateTimeFormatterBuilder

DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
    // date and time
    .appendPattern("yyyy-MM-dd'T'HH:mm:ss")
    // fraction of seconds, from 0 to 6 digits
    .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true)
    // optional offset patterns
    .appendPattern("[VV][x][xx][xxx]")
    .toFormatter();

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