2015年1月12日

JVM 日期格式該使用 yyyy 或是 YYYY

去年年底有則 twitter 談到因為誤用了日期格式化,使用了 YYYY 來格式化日期,在 2014/12/29 造成了日期錯誤,而把日期變成了 2015 年。

SimpleDateFormat 日期格式化

要在 Java 程式中格式化日期,最正確的格式化字串為

yyyy/MM/dd HH:mm:ss

第一次使用時,通常會遇到的問題是,MM 跟 mm 差別在哪裡,MM 代表是月份,而 mm 代表分鐘,這個部份的錯誤如果寫錯了,很容易就可以發現問題。

再來可能會遇到的問題,是把 HH 打成了 hh,HH 代表 24 小時制的小時,而 hh 是 Hour in am/pm (1-12) ,如果是在早上寫程式,那就不會發現自己寫錯了。

最糟糕的,就是年份遇到的問題,當日期落在 12/28~12/31 區間的時候,就會發生年份增加一年的錯誤。

測試程式

import java.util.Date;
import java.text.SimpleDateFormat;

class Test{
    public static void main(String[] args){

        System.out.println(
            new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()));

    // 小時 錯誤
    System.out.println(
            new SimpleDateFormat("yyyy/MM/dd hh:mm:ss").format(new Date()));

    // 月份 錯誤
    System.out.println(
            new SimpleDateFormat("yyyy/mm/dd HH:mm:ss").format(new Date()));

    // 年份 錯誤
    System.out.println(
            new SimpleDateFormat("YYYY/MM/dd HH:mm:ss").format(new Date()));

    }
}

我們直接用 date 指令修改日期,當機器日期在 12/27 的時候,年份是沒有問題的。

> date 122720292014
六 12月 27 20:29:00 CST 2014
> java Test
2014/12/27 20:29:02
2014/12/27 08:29:02        -> 小時錯誤
2014/29/27 20:29:02        -> 月份錯誤
2014/12/27 20:29:02

當機器日期在 12/28~12/31 區間的時候,測試結果如下

> date 122920292014
一 12月 29 20:29:02 CST 2014
> java Test
2014/12/29 20:29:05
2014/12/29 08:29:05        -> 小時錯誤
2014/29/29 20:29:05        -> 月份錯誤
2015/12/29 20:29:05        -> 年份錯誤

Gregorian Calendar vs ISO 8601

一般我們使用的日曆系統為 Gregorian Calendar,他是以400年為一個週期,在這個週期中,一共有97個閏日,閏日儘可能均勻地分佈在各個年份中,所以一年的長度可能有 365天或366天。

而另一個時間規格標準 ISO 8601 卻是用不同的方式處理閏日,他是用潤週的概念,因此一年的長度是364或371天。而且 ISO 8601 規定一年中第一個週四所在的那個星期,作為一年的第一個星期。

今年2015年剛好1月1日就是週四,所以 12/28~12/31 算在 2015年第一週,如果用 ISO 8601 的規則,12/28 是 2015 年。

在 Java SimpleDateFormat 裡面,代表 ISO 8601的年份格式符號是 YYYY,Gregorian Calendar 的符號是 yyyy。

如果使用了 YYYY 作為 DateFormat 的格式符號,就會發生日期瞬間移動一年的問題。

結語

發生 HH 與 hh 的問題,頂多只是 12 小時的錯誤,如果發生了 YYYY 與 yyyy 的問題,就會有一年的落差。

不管如何,在處理日期格式時,必須將 文件 看清楚,在寫程式時,多花一點時間 review 一下,用直覺跟印象寫程式,就很容易出錯。