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要解析这两种格式 (yyyy-MM-dd和yyyy/MM/dd),您可以使用DateTimeFormatter带有可选模式的 a (由 分隔[])并解析为 a LocalDate(因为您只有日期字段):
// 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);\nRun 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");\nRun Code Online (Sandbox Code Playgroud)\n\n然后你可以设置构建的时间LocalDateTime:
// set time to midnight\nLocalDateTime ldt = dt.atStartOfDay();\n\n// set time to 2:30 PM\nLocalDateTime ldt = dt.atTime(14, 30);\nRun Code Online (Sandbox Code Playgroud)\n\n或者,您也可以使用parseDefaulting,正如@greg\'s 答案中已经解释的那样:
// 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);\nRun Code Online (Sandbox Code Playgroud)\n\n请注意,我必须将小时和分钟设置为零。您还可以将秒 ( ChronoField.SECOND_OF_MINUTE) 和纳秒 ( ChronoField.NANO_OF_SECOND) 设置为零,但设置小时和分钟足以将所有其他字段设置为零。
您告诉您要使用系统的默认偏移量。这有点棘手。
\n\n“系统的默认偏移量”将取决于系统的默认时区。一个时区可以有多个偏移量,具体取决于您处于时间轴中的时间。
\n\n我将使用系统的默认时区 ( America/Sao_Paulo) 作为示例。在下面的代码中,我使用的是ZoneId.systemDefault(),但请记住,这在每个系统/环境中都会有所不同。对于下面的所有示例,请记住ZoneId.systemDefault()返回America/Sao_Paulo时区。如果你想得到一个特定的,你应该使用ZoneId.of("zone_name")- 实际上这是首选。
LocalDateTime首先,您必须获取, 在指定时区的有效偏移量列表:
// 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);\nRun Code Online (Sandbox Code Playgroud)\n\n根据javadoc,validOffsets对于任何给定的本地日期时间,列表大小可以是零、一或二。
对于大多数情况,只有一个有效的偏移量。在这种情况下,可以直接得到OffsetDateTime:
// most common case: just one valid offset\nOffsetDateTime odt = ldt.atOffset(validOffsets.get(0));\nRun 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。
因此,在 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\nRun Code Online (Sandbox Code Playgroud)\n\n没有有效的偏移量,因此您必须决定在这种情况下该怎么做(使用“默认”偏移量?抛出异常?)。
\n即使您的时区今天没有夏令时,它也可能在过去有(并且过去的日期可能是这种情况),或者将来也可能有(因为任何国家/地区的夏令时和偏移量)由政府和法律定义,并且不能保证将来没有人会改变)。
但是,如果您创建一个ZonedDateTime,结果会有所不同:
// October 15th 2017 at midnight, DST starts in Sao Paulo\nLocalDateTime ldt = LocalDateTime.parse("2017-10-15", parser);\nZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());\nRun Code Online (Sandbox Code Playgroud)\n\n变量zdt将是2017-10-15T01:00-02:00[America/Sao_Paulo]- 时间和偏移量将自动调整为偏移量处的凌晨 1 点-02:00。
并且存在两个有效偏移的情况。在圣保罗,夏令时将于 2018 年 2 月 18 日结束:午夜时分,时钟将向后移动 1 小时至 17日晚上 11 点,偏移量从-02:00变为-03:00。这意味着 23:00 到 23:59 之间的所有本地时间在两个偏移量中都将存在两次。
由于我将默认时间设置为午夜,因此只有一个有效的偏移量。但假设我决定使用默认时间 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\nRun Code Online (Sandbox Code Playgroud)\n\n将会有 2 个有效的偏移量LocalDateTime。在这种情况下,您必须选择其中之一:
// 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));\nRun Code Online (Sandbox Code Playgroud)\n\n但是,如果您创建一个ZonedDateTime,它将使用第一个偏移量作为默认值:
// 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());\nRun Code Online (Sandbox Code Playgroud)\n\nzdt将2018-02-17T23:00-02:00[America/Sao_Paulo]- 请注意,默认情况下它使用 DST 偏移量 ( -02:00)。
如果您想要 DST 结束后的偏移量,您可以执行以下操作:
\n\n// get offset after DST ends\nZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault()).withLaterOffsetAtOverlap();\nRun Code Online (Sandbox Code Playgroud)\n\nzdt将2018-02-17T23:00-03:00[America/Sao_Paulo]- 它使用偏移量-03:00(DST 结束后)。
只是提醒一下,系统的默认时区甚至可以在运行时更改,并且最好使用特定的时区名称(例如ZoneId.of("America/Sao_Paulo"))。您可以通过调用 获取可用时区列表(并选择最适合您的系统的时区)ZoneId.getAvailableZoneIds()。