DateTimeFormatter 和 ZonedDateTime - 在不明确和不存在的日期时间 (DST) 上抛出异常

DateTimeFormatter and ZonedDateTime - Throw exception on ambiguous and nonexistent datetimes (DST)

我正在尝试创建一个非常严格的 DateTimeFormatter/ZonedDateTime 工厂。如果 datetetime 没有意义,那么它应该抛出异常 - 没有调整。示例:如果在 DST 期间跳过一个小时。

目前这些测试失败,没有抛出异常。 ZonedDateTime guesses/adjusts 基于文档中描述的规则。

    @Test
    fun `Ambiguous hour during  +0200 to +0100 DST change`() {
        val input = "2019-10-27T02:30:00Europe/Prague"
        val formatter = createFormatter()

        Assertions.assertThrows(DateTimeParseException::class.java) {
            ZonedDateTime.from(formatter.parse(input))
            // Gets instead adjusted as 2019-10-27T02:30:00Europe/Prague+02:00

        }
    }

    @Test
    fun `Skipped hour during +0100 to +0200 DST change`() {
        val formatter = createFormatter()

        Assertions.assertThrows(DateTimeParseException::class.java) {
            ZonedDateTime.from(formatter.parse("2019-03-31T02:30:00Europe/Prague"))
            // Gets instead adjusted as 2019-03-31T03:30:00Europe/Prague+02:00
        }
    }


    @Test
    fun `Test impossible offset during DST change`() {
        val input = "2019-10-27T02:30:00Europe/Prague+05:00"
        val formatter = createFormatter()

        Assertions.assertThrows(DateTimeParseException::class.java) {
            ZonedDateTime.from(formatter.parse(input))
            // Gets instead  adjusted as 2019-10-26T23:30:00Europe/Prague+02:00
        }
    }

Europe/Prague 区域的更改来自 here

我有代码:

fun createFormatter(): DateTimeFormatter {
    return DateTimeFormatterBuilder()
        .parseCaseSensitive()
        .parseStrict()
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        .appendZoneRegionId()
        .optionalStart()
        .appendOffsetId()
        .optionalEnd()
        .toFormatter()
        .withResolverStyle(ResolverStyle.STRICT)
}

ZonedDateTime 按照文档中的描述进行调整。我想将此行为更改为在不完全清楚的情况下随时抛出。

您需要添加额外的逻辑来验证结果。

以下代码在 Java 中,但您可以轻松转换为 Kotlin。

static ZonedDateTime parseVeryStrict(String text) {
    TemporalAccessor parsed = createFormatter().parse(text);
    ZonedDateTime zonedDateTime = ZonedDateTime.from(parsed);

    if (parsed.isSupported(OFFSET_SECONDS)) {
        // Verify given offset was correct
        ZoneOffset zoneOffset = ZoneOffset.from(parsed);
        if (! zoneOffset.equals(zonedDateTime.getOffset()))
            throw new DateTimeParseException("Incorrect offset: '" + text + "'", text, 0);
    } else {
        // Without offset, fail if in DST overlap time range
        if (! zonedDateTime.withEarlierOffsetAtOverlap().isEqual(zonedDateTime.withLaterOffsetAtOverlap()))
            throw new DateTimeParseException("Ambiguous time (DST overlap): '" + text + "'", text, 0);
    }

    // Verify time wasn't adjusted because it was in DST gap time range
    LocalTime localTime = LocalTime.from(parsed);
    if (! localTime.equals(zonedDateTime.toLocalTime()))
        throw new DateTimeParseException("Invalid time (DST gap): '" + text + "'", text, 0);

    return zonedDateTime;
}

测试

public static void main(String[] args) {
    test("2019-10-27T02:30:00");
    test("2019-10-27T02:30:00Europe/Prague");
    test("2019-03-31T02:30:00Europe/Prague");
    test("2019-10-27T02:30:00Europe/Prague+05:00");
    test("2019-10-27T02:30:00Europe/Prague+02:00");
    test("2019-10-27T02:30:00Europe/Prague+01:00");
}

static void test(String text) {
    try {
        System.out.println(text + " -> " + parseVeryStrict(text));
    } catch (DateTimeParseException e) {
        System.out.println(text + " - " + e.getMessage());
    }
}

输出

2019-10-27T02:30:00 - Text '2019-10-27T02:30:00' could not be parsed at index 19
2019-10-27T02:30:00Europe/Prague - Ambiguous time (DST overlap): '2019-10-27T02:30:00Europe/Prague'
2019-03-31T02:30:00Europe/Prague - Invalid time (DST gap): '2019-03-31T02:30:00Europe/Prague'
2019-10-27T02:30:00Europe/Prague+05:00 - Incorrect offset: '2019-10-27T02:30:00Europe/Prague+05:00'
2019-10-27T02:30:00Europe/Prague+02:00 -> 2019-10-27T02:30+02:00[Europe/Prague]
2019-10-27T02:30:00Europe/Prague+01:00 -> 2019-10-27T02:30+01:00[Europe/Prague]