先来看一下日期格式主要有下面三种,US style,Euro style,RFC 3389。
Layout Format string Example
------------------------ ------------- ----------
US style (Dec 29, 2019) MM/DD/YYYY 12/29/2019
Euro style (29 Dec 2019) DD/MM/YYYY 29/12/2019
RFC 3339 (2019-12-29) YYYY-MM-DD 2019-12-29
开发中,我们经常需要将时间进行转换成我们需要的格式,我们可以使用JDK8提供的一个DateTimeFormatter
类来完成对日期的转换,如下面的代码:
public class CarefulWithThatDateEugene {
private static void tryit(int Y, int M, int D, String pat) {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern(pat);
LocalDate dat = LocalDate.of(Y, M, D);
String str = fmt.format(dat);
System.out.printf("Y=%04d M=%02d D=%02d " +
"formatted with " +
"\"%s\" -> %s\n", Y, M, D, pat, str);
}
public static void main(String[] args) {
tryit(2020, 01, 20, "MM/DD/YYYY");
tryit(2020, 01, 21, "DD/MM/YYYY");
tryit(2020, 01, 22, "YYYY-MM-DD");
}
}
上面的代码执行之后,可以完美的输出:
Y=2020 M=01 D=20 formatted with "MM/DD/YYYY" -> 01/20/2020
Y=2020 M=01 D=21 formatted with "DD/MM/YYYY" -> 21/01/2020
Y=2020 M=01 D=22 formatted with "YYYY-MM-DD" -> 2020-01-22
DD
但是如果我们执行下面的代码去测试:
tryit(2020,05,17,"MM/DD/YYYY");
tryit(2020,05,18,"DD/MM/YYYY");
tryit(2020,05,19,"YYYY-MM-DD");
你会发现控制台会抛出异常
Exception in thread "main" java.time.DateTimeException: Field DayOfYear cannot be printed as the value 138 exceeds the maximum print width of 2
at java.time.format.DateTimeFormatterBuilder$NumberPrinterParser.format(DateTimeFormatterBuilder.java:2548)
at java.time.format.DateTimeFormatterBuilder$CompositePrinterParser.format(DateTimeFormatterBuilder.java:2179)
at java.time.format.DateTimeFormatter.formatTo(DateTimeFormatter.java:1746)
at java.time.format.DateTimeFormatter.format(DateTimeFormatter.java:1720)
at com.liuyao.time.CarefulWithThatDateEugene.tryit(CarefulWithThatDateEugene.java:15)
at com.liuyao.time.CarefulWithThatDateEugene.main(CarefulWithThatDateEugene.java:26)
提示说Day的值为138,超过了日期的最大长度2,为什么日期会变成138呢,如果我们不管异常,输出的结果将会是:
Y=2020 M=05 D=17 formatted with "MM/DD/YYYY" -> 05/138/2020
Y=2020 M=05 D=18 formatted with "DD/MM/YYYY" -> 139/05/2020
Y=2020 M=05 D=19 formatted with "YYYY-MM-DD" -> 2020-05-140
可见日期都不对了,因为 DD
代表的并不是一个月的某一天,而是一年的某一天,所以才会出现138超过31的值。
所以我们应该使用 dd
来转换日期。
YYYY
然后继续执行代码:
tryit(2018,12,30,"YYYY-MM-dd");
tryit(2018,12,31,"YYYY-MM-dd");
tryit(2019,01,01,"YYYY-MM-dd");
输出的结果是:
Y=2018 M=12 D=30 formatted with "YYYY-MM-dd" -> 2019-12-30
Y=2018 M=12 D=31 formatted with "YYYY-MM-dd" -> 2019-12-31
Y=2019 M=01 D=01 formatted with "YYYY-MM-dd" -> 2019-01-01
可见前面两个的2018年变成了2019年,年份变了。
这是为什么呢?因为YYYY
使用的基于周的年份,而不是基于天数的,会计人员可以使用 YYYY
来避免两年的日期分割,从而方便的计算工资等, 它的转换采用下面的规则:
由于上面第二条的限制:
Sun 2015-12-27 -> Payroll week 52 of 2015
Mon 2015-12-28 -> Payroll week 53 of 2015
Tue 2015-12-29 -> Payroll week 53 of 2015
Wed 2015-12-30 -> Payroll week 53 of 2015
Thu 2015-12-31 -> Payroll week 53 of 2015
-------------NEW YEAR---------------------
Fri 2016-01-01 -> Payroll week 53 of 2015
Sat 2016-01-02 -> Payroll week 53 of 2015
Sun 2016-01-03 -> Payroll week 53 of 2015
Mon 2016-01-04 -> Payroll week 01 of 2016
2015年,第52周之后还剩下四天,因此2016年的前三天被“放到”到2015年的工资单中。
但在2025年,情况刚好相反,到2025年底只剩下三天时间,就会被“放进”到2026年的工资单年份
Sun 2025-12-28 -> Payroll week 52 of 2025
Mon 2025-12-29 -> Payroll week 01 of 2026
Tue 2025-12-30 -> Payroll week 01 of 2026
Wed 2025-12-31 -> Payroll week 01 of 2026
-------------NEW YEAR---------------------
Thu 2026-01-01 -> Payroll week 01 of 2026
Fri 2026-01-02 -> Payroll week 01 of 2026
Sat 2026-01-03 -> Payroll week 01 of 2026
Sun 2026-01-04 -> Payroll week 01 of 2026
Mon 2026-01-05 -> Payroll week 02 of 2026
所以你如果采用的是 YYYY
来格式化的年份,那么你讲不可避免的会在一年的结束或者一年的开始遇到这个问题,除非某年的第一天刚好是星期一,这样ISO-8601就会把日期分割的刚刚好。
下面就是活生生的例子:
采用 DD
来格式化代码,你会很容易发现这个错误,因为一年有85%的时间都是错误的,但是你如果采用了 YYYY
这个错误的 格式来格式年份,一年中只有1%的时间是错误的,而且这个错误还不是每7年就会出现一次的。
正确的格式化ISO-8601模板为: yyyy-MM-dd HH:mm:ss
正确的UTC的时间模板为 yyyy-MM-dd'T'HH:mm:ss.SSSXXX