为什么Instant.now()有时格式不同?

7

我有一段代码长这样:

String PROPER_DATE_FORMAT = "yyyy-MM-dd";
String format = "yyyy-MM-dd'T'HH:mm:ss.SSSX";

SimpleDateFormat sdf = new SimpleDateFormat(format);
Date d = sdf.parse(Instant.now().toString());
SimpleDateFormat properFormat = new SimpleDateFormat(PROPER_DATE_FORMAT);
String formatterDate = properFormat.format(d);

现在,大部分时间,这段代码都能正常工作,但是偶尔会遇到 Instant.now() 返回 2018-05-25T18:56:09Z 的情况,然后它就会抛出异常因为格式不匹配。为什么有时会不一致呢?
异常信息是 java.lang.RuntimeException: java.text.ParseException: Unparseable date: "2018-05-25T18:56:09Z" 通常情况下,Instant.now().toString() 会返回上述格式的字符串。再次说明,这段代码大部分时间都能正常工作,但是有几次却不能,我对此感到困惑。
我考虑过是不是因为结尾有个零而没有显示出来,但我并不确定,因为我可以通过调用 Instant.now().toString() 获得一个值为 2018-05-25T20:06:58.900Z 的结果。

1
谢谢,我已经包含了异常信息。 - Vicky
2
为什么要将Instant转换为字符串再解析成日期?为什么不直接写 Date d = new Date(Instant.now().toEpochMilli()); 呢? - Thomas Kläger
1
Instant.toString() 只在必要时(如果它们不是 000)输出毫秒数的数字。因此,您有千分之一的机会恰好在秒数上(所有毫秒数字都为零)遇到代码失败的情况。 - Thomas Kläger
1
由于您可以使用现代的Java日期和时间API java.time,因此不要再使用过时且设计不佳的Date。只有在需要为无法更改的旧版API使用Date时,才应使用Date.from(Instant)进行转换。并且在任何情况下都不要使用臭名昭著的问题多多的SimpleDateFormat - Ole V.V.
1
顺便提一下,在我的电脑上运行Java 9时,Instant.now().toString()会产生类似于2018-05-26T05:56:50.294304Z的输出,而你的代码将其转换为了Date类型,显示为Sat May 26 08:01:44 CEST 2018。CEST偏移量为+02:00,因此你会发现时间不正确。 - Ole V.V.
显示剩余10条评论
4个回答

8

当纳秒为零时,它不包含在格式化的字符串中。 因此,输出将格式化为“yyyy-MM-dd'T'HH:mm:ssX”

您可以通过以下方式查看差异:

public static void main(String[] args) throws ParseException {
        final Instant now = Instant.now();
        System.out.println("Now:\t" +  now);

        final Instant zeroedNow = now.with(ChronoField.NANO_OF_SECOND, 0);
        System.out.println("Zeroed:\t" + zeroedNow);
} 

输出结果如下:
Now:    2018-05-25T20:23:54.208Z
Zeroed: 2018-05-25T20:23:54Z

8
您的代码失败是因为Instant.toString()的文档中写道:

使用的格式与DateTimeFormatter.ISO_INSTANT相同。

根据DateTimeFormatter.html#ISO_INSTANT

必要时,纳秒输出零、三、六或九个数字。

所以您有一千分之一的机会得到一个毫秒为000的Instant,从而导致代码失败。


无论如何,您的代码过于复杂,可以替换为

String formattedDate = DateTimeFormatter.ofPattern("yyyy-MM-dd")
    .withZone(ZoneId.systemDefault())
    .format(Instant.now());

无需将Instant转换为字符串,再解析成日期,然后再将其转换为字符串。


那不是被问到的内容。 - bruno
1
我同意@bruno的观点 - 虽然你的方法(可能)是更好的转换方式,但它并没有回答“为什么Instant.now()有时格式不同?”这个问题。 - Stephen P

1
回答你的问题:“Instant.now()有时会格式不同?”让我们来看一下Instance.now().toString()是如何实现的:
Instant.now().toString()源代码:
public String toString() {
    return DateTimeFormatter.ISO_INSTANT.format(this);
}

的Javadoc文档

DateTimeFormatter.ISO_INSTANT

https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_INSTANT

 When formatting, the second-of-minute is always output.
 The nano-of-second outputs zero, three, six or nine digits digits as necessary.
从这个javadoc中,我们可以了解到Instance.now.toString()并不总是24位长度。
如果毫秒数为000,则会产生如下格式:
2018-05-25T18:56:09Z - 20位长度
而不是
2018-05-25T18:56:09.000Z - 24位长度
一切都清楚了吗?

0

你可以使用以下方式格式化Instant:

instant.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"))

如果你想使用相同的API,而又没有任何标准方法可用,你可以考虑采用以下逻辑

Instant inst = Instant.now()
if(inst.get(ChronoField.MILLI_OF_SECOND) == 0)
inst  = inst.minusMillis(1)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接