【问题标题】:How to create Java time instant from pattern?如何从模式创建 Java 时间瞬间?
【发布时间】:2016-06-07 06:30:06
【问题描述】:

考虑一个代码:

TemporalAccessor date = DateTimeFormatter.ofPattern("yyyy-MM-dd").parse("9999-12-31");
Instant.from(date);

最后一行抛出异常:

Unable to obtain Instant from TemporalAccessor: {},ISO resolved to 9999-12-31 of type java.time.format.Parsed

如何从yyyy-MM-dd 模式创建Instant

【问题讨论】:

  • 首先我会尝试比 9999 低一年;D 例如在 2016 年尝试。
  • 使用 LocalDate.parse(),通过选择时间(atStartOfDay?)将其转换为 LocalDateTime,通过选择时区将其转换为 ZonedDateTime(日期是否代表法国的瞬间?在英国?在日本?),然后将 ZonedDateTime 转换为 Instant。
  • 瞬间就是瞬间。 31-12-9999 是一个日期,而不是一个瞬间。因此,如果您只需要日期,您可以将其解析为 LocalDate,或者您必须指定丢失的位(时间 + 区域偏移量或时区)。

标签: java java-time


【解决方案1】:

字符串“9999-12-31”仅包含有关日期的信息。它不包含有关时间或偏移的任何信息。因此,没有足够的信息来创建Instant。 (其他日期和时间库更宽松,但java.time 避免默认这些值)

您的第一选择是使用 LocalDate 而不是 `Instant:

LocalDate date = LocalDate.parse("9999-12-31");

您的第二个选择是后处理日期以将其转换为瞬间,这需要一个时区,这里选择为巴黎:

LocalDate date = LocalDate.parse("9999-12-31");
Instant instant = date.atStartOfDay(ZoneId.of("Europe/Paris")).toInstant();

您的第三个选择是将时区添加到格式化程序,并默认时间:

static final DateTimeFormatter FMT = new DateTimeFormatterBuilder()
    .appendPattern("yyyy-MM-dd")
    .parseDefaulting(ChronoField.NANO_OF_DAY, 0)
    .toFormatter()
    .withZone(ZoneId.of("Europe/Paris"));
Instant instant = FMT.parse("9999-31-12", Instant::from);

(如果这不起作用,请确保您拥有最新的 JDK 8 版本,因为该区域已修复错误)。

值得注意的是,这些可能性都没有直接使用TemporalAccessor,因为该类型是低级框架接口,不是大多数应用程序开发人员使用的。

【讨论】:

  • 与您所展示的相反,java.util.Instantparse 方法不采用 DateTimeFormatter。相反,您必须执行FMT.parse("9999-31-12", Instant::from)。作为作者,您能否解释为什么 Instant 与其他类不同,例如LocalDateTime,在这方面?
  • 最初,Instant 根本不适用于 DateTimeFormatter。这在开发过程中有所改变,但没有添加您所指的方法。话虽如此,因为许多格式化程序不能与 Instant 一起使用,所以有一种情况说添加这样的方法会添加一些经常会失败的东西,并且会惹恼用户。
  • 谢谢!这个答案帮助我让我的 Java 代码解析没有分钟字段的 ISO 8601 时间戳,即“2019-06-11T08:00Z”:Instant.from((new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd'T'HH':'mm'Z'").parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0).parseDefaulting(ChronoField.NANO_OF_SECOND, 0).toFormatter().withZone(ZoneId.of("UTC"))).parse(string));
【解决方案2】:

问题不在于您使用的是 9999 年这一事实。Instant.MAX 字段的计算结果为时间戳 1000000000-12-31T23:59:59.999999999Z,因此 9999 作为一年就可以了。

处理TemporalAccessors 而不是LocalDateTimeZonedDateTime 等语义更丰富的类型就像使用Map 对对象及其属性进行建模而不是编写class——你必须确保该值具有接收它的东西所期望的字段(如秒、纳秒等),而不是依赖于更高级别的类中正式声明的操作以防止依赖关系未得到满足。

在您的情况下,时间访问器可能包含给定的已解析日期字段,但没有Instant 需要的“秒”字段。在大多数情况下,最好使用语义更丰富的类型,例如 LocalDateTime

由于您只有日期字段,因此您应该将其解析为日期,然后在将其转换为 Instant 之前添加时间字段。这是一种方法,使用 LocalDate 来解析日期:

LocalDate localDate = LocalDate.parse("2016-04-17");
LocalDateTime localDateTime = localDate.atStartOfDay();
Instant instant = localDateTime.toInstant(ZoneOffset.UTC);

【讨论】:

    【解决方案3】:
    public static void main(String[] args) throws ParseException {
            System.out.println(new SimpleDateFormat("yyyy-MM-dd").parse("2016-12-31").toInstant());
    }
    

    上面的代码给出以下输出:

    2016-12-31T00:00:00Z

    我已经使用 java 8 的特性('toInstant' 方法)回答了这个问题。希望这能回答你的问题......

    【讨论】:

    • 该代码任意设置了很多东西(并将旧版 Date API 与新的 Java 时间 API 混合在一起)...
    • @assylias 是的,'toInstant()' 方法仅在最新版本的 java 中可用。会在我的回答中提到。
    • @downvoter 你能解释一下吗。我可以知道否决票的原因吗?这将帮助我改进我的答案。
    • 我认为否决的原因是我上面所说的:(i)您不必要地混合 API,这令人困惑,并且(ii)您的示例的输出取决于用户时区(如果你在夏威夷,它会打印2016-12-31T10:00:00Z 例如)。
    • 取决于用户时区
    【解决方案4】:

    您要么只对日期本身(9999 年 12 月 31 日)感兴趣,在这种情况下,适当的类型将是 LocalDate

    LocalDate date = LocalDate.parse("9999-12-31");
    

    或者您确实想要Instant,在这种情况下您需要设置时间和时区,例如东京的00:00

    Instant instant = date.atStartOfDay(ZoneId.of("Asia/Tokyo")).toInstant();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-23
      • 1970-01-01
      • 2022-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多