OffsetDateTime

时间:2017-08-10 03:49:32

标签: 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")
                        .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

任何人都可以帮我解决这个问题吗?

2 个答案:

答案 0 :(得分:4)

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

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

要解析这两种格式(yyyy-MM-ddyyyy/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