时间(4)

Updated: 2019-01-05

入门

不同的语言有不同的格式化时间的方法,如 Java 中用M表示月,d表示日,有趣的是Yy都可以表示年,而且看起来好像也没啥差别:

jshell> import java.time.format.DateTimeFormatter;

jshell> import java.time.LocalDate;

jshell> DateTimeFormatter.ofPattern("YYYY-MM-dd").format(LocalDate.of(2018, 10, 1))
$1 ==> "2018-10-01"

jshell> DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.of(2018, 10, 1))
$2 ==> "2018-10-01"

YYYYyyyy的结果完全一样。难道是不分大小写?

坑!

别急着下结论,看看这两个:

jshell> DateTimeFormatter.ofPattern("YYYY-MM-dd").format(LocalDate.of(2018, 12, 30))
$3 ==> "2019-12-30"

jshell> DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.of(2018, 12, 30))
$4 ==> "2018-12-30"

从国庆改到年底,YYYY就迫不及待的跳到下一年去了。难道是java.time引入的 bug?用老办法试试:

jshell> new java.text.SimpleDateFormat("YYYY-MM-dd").format(new java.util.Date("12/30/2018"))
$5 ==> "2019-12-30"

jshell> new java.text.SimpleDateFormat("YYYY-MM-dd").parse("2018-12-30")
$6 ==> Sun Dec 31 00:00:00 GMT 2017

同样的format的结果跑到了 2019,而parse的结果回到了 2017...

进阶

显然YYYYyyyy是不一样的,那区别在哪的?注意看文档,yyear-of-era,也就是日历年;而Y则是week-based-year,基于周的年,因为 1 月 1 号可能出现在一周的任何一天,比如 2019 年的 1 月 1 号是周二,2018 年的 12 月 30 号是周日。如果一周是从周日开始算的话,2018 年 12 月 30 号确实是在 2019 年的第一周,所以当把这一天格式化输出的时候,YYYY一看,已经2019了;而由于年月日是分别进行格式化的,所以MMdd还是正确的1230,导致结果为2019-12-30

同样道理,当用YYYY去解析2018的时候,YYYY的搭档是w(week-of-week-based-year)和e(localized day-of-week),也就是第几周和第几天,而MM(12)和dd(30)这两个猪队友没有提供任何有效的信息,所以只能当做是默认的第一周第一天,也就是 2018 年的第一周的那个周日,就是 2017 年的 12 月 31 日。来验证一下:

jshell> DateTimeFormatter.ofPattern("YYYY-w-e").parse("2018-1-1")
$7 ==> {},ISO resolved to 2017-12-31

YYYY-w-e表示基于周的年-第几周-这一周的第几天,2018 年第一周第一天果然就是2017-12-31

这么说感觉像是个很不起眼的小问题,但 Twitter 就曾被这个小问题给坑了:2014 年年底,由于错误的使用了week-based-year,Twitter 的安卓客户端突然以为自己是在 2015 年的年底了,很多用户被迫被 log out,并无法登陆;即使问题修复之后,很多用户的时间线也因此乱了。从原始新闻的截图可以看到,出问题的是 2014 年 12 月 29 号:

jshell> DateTimeFormatter.ofPattern("YYYY-MM-dd").format(LocalDate.of(2014, 12, 29))
$8 ==> "2015-12-29"