我正在将日期/日期时间字符串转换为OffsetDateTime
,我的日期时间格式可能包含其中一个值
yyyy-MM-dd, yyyy/MM/dd
有时有无时间,我需要将其转换为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();
因为它没有时间我将其设置为默认值但是当我尝试解析时
OffsetDateTime.parse("2016-06-06", DATE_FORMAT)
像
那样抛出错误线程“main”中的异常java.time.format.DateTimeParseException:无法解析文本“2016-06-06”:无法从TemporalAccessor获取OffsetDateTime:{},ISO已解析为2016-06-06T00:00类型java.time.format.Parsed
任何人都可以帮我解决这个问题吗?
答案 0 :(得分:4)
要创建OffsetDateTime
,您需要日期(日,月和年),时间(小时,分钟,秒和纳秒)和offset(与UTC的差异)。
您的输入只有日期,因此您必须构建其余部分,或为其假设默认值。
要解析这两种格式(yyyy-MM-dd
和yyyy/MM/dd
),您可以使用带有可选模式的DateTimeFormatter
(由[]
分隔)并解析为LocalDate
(因为你只有日期字段):
// parse yyyy-MM-dd or yyyy/MM/dd
DateTimeFormatter parser = DateTimeFormatter.ofPattern("[yyyy-MM-dd][yyyy/MM/dd]");
// parse yyyy-MM-dd
LocalDate dt = LocalDate.parse("2016-06-06", parser);
// or parse yyyy/MM/dd
LocalDate dt = LocalDate.parse("2016/06/06", parser);
你也可以使用它(有点复杂,但它的工作方式相同):
// year followed by - or /, followed by month, followed by - or /, followed by day
DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy[-][/]MM[-][/]dd");
然后您可以设置构建LocalDateTime
的时间:
// set time to midnight
LocalDateTime ldt = dt.atStartOfDay();
// set time to 2:30 PM
LocalDateTime ldt = dt.atTime(14, 30);
或者,您也可以使用parseDefaulting
,如@greg's answer中所述:
// parse yyyy-MM-dd or yyyy/MM/dd
DateTimeFormatter parser = new DateTimeFormatterBuilder().appendPattern("[yyyy-MM-dd][yyyy/MM/dd]")
// set hour to zero
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
// set minute to zero
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
// create formatter
.toFormatter();
// parse the LocalDateTime, time will be set to 00:00
LocalDateTime ldt = LocalDateTime.parse("2016-06-06", parser);
请注意,我必须将小时和分钟设置为零。您还可以将秒(ChronoField.SECOND_OF_MINUTE
)和纳秒(ChronoField.NANO_OF_SECOND
)设置为零,但设置小时和分钟足以将所有其他字段设置为零。
您告诉过您要使用系统的默认偏移量。这有点棘手。
“系统的默认偏移量”取决于系统的默认时区。并且时区可以有多个偏移量,具体取决于您在时间轴中的 。
我将使用系统的默认时区(America/Sao_Paulo
)作为示例。在下面的代码中,我使用的是ZoneId.systemDefault()
,但请记住,每个系统/环境都会有所不同。 对于以下所有示例,请记住ZoneId.systemDefault()
会返回America/Sao_Paulo
时区。如果你想获得一个特定的,你应该使用ZoneId.of("zone_name")
- 实际上这是首选。
首先,您必须在指定的时区获取LocalDateTime
的有效抵消列表:
// using the parser with parseDefaulting
LocalDateTime ldt = LocalDateTime.parse("2016-06-06", parser);
// get all valid offsets for the date/time, in the specified timezone
List<ZoneOffset> validOffsets = ZoneId.systemDefault().getRules().getValidOffsets(ldt);
根据javadoc,对于任何给定的本地日期时间,validOffsets
列表大小可以是零,一个或两个。
对于大多数情况,只有一个有效的偏移量。在这种情况下,获得OffsetDateTime
:
// most common case: just one valid offset
OffsetDateTime odt = ldt.atOffset(validOffsets.get(0));
其他情况(零或两个有效偏移)通常由于夏令时变化(DST)而发生。
在圣保罗时区,DST将于2017年10月15日 th 开始:在午夜,时钟向前移动到凌晨1点,并将变化从-03:00
偏移到-02:00
。这意味着从00:00到00:59的所有当地时间都不存在 - 您还可以认为时钟从23:59直接变为01:00。
因此,在圣保罗时区,此日期将没有有效的抵消:
// October 15th 2017 at midnight, DST starts in Sao Paulo
LocalDateTime ldt = LocalDateTime.parse("2017-10-15", parser);
// system's default timezone is America/Sao_Paulo
List<ZoneOffset> validOffsets = ZoneId.systemDefault().getRules().getValidOffsets(ldt);
System.out.println(validOffsets.size()); // zero
没有有效的偏移量,所以你必须决定在这种情况下做什么(使用“默认”的?抛出异常?)。
即使您的时区今天没有DST,它可能在过去(过去的日期可以是那种情况),也可能在未来(因为任何国家的DST和抵消都是由政府和法律,并不能保证将来没有人会改变。)
但是,如果您创建ZonedDateTime
,结果会有所不同:
// October 15th 2017 at midnight, DST starts in Sao Paulo
LocalDateTime ldt = LocalDateTime.parse("2017-10-15", parser);
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
zdt
变量将为2017-10-15T01:00-02:00[America/Sao_Paulo]
- 时间和偏移量会自动调整为凌晨1点-02:00
偏移量。
并且存在两个有效偏移的情况。在圣保罗,DST将于2018年2月18日 th 结束:在午夜,时钟将在1小时后转移到17点 th ,并且偏移量从{{{ 1}}到-02:00
。这意味着23:00和23:59之间的所有本地时间将在两个偏移中存在两次。
当我将默认时间设置为午夜时,将只有一个有效偏移。 但是假设我决定使用23:00的默认时间:
-03:00
// parse yyyy-MM-dd or yyyy/MM/dd
parser = new DateTimeFormatterBuilder().appendPattern("[yyyy-MM-dd][yyyy/MM/dd]")
// *** set hour to 11 PM ***
.parseDefaulting(ChronoField.HOUR_OF_DAY, 23)
// set minute to zero
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
// create formatter
.toFormatter();
// February 18th 2018 at midnight, DST ends in Sao Paulo
// local times from 23:00 to 23:59 at 17th exist twice
LocalDateTime ldt = LocalDateTime.parse("2018-02-17", parser);
// system's default timezone is America/Sao_Paulo
List<ZoneOffset> validOffsets = ZoneId.systemDefault().getRules().getValidOffsets(ldt);
System.out.println(validOffsets.size()); // 2
将有2个有效的偏移量。在这种情况下,您必须选择其中一个:
LocalDateTime
如果您创建// DST offset: 2018-02-17T23:00-02:00
OffsetDateTime dst = ldt.atOffset(validOffsets.get(0));
// non-DST offset: 2018-02-17T23:00-03:00
OffsetDateTime nondst = ldt.atOffset(validOffsets.get(1));
,它将使用第一个偏移量作为默认值:
ZonedDateTime
// February 18th 2018 at midnight, DST ends in Sao Paulo
LocalDateTime ldt = LocalDateTime.parse("2018-02-17", parser);
// system's default timezone is America/Sao_Paulo
List<ZoneOffset> validOffsets = ZoneId.systemDefault().getRules().getValidOffsets(ldt);
// by default it uses DST offset
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
将为zdt
- 请注意,默认情况下它使用DST偏移量(2018-02-17T23:00-02:00[America/Sao_Paulo]
)。
如果您希望DST结束后的偏移量,您可以执行以下操作:
-02:00
// get offset after DST ends
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault()).withLaterOffsetAtOverlap();
将为zdt
- 它使用偏移2018-02-17T23:00-03:00[America/Sao_Paulo]
(在DST结束后)。
只需提醒即使在运行时也可以更改系统的默认时区,最好使用特定的区域名称(如-03:00
)。您可以致电ZoneId.of("America/Sao_Paulo")
获取可用时区列表(并选择最适合您系统的时区)。
答案 1 :(得分:2)
OffsetDateTime
需要知道与UTC的时区偏移,因此您需要为此提供默认值。提供ChronoField.OFFSET_SECONDS
的默认值:
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)
.parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
.toFormatter();
或者,您拥有的格式足以支持LocalDateTime
。