2017-08-10 51 views
1

我转换日期/日期的字符串OffsetDateTime,我有可能是这些值字符串日期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'无法解析:无法获取OffsetDateT ime从TemporalAccessor:{},ISO解析为2016-06-06T00:00类型java.time.format.Parsed

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

+1

可能的重复https://stackoverflow.com/questions/44297939/convert-string-to-offsetdatetime-in-java –

+0

[将字符串转换为Java中的OffsetDateTime](https://stackoverflow.com/问题/ 44297939/convert-string-to-offsetdatetime-in-java) –

回答

1

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

+0

有没有什么办法可以将系统默认偏移量添加为ChronoField.OFFSET_SECONDS – wazza

+1

这很简单,因为系统上的当前偏移量可能与正确的偏移量不同由于夏令时的日期。 –

1

要创建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)); 

的其他情况下(0或2个的有效偏移)通常是由于夏令时更改(DST)发生。

在圣保罗时区,DST将开始在10月15日 2017:在午夜,时钟向前移动凌晨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将于2月18日结束th 2018:在午夜,时钟将会转移1小时回到11 PM 17 th,并且偏移量从-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 

会有为LocalDateTime 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)); 

如果创建一个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()); 

zdt2018-02-17T23:00-02:00[America/Sao_Paulo] - 记它默认使用DST偏移量(-02:00)。

如果你想抵消DST结束后,你可以这样做:

// get offset after DST ends 
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault()).withLaterOffsetAtOverlap(); 

zdt2018-02-17T23:00-03:00[America/Sao_Paulo] - 它采用了偏移-03:00(DST结束后)。


只是提醒的是系统默认的时区甚至可以在运行时改变,而且最好使用特定的区域名称(如ZoneId.of("America/Sao_Paulo"))。您可以通过拨打ZoneId.getAvailableZoneIds()获取可用时区列表(并选择最适合您的系统的时区)。

+0

感谢什么如果我想ZonedDateTime而不是OffsetdateTime。如何实现这一目标? – wazza

+1

@wazza我已经更新了答案 – 2017-08-10 12:56:41