我将date / datetime的字符串转换为OffsetDateTime
,并且具有datetime格式,该格式可能具有以下值之一
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)
它像抛出错误
线程“主”中的异常java.time.format.DateTimeParseException:无法解析文本“2016-06-06”:无法从TemporalAccessor中获取OffsetDateTime:{},ISO解析为Java类型的2016-06-06T00:00 .time.format.Parsed
谁能帮我解决这个问题?
最佳答案
要创建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日开始:在午夜,时钟向前移动到凌晨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
没有有效的偏移量,因此您必须确定在这种情况下的处理方法(使用“默认”值吗?抛出异常?)。
即使您的时区今天没有夏令时,也可能是过去的夏令时(在这种情况下可能是过去的日期),也可能是将来的夏令时(因为夏令时和任何国家/地区的偏移量都由政府和法律,并且不能保证将来没有人会改变)。
但是,如果创建
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]
-时间和偏移量将自动调整为的凌晨到。并且存在两个有效偏移量的情况。在圣保罗,DST将于2018年2月18日结束:在午夜,时钟将偏移1小时回到17日的11 PM,偏移量从
-02:00
更改为-02:00
。这意味着在23:00和23:59之间的所有本地时间将在两个偏移量中都存在两次。当我将默认时间设置为午夜时,将只有一个有效的偏移量。 但是假设我决定使用默认时间23: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
-03:00
将有2个有效偏移量。在这种情况下,您必须选择以下选项之一:// 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));
如果创建
LocalDateTime
,它将使用第一个偏移量作为默认值:// 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());
ZonedDateTime
将为zdt
-注意,默认情况下,它使用DST偏移量(2018-02-17T23:00-02:00[America/Sao_Paulo]
)。如果您希望DST结束后的偏移量,可以执行以下操作:
// get offset after DST ends
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault()).withLaterOffsetAtOverlap();
-02:00
将为zdt
-它使用偏移量2018-02-17T23:00-03:00[America/Sao_Paulo]
(在DST结束之后)。只是提醒您,即使在运行时也可以更改系统的默认时区,最好使用特定的时区名称(例如
-03:00
)。您可以通过调用ZoneId.of("America/Sao_Paulo")
获得可用时区的列表(并选择最适合您的系统的时区)。
关于java-8 - 到OffsetDateTime的日期字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45604130/