英文:
SimpleDateFormat [0] issue
问题
我有以下的 SimpleDateFormat
代码:
Date date = new Date();
DateFormat inputDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SS'Z'");
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
String dateStr = inputDateFormat.format(calendar.getTime());
这段代码在我的开发服务器上运行得很好,但在沙箱实例上失败,报以下错误:
org.junit.ComparisonFailure: 期望值为:<...20-08-12T19:06:02.85[0]Z>,但实际值为:<...20-08-12T19:06:02.85[]Z>
我已经这样处理过:
dateStr = dateStr.replace("[0]", "");
dateStr = dateStr.replace("[]", "");
但是,我仍然不理解为什么在不同的服务器实例上我的日期不同,是否有更好的方法来处理这个问题。
英文:
I've below SimpleDateFormat
Code
Date date = new Date();
DateFormat inpuDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SS'Z'");
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
String dateStr = inpuDateFormat.format(cal.getTime());
It works perfectly on my dev servers but it fails on sandbox instances with following error.
org.junit.ComparisonFailure: expected:<...20-08-12T19:06:02.85[0]Z> but was:<...20-08-12T19:06:02.85[]Z>
I've handled it as
dateStr = dateStr.replace("[0]","");
dateStr = dateStr.replace("[]","");
But, I still didn't get the logic why my date is different on different server instances and is there any better way to handle it
答案1
得分: 4
## java.time
肯定有更好的方法来处理这个问题。在处理日期和时间时,请使用现代的 Java 日期和时间 API `java.time`,而不是使用`Date`、`DateFormat`、`SimpleDateFormat`或`Calendar`。
Instant now = Instant.now();
String dateStr1 = now.toString();
System.out.println(dateStr1);
一次运行的输出结果是:
> 2020-07-24T18:06:07.988093Z
你会注意到,秒数的小数部分输出了六位小数,而不是两位。在其他运行中,你可能会有三位小数或根本没有小数部分。不用担心,对于大多数情况,这是完全可以接受的。打印的格式是 ISO 8601,根据 ISO 8601,秒数的小数位数,甚至是否包含秒数都是可选的。因此,无论你需要将字符串用于什么目的,只要期望的是 ISO 8601 格式,上面代码片段中的字符串都会被接受。
我利用了 `Instant.toString()` 生成 ISO 8601 格式,所以我们不需要任何格式化器。
如果由于某种奇怪的原因,你确实需要恰好两位小数的秒数部分,可以使用格式化器来指定(现在输出 `Z`):
```java
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSX")
.withZone(ZoneOffset.UTC);
String dateStr2 = formatter2.format(now);
System.out.println(dateStr2);
2020-07-24T18:06:07.98Z
对于 DateTimeFormatter
(与 SimpleDateFormat
相对应),格式模式字符串中的大写 S
表示秒的小数部分,你可以自由地使用从一到九个 S
来获得从一位到九位小数。
你的代码出了什么问题?
首先,你从 JUnit 测试中得到的消息是:
org.junit.ComparisonFailure: expected:<...20-08-12T19:06:02.85[0]Z> but was:<...20-08-12T19:06:02.85[]Z>
方括号是 JUnit 引起我们注意预期值和实际值之间差异的方式。因此,它们不是这些值的一部分。JUnit 告诉我们,预期值应该以 .850Z
结尾,但实际上以 .85Z
结尾。因此,缺少了一个零。你的测试可能过于严格,因为如我所说,秒数的小数部分是两位还是三位并不重要。而且 02.85
和 02.850
只是表示完全相同值的不同方式。
方括号的作用还解释了为什么在字符串中替换 [0]
和 []
并没有帮助:方括号从未出现在字符串中,因此替换从未对字符串进行任何更改。
其次,对于 SimpleDateFormat
(与 DateTimeFormatter
相对应)格式模式字母大写 S
表示毫秒。因此,除了三个 S
之外的任何其他数字都没有意义,并会给你带来错误的结果。在你的代码中,你放置了两个 S
。在十次中有九次毫秒值在 100 到 999 的区间内,在这种情况下,SimpleDateFormat
会打印所有三个数字,尽管只有两个模式字母 S
。这可能解释了为什么你的单元测试在开发环境中通过了。在你的沙箱中,时间最终以 2.085 秒结束。正确的表示方式包括 02.08
和 02.085
。然而你的 SimpleDateFormat
都没有选取。对它来说,85 的毫秒值应该渲染为两个位置,所以它生成了 02.85
,这是错误的值,比正确值晚了 765 毫秒。你的单元测试对此提出了异议,尽管有一次只有两位小数,而不是三位。
第三点,虽然不是你问的问题,但无论是使用麻烦的 SimpleDateFormat
还是现代的 DateTimeFormatter
,你绝对不能在格式模式字符串中硬编码 Z
作为文字。尾随的 Z
表示 UTC 或与 UTC 的偏移为零。它需要被打印出来(如果是解析的话也是如此)作为一个偏移量,否则你会得到错误的结果。确保获得 Z
而不是例如 +02:00
的方法是确保指定了一个偏移量为 0。这就是我在我的格式化器上添加 .withZone(ZoneOffset.UTC)
的原因。
链接
- Oracle 教程:日期时间 解释了如何使用 java.time。
- Wikipedia 文章:ISO 8601
<details>
<summary>英文:</summary>
## java.time
There certainly is a much better way to handle it. Use java.time, the modern Java date and time API, for your date and time work, not `Date`, `DateFormat`, `SimpleDateFormat` nor `Calendar`.
Instant now = Instant.now();
String dateStr1 = now.toString();
System.out.println(dateStr1);
Output in one run was:
> 2020-07-24T18:06:07.988093Z
You notice that six decimals on the seconds were output, not two. In other runs you may have three decimals or no fraction at all. Don’t worry, for the majority of purposes you’ll be just fine. The format printed is ISO 8601, and according to ISO 8601 the count of decimals on the seconds, even the presence of seconds at all, is optional. So whatever you need the string for, as long as ISO 8601 format is expected, the string from the above code snippet should be accepted.
I am exploiting the fact that `Instant.toString()` produces ISO 8601 format, so we don’t need any formatter.
If for some strange reason you do need exactly two decimals on the seconds, use a formatter for specifying so (edit: now outputting `Z`):
DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSX")
.withZone(ZoneOffset.UTC);
String dateStr2 = formatter2.format(now);
System.out.println(dateStr2);
> 2020-07-24T18:06:07.98Z
To a `DateTimeFormatter` (opposite a `SimpleDateFormat`) uppercase `S` in the format pattern string means *fraction of second*, and you are free to place from one through nine of them to get from one to nine decimals.
## What went wrong in your code?
First, the message that you got from your JUnit test was:
> org.junit.ComparisonFailure: expected:<...20-08-12T19:06:02.85[0]Z> but was:<...20-08-12T19:06:02.85[]Z>
The square brackets is JUnit’s way of drawing our attention to the difference between the expected and the actual value. So they are not part of those values. What JUnit tells us is that the value was expected to end in `.850Z` but instead ended in just `.85Z`. So a zero was missing. Your test is probably too strict since as I said, it shouldn’t matter whether there are two or three decimals. And `02.85` and `02.850` are just different ways of denoting the exact same value.
This role of the square brackets also explains why replacing `[0]` and `[]` in the string didn’t help: the square brackets were never in the strings, so the replacements never made any change to the strings.
Second, to `SimpleDateFormat` (opposite `DateTimeFormatter`) format pattern letter uppercase `S` means *millisecond*. So putting any other number than three of them makes no sense and gives you incorrect results. In your code you put two. In nine of ten cases the millisecond value is in the interval 100 through 999, and in this case `SimpleDateFormat` prints all three digits in spite of the only two pattern letters `S`. This probably explains why your unit test passed in your development environment. On your sandbox incidentally the time ended in 2.085 seconds. The correct ways to render this include `02.08` and `02.085`. Your `SimpleDateFormat` chose neither. To it the millisecond value of 85 was to be rendered in two positions, so it produces `02.85`, which is the wrong value, 765 milliseconds later. And your unit test objected while this once there were only two decimals, not three.
Third, not what you asked, but no matter if using the troublesome `SimpleDateFormat` or the modern `DateTimeFormatter` you must *never* hardcode `Z` as a literal in the format pattern string. The trailing `Z` means UTC or offset zero from UTC. It needs to be printed (and parsed if that were the case) as an offset, or you get wrong results. The way to make sure you get a `Z` and not for example an offset of `+02:00` is to make sure that an offset of 0 is specified. This was why I put `.withZone(ZoneOffset.UTC)` on my formatter.
## Links
- [Oracle tutorial: Date Time](https://docs.oracle.com/javase/tutorial/datetime/) explaining how to use java.time.
- [Wikipedia article: ISO 8601](https://en.wikipedia.org/wiki/ISO_8601)
</details>
# 答案2
**得分**: 2
尝试移除对 'Z' 的引号,因为 'Z' 是一个常量,而不带引号的情况下它表示 '时区':
```java
DateFormat inpuDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
(顺便说一下,在大多数情况下,毫秒要使用三位小数: "SSS"。)
英文:
Try to remove the quotes around the 'Z', as 'Z' is a constant whilst without quotes it means 'time zone':
DateFormat inpuDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
(By the way, in most cases you want to use three decimal places for milliseconds: "SSS".)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论