日期字符串到 OffsetDateTime

waz*_*zza 3 datetimeoffset datetime-parsing java-8 java-time

我正在将日期/日期时间字符串转换为OffsetDateTime并且我有日期时间格式,它可能具有这些值之一

yyyy-MM-dd, yyyy/MM/dd
Run Code Online (Sandbox Code Playgroud)

有时有或没有时间,我需要将其转换为OffsetDateTime.

我试过下面的代码

// for format yyyy-MM-dd
DateTimeFormatter DATE_FORMAT = new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd")
                        .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
                        .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
                        .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
                        .parseDefaulting(ChronoField.MILLI_OF_SECOND, 0)
                        .toFormatter();
Run Code Online (Sandbox Code Playgroud)

由于没有时间,我将其设置为默认值,但是当我尝试解析时

OffsetDateTime.parse("2016-06-06", DATE_FORMAT)
Run Code Online (Sandbox Code Playgroud)

它抛出错误就像

线程“main”中的异常 java.time.format.DateTimeParseException:无法解析文本“2016-06-06”:无法从 TemporalAccessor 获取 OffsetDateTime:{},ISO 解析为 java 类型的 2016-06-06T00:00 .time.format.Parsed 已解析

谁能帮我解决这个问题吗?

小智 5

要创建OffsetDateTime,您需要日期(日、月和年)、时间(小时、分钟、秒和纳秒)和偏移量(与UTC的差异)。

\n\n

您的输入只有日期,因此您必须构建其余部分,或者假设它们的默认值。

\n\n

要解析这两种格式 (yyyy-MM-ddyyyy/MM/dd),您可以使用DateTimeFormatter带有可选模式的 a (由 分隔[])并解析为 a LocalDate(因为您只有日期字段):

\n\n
// parse yyyy-MM-dd or yyyy/MM/dd\nDateTimeFormatter parser = DateTimeFormatter.ofPattern("[yyyy-MM-dd][yyyy/MM/dd]");\n\n// parse yyyy-MM-dd\nLocalDate dt = LocalDate.parse("2016-06-06", parser);\n\n// or parse yyyy/MM/dd\nLocalDate dt = LocalDate.parse("2016/06/06", parser);\n
Run Code Online (Sandbox Code Playgroud)\n\n

你也可以使用这个(稍微复杂一点,但它的工作原理是一样的):

\n\n
// year followed by - or /, followed by month, followed by - or /, followed by day\nDateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy[-][/]MM[-][/]dd");\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后你可以设置构建的时间LocalDateTime

\n\n
// set time to midnight\nLocalDateTime ldt = dt.atStartOfDay();\n\n// set time to 2:30 PM\nLocalDateTime ldt = dt.atTime(14, 30);\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

或者,您也可以使用parseDefaulting,正如@greg\'s 答案中已经解释的那样:

\n\n
// parse yyyy-MM-dd or yyyy/MM/dd\nDateTimeFormatter parser = new DateTimeFormatterBuilder().appendPattern("[yyyy-MM-dd][yyyy/MM/dd]")\n    // set hour to zero\n    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)\n    // set minute to zero\n    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)\n    // create formatter\n    .toFormatter();\n// parse the LocalDateTime, time will be set to 00:00\nLocalDateTime ldt = LocalDateTime.parse("2016-06-06", parser);\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,我必须将小时和分钟设置为零。您还可以将秒 ( ChronoField.SECOND_OF_MINUTE) 和纳秒 ( ChronoField.NANO_OF_SECOND) 设置为零,但设置小时和分钟足以将所有其他字段设置为零。

\n\n
\n\n

您告诉您要使用系统的默认偏移量。这有点棘手。

\n\n

“系统的默认偏移量”将取决于系统的默认时区。一个时区可以有多个偏移量,具体取决于您处于时间轴中的时间。

\n\n

我将使用系统的默认时区 ( America/Sao_Paulo) 作为示例。在下面的代码中,我使用的是ZoneId.systemDefault(),但请记住,这在每个系统/环境中都会有所不同。对于下面的所有示例,请记住ZoneId.systemDefault()返回America/Sao_Paulo时区。如果你想得到一个特定的,你应该使用ZoneId.of("zone_name")- 实际上这是首选

\n\n

LocalDateTime首先,您必须获取, 在指定时区的有效偏移量列表:

\n\n
// using the parser with parseDefaulting\nLocalDateTime ldt = LocalDateTime.parse("2016-06-06", parser);\n\n// get all valid offsets for the date/time, in the specified timezone\nList<ZoneOffset> validOffsets = ZoneId.systemDefault().getRules().getValidOffsets(ldt);\n
Run Code Online (Sandbox Code Playgroud)\n\n

根据javadocvalidOffsets对于任何给定的本地日期时间,列表大小可以是零、一或二。

\n\n

对于大多数情况,只有一个有效的偏移量。在这种情况下,可以直接得到OffsetDateTime

\n\n
// most common case: just one valid offset\nOffsetDateTime odt = ldt.atOffset(validOffsets.get(0));\n
Run Code Online (Sandbox Code Playgroud)\n\n

其他情况(零或两个有效偏移)通常是由于夏令时更改 (DST) 造成的。

\n\n

在 S\xc3\xa3o 保罗时区,夏令时将于 2017 年10月 15 日开始:午夜时分,时钟向前移动到凌晨 1 点,偏移量从-03:00变为-02:00。这意味着从 00:00 到 00:59 的所有当地时间都不存在 - 您也可以认为时钟从 23:59 直接更改为 01:00。

\n\n

因此,在 S\xc3\xa3o 保罗时区中,该日期将没有有效的偏移量:

\n\n
// October 15th 2017 at midnight, DST starts in Sao Paulo\nLocalDateTime ldt = LocalDateTime.parse("2017-10-15", parser);\n// system\'s default timezone is America/Sao_Paulo\nList<ZoneOffset> validOffsets = ZoneId.systemDefault().getRules().getValidOffsets(ldt);\nSystem.out.println(validOffsets.size()); // zero\n
Run Code Online (Sandbox Code Playgroud)\n\n

没有有效的偏移量,因此您必须决定在这种情况下该怎么做(使用“默认”偏移量?抛出异常?)。
\n即使您的时区今天没有夏令时,它也可能在过去有(并且过去的日期可能是这种情况),或者将来也可能有(因为任何国家/地区的夏令时和偏移量)由政府和法律定义,并且不能保证将来没有人会改变)。

\n\n

但是,如果您创建一个ZonedDateTime,结果会有所不同:

\n\n
// October 15th 2017 at midnight, DST starts in Sao Paulo\nLocalDateTime ldt = LocalDateTime.parse("2017-10-15", parser);\nZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());\n
Run Code Online (Sandbox Code Playgroud)\n\n

变量zdt将是2017-10-15T01:00-02:00[America/Sao_Paulo]- 时间和偏移量将自动调整为偏移量处的凌晨 1 点-02:00

\n\n
\n\n

并且存在两个有效偏移的情况。在圣保罗,夏令时将于 2018 年 2 月 18 日结束午夜时分,时钟将向后移动 1 小时至 17晚上 11 点,偏移量从-02:00变为-03:00。这意味着 23:00 到 23:59 之间的所有本地时间在两个偏移量中都将存在两次。

\n\n

由于我将默认时间设置为午夜,因此只有一个有效的偏移量。但假设我决定使用默认时间 23:00

\n\n
// parse yyyy-MM-dd or yyyy/MM/dd\nparser = new DateTimeFormatterBuilder().appendPattern("[yyyy-MM-dd][yyyy/MM/dd]")\n    // *** set hour to 11 PM ***\n    .parseDefaulting(ChronoField.HOUR_OF_DAY, 23)\n    // set minute to zero\n    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)\n    // create formatter\n    .toFormatter();\n\n// February 18th 2018 at midnight, DST ends in Sao Paulo\n// local times from 23:00 to 23:59 at 17th exist twice\nLocalDateTime ldt = LocalDateTime.parse("2018-02-17", parser);\n// system\'s default timezone is America/Sao_Paulo\nList<ZoneOffset> validOffsets = ZoneId.systemDefault().getRules().getValidOffsets(ldt);\nSystem.out.println(validOffsets.size()); // 2\n
Run Code Online (Sandbox Code Playgroud)\n\n

将会有 2 个有效的偏移量LocalDateTime。在这种情况下,您必须选择其中之一:

\n\n
// DST offset: 2018-02-17T23:00-02:00\nOffsetDateTime dst = ldt.atOffset(validOffsets.get(0));\n\n// non-DST offset: 2018-02-17T23:00-03:00\nOffsetDateTime nondst = ldt.atOffset(validOffsets.get(1));\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是,如果您创建一个ZonedDateTime,它将使用第一个偏移量作为默认值:

\n\n
// February 18th 2018 at midnight, DST ends in Sao Paulo\nLocalDateTime ldt = LocalDateTime.parse("2018-02-17", parser);\n// system\'s default timezone is America/Sao_Paulo\nList<ZoneOffset> validOffsets = ZoneId.systemDefault().getRules().getValidOffsets(ldt);\n\n// by default it uses DST offset\nZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());\n
Run Code Online (Sandbox Code Playgroud)\n\n

zdt2018-02-17T23:00-02:00[America/Sao_Paulo]- 请注意,默认情况下它使用 DST 偏移量 ( -02:00)。

\n\n

如果您想要 DST 结束后的偏移量,您可以执行以下操作:

\n\n
// get offset after DST ends\nZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault()).withLaterOffsetAtOverlap();\n
Run Code Online (Sandbox Code Playgroud)\n\n

zdt2018-02-17T23:00-03:00[America/Sao_Paulo]- 它使用偏移量-03:00(DST 结束后)。

\n\n
\n\n

只是提醒一下,系统的默认时区甚至可以在运行时更改,并且最好使用特定的时区名称(例如ZoneId.of("America/Sao_Paulo"))。您可以通过调用 获取可用时区列表(并选择最适合您的系统的时区)ZoneId.getAvailableZoneIds()

\n