如何解析 Java 8 中不包含标点符号的 ISO-8601 格式字符串?

How do I parse an ISO-8601 formatted string that contains no punctuation in Java 8?

如何将以下字符串解析为 LocalDateTime-Object?

20200203092315000000

我总是得到以下异常,但我不明白:

java.time.format.DateTimeParseException: Text '20200203092315000000' could not be parsed at index 0

    at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
    at java.time.LocalDateTime.parse(LocalDateTime.java:492)
    at de.x.struct.type.LocalDateTimeStructField.setBytesValue(LocalDateTimeStructField.java:44)
    at de.x.struct.Struct.bytesToStruct(Struct.java:110)
    at de.x.struct.StructTest.testStringToStruct(StructTest.java:60)

我的应用程序代码如下所示:

LocalDateTime ldt = LocalDateTime.parse("20200203092315000000", DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSSSSS"));

您的输入无效且超出范围。

当您输入正确时使用此代码。

Calendar cal = Calendar.getInstance();
        cal.setTimeInMillis(your input * 1000);
        System.out.println(cal.getTime());

看起来像是一个已知问题...

bug_id=JDK-8031085 bug_id=JDK-8138676

Workaround:

DateTimeFormatter dtf = new DateTimeFormatterBuilder().appendPattern("yyyyMMddHHmmss").appendValue(ChronoField.MILLI_OF_SECOND, 3).toFormatter()

CUSTOMER SUBMITTED WORKAROUND : use the following format (mind the '.'): "yyyyMMddHHmmss.SSS"

LocalDateTime.parse("20150910121314987", DateTimeFormatter.ofPattern("yyyyMMddHHmmss.SSS"))

or alternatively use jodatime library

我已经使用以下代码将近三年了,它对我很有帮助。

ISO 8601 允许两种格式:基本格式和扩展格式。基本格式不包含任何标点符号,而扩展格式包含。例如,扩展格式的 2017-12-10T09:47:00-04:00 等同于基本格式的 20171210T094700-0400。

由于 Java 8 中的行为是仅接受扩展格式,因此我通常使用接受这两种格式的新 DateTimeFormatter。

此格式不能准确表示 ISO 8601 的要求。规则是时间戳的文本表示要么是完全基本的(即没有标点符号)要么是完全扩展的(即所有标点符号都存在)。仅使用一些标点符号是无效的,但我发现某些图书馆不遵守此规则。具体来说,Gson 生成时区说明符中缺少冒号的时间戳。因为我希望在我接受的东西上自由,所以我完全忽略所有标点符号。

Java 8 也无法准确处理年份,尤其是在缺少标点符号的情况下。 ISO 8601 要求年份中正好有四位数字,但如果双方就明确的数字位数达成一致,则允许额外的数字。在这种情况下,年份之前必须有一个符号。但是,Java 8 不强制要求有符号并接受四到九位数字。虽然这种方法更自由,并且与我通常尝试完成的目标保持一致,但由于我不知道应该出现多少位数字,因此无法解析一年。在这种情况下,我收紧了格式以符合 ISO 8601,尽管如果需要可以使用替代方法。例如,您可以预先解析文本,了解您期望的位数,并在位数过多时添加符号。

我建议只在解析时使用此格式化程序,而不是在序列化时使用,因为我更喜欢严格控制我生成的内容。

下面的代码应该接受您的格式,有或没有时区偏移。它只与您所拥有的有所不同,因为它接受九位小数秒而不是六位。您可以根据需要进行调整。

// somewhere above
import java.time.format.SignStyle;
import static java.time.temporal.ChronoField.*;

static DateTimeFormatter ISO_8601_LENIENT_FORMAT = new DateTimeFormatterBuilder()
    .parseCaseInsensitive()
    .appendValue(YEAR, 4, 4, SignStyle.EXCEEDS_PAD)
    .optionalStart().appendLiteral('-').optionalEnd()   // Basic format has no punctuation
    .appendValue(MONTH_OF_YEAR, 2)
    .optionalStart().appendLiteral('-').optionalEnd()
    .appendValue(DAY_OF_MONTH, 2)
    .optionalStart().appendLiteral('T').optionalEnd()   // permitted to omit the 'T' character by mutual agreement
    .appendValue(HOUR_OF_DAY, 2)
    .optionalStart().appendLiteral(':').optionalEnd()
    .appendValue(MINUTE_OF_HOUR, 2)
    .optionalStart()                                    // seconds are optional
    .optionalStart().appendLiteral(':').optionalEnd()
        .appendValue(SECOND_OF_MINUTE, 2)
        .optionalStart().appendFraction(NANO_OF_SECOND, 0, 9, false).optionalEnd()
    .optionalEnd()
    .optionalStart().appendOffset("+HH:MM", "Z").optionalEnd()
    .optionalStart().appendOffset("+HHMM", "Z").optionalEnd()
    .optionalStart().appendOffset("+HH", "Z").optionalEnd()
    .toFormatter()
    ;

更严格的版本拒绝所有标点符号,假设年份为非负数,将小数秒限制为六位数,并省略时区信息。

static DateTimeFormatter ISO_8601_NO_PUNCTUATION = new DateTimeFormatterBuilder()
    .appendValue(YEAR, 4, 4, SignStyle.NEVER)
    .appendValue(MONTH_OF_YEAR, 2)
    .appendValue(DAY_OF_MONTH, 2)
    .appendValue(HOUR_OF_DAY, 2)
    .appendValue(MINUTE_OF_HOUR, 2)
    .appendValue(SECOND_OF_MINUTE, 2)
    .appendFraction(NANO_OF_SECOND, 6, 6, false)
    .toFormatter()
    ;