java-8 - 到OffsetDateTime的日期字符串

标签 java-8 java-time datetimeoffset datetime-parsing

我将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-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日开始:在午夜,时钟向前移动到凌晨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/

相关文章:

Java Stream API 字符串操作

java - 在 Java 8 中,如何从 Stream< 获取 Stream<T>?扩展集合<T>>?

clojure - take-while 和 java-time/iterate 创建奇怪的结果

kotlin - 具有多种格式的 Moshi LocalDateTime 适配器

c# - 将 DateTimeOffset 转换为 DateTime 并为此 DateTime 添加偏移量

java - 如何指定通用类型作为函数参数

java - 创建自定义流

java - 如何在 Java-8 中显示普通纪元 ("CE")?

c# - 如何从 NodaTime 中的偏移量获取所有 IANA 时区?

python - 数据框列上的日期偏移量