Java 8 要偏移DateTime的日期字符串

Java 8 要偏移DateTime的日期字符串,java-8,java-time,datetimeoffset,datetime-parsing,Java 8,Java Time,Datetimeoffset,Datetime Parsing,我正在将日期/日期时间字符串转换为OffsetDateTime,并且我有一个日期时间格式,它可能有以下值之一 yyyy-MM-dd, yyyy/MM/dd 有时有时间,有时没有时间,我需要将其转换为OffsetDateTime 我试过下面的代码 // for format yyyy-MM-dd DateTimeFormatter DATE_FORMAT = new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd")

我正在将日期/日期时间字符串转换为
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


有人能帮我解决这个问题吗?

OffsetDateTime
需要知道时区与UTC的偏移量,因此您需要为此提供一个默认值。为
计时字段提供默认值。偏移量\u秒

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
创建
OffsetDateTime
,您需要日期(日、月和年)、时间(小时、分钟、秒和纳秒)以及(与的差异)

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

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

您也可以使用它(稍微复杂一点,但工作方式相同):

然后,您可以设置构建
LocalDateTime
的时间:

// set time to midnight
LocalDateTime ldt = dt.atStartOfDay();

// set time to 2:30 PM
LocalDateTime ldt = dt.atTime(14, 30);
// most common case: just one valid offset
OffsetDateTime odt = ldt.atOffset(validOffsets.get(0));

您还可以选择使用
parseDefaulting
,如中所述:

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


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

“系统的默认偏移量”将取决于系统的默认时区。一个时区可以有多个偏移,这取决于您在时间线上的时间

我将使用系统的默认时区(
美国/圣保罗
)作为示例。在下面的代码中,我使用的是
ZoneId.systemDefault()
,但请记住,这在每个系统/环境中都是不同的对于下面的所有示例,请记住
ZoneId.systemDefault()
返回
美国/圣保罗
时区。如果你想得到一个特定的,你应该使用
ZoneId.of(“zone\u 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);
其他情况(零或两个有效偏移)通常由于夏令时更改(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
zdt
变量将为
2017-10-15T01:00-02:00[美国/圣保罗]
-时间和偏移量将在
-02:00
偏移量时自动调整为凌晨1点


还有两个有效偏移量的情况。在圣保罗,DST将于2018年2月18日结束:午夜时分,时钟将从17日晚上11点向后移动1小时,偏移量从
-02:00
更改为
-03: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
但是,如果创建一个
ZoneDateTime
,它将使用第一个偏移作为默认值:

// 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
将是
2018-02-17T23:00-03:00[美国/圣保罗]
-它使用偏移量
-03:00
(DST结束后)



只要提醒一下,即使在运行时,系统的默认时区也可以更改,最好使用特定的时区名称(如
ZoneId.of(“America/SaoèPaulo”)
)。通过调用
ZoneId.getAvailableZoneIds()可以获得可用时区的列表(并选择最适合您的系统的时区)

的可能副本的可能副本是否有任何方法将系统默认偏移量添加为ChronoField.offset\u seconds这是一种快速的技巧,因为由于夏时制,系统上的当前偏移量可能与日期的正确偏移量不同。如果我想要ZonedDateTime而不是OffsetdateTime,请多谢。如何做到这一点?
// 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
// October 15th 2017 at midnight, DST starts in Sao Paulo
LocalDateTime ldt = LocalDateTime.parse("2017-10-15", parser);
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
// 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
// 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));
// 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());
// get offset after DST ends
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault()).withLaterOffsetAtOverlap();