1. 程式人生 > >月光寶盒之時間魔法--java時間的前生今世

月光寶盒之時間魔法--java時間的前生今世

月光寶盒花絮

“曾經有一份真誠的愛情擺在我的面前,但是我沒有珍惜,等到了失去的時候才後悔莫及,塵世間最痛苦的事莫過於此。如果可以給我一個機會再來一次的話,我會跟那個女孩子說我愛她,如果非要把這份愛加上一個期限,我希望是一萬年!”---大話西遊之仙履奇緣

 

《大話西遊之大聖娶親》(又名《大話西遊之仙履奇緣》)是周星馳彩星電影公司1994年製作和出品的一部經典的無厘頭搞笑愛情片,改編依據是吳承恩所撰寫的神怪小說《西遊記》,該片是《大話西遊》系列的第二部,由劉鎮偉導演,技安編劇,周星馳製作,周星馳、朱茵、莫文蔚、蔡少芬、陸樹銘、吳孟達等人主演。

該片主要講述了至尊寶為了救白晶晶而穿越回到五百年前,遇見紫霞仙子之後發生一段感情並最終成長為孫悟空的故事。該片於1995年2月4日在香港首映併入圍第十五屆香港電影金像獎最佳編劇獎和最佳男主角獎,周星馳憑藉該片獲得第一屆香港電影金紫荊獎最佳男主角獎和第二屆香港電影評論學會獎最佳男主角獎。

java中關於時間的設計經歷了Date,Calendar,到最後引用第三方包joda time,都發生了什麼?讓我們看看吧

java時間前生之Date

在Java平臺首次釋出時,它唯一支援日曆計算類的就是Date 類。這個類在能力方面是受限的,特別是當需要支援國際化時,它就暴露出了一個基本的設計缺陷:Date例項是易變的。Date會產生什麼問題呢?請看一下下面程式的輸出:

    public static void main(String[] args) {
        Date date=new Date(2018,12,31,0,0,0);        
        System.out.println(date.getYear());
        System.out.println(date.getMonth());
        System.out.println(date.getDay());
    }

 

我們想打印出的結果是

2018

12

31

可是,執行後的結果列印

2019

0

5

穿越了嗎?還是我的機器有問題?

 

換了別的機器依然如此。程式碼是不會騙人的,只好進原始碼看看

 /**
 * Allocates a <code>Date</code> object and initializes it so that
 * it represents the instant at the start of the minute specified by
 * the <code>year</code>, <code>month</code>, <code>date</code>,
 * <code>hrs</code>, and <code>min</code> arguments, in the local
 * time zone.
 *
 * @param year the year minus 1900.
 * @param month the month between 0-11.
 * @param date the day of the month between 1-31.
 * @param hrs the hours between 0-23.
 * @param min the minutes between 0-59.
 * @see java.util.Calendar
 * @deprecated As of JDK version 1.1,
 * replaced by <code>Calendar.set(year + 1900, month, date,
 * hrs, min)</code> or <code>GregorianCalendar(year + 1900,
 * month, date, hrs, min)</code>.
 */
 @Deprecated
 public Date(int year, int month, int date, int hrs, int min) {
 this(year, month, date, hrs, min, 0);
 }

 

程式大揭祕

  1. 設定年份是從1900開始的,即2018-1900=118
  2. 設定月份是從0開始的,即0~11,12等於下一年119年的第一個月即值為0
  3. day返回的是是周幾
 /**
 * Returns the day of the week represented by this date. The
 * returned value (<tt>0</tt> = Sunday, <tt>1</tt> = Monday,
 * <tt>2</tt> = Tuesday, <tt>3</tt> = Wednesday, <tt>4</tt> =
 * Thursday, <tt>5</tt> = Friday, <tt>6</tt> = Saturday)
 * represents the day of the week that contains or begins with
 * the instant in time represented by this <tt>Date</tt> object,
 * as interpreted in the local time zone.
 *
 * @return the day of the week represented by this date.
 * @see java.util.Calendar
 * @deprecated As of JDK version 1.1,
 * replaced by <code>Calendar.get(Calendar.DAY_OF_WEEK)</code>.
 */
 @Deprecated
 public int getDay() {
 return normalize().getDayOfWeek() - BaseCalendar.SUNDAY;
 }

 

java時間前生之Calenar

在1.1 版中,Calendar 類被新增到了Java 平臺中,以矯正Date的缺點,由此大部分的Date 方法就都被棄用了。遺憾的是,這麼做只能使情況更糟。我們的程式說明Date 和Calendar API 有許多問題。

    public static void main(String[ ] args) {
        Calendar cal = Calendar.getInstance();
        cal.set(2018, 12, 31); // Year, Month, Day
        System.out.print(cal.get(Calendar.YEAR) + " ");
        Date d = cal.getTime();
        System.out.println(d.getDay());
        }

來幹活吧,執行輸出結果:

2019 4

 

程式碼是不會騙人的,進原始碼看看吧

 /**
 * Sets the values for the calendar fields <code>YEAR</code>,
 * <code>MONTH</code>, and <code>DAY_OF_MONTH</code>.
 * Previous values of other calendar fields are retained. If this is not desired,
 * call {@link #clear()} first.
 *
 * @param year the value used to set the <code>YEAR</code> calendar field.
 * @param month the value used to set the <code>MONTH</code> calendar field.
 * Month value is 0-based. e.g., 0 for January.
 * @param date the value used to set the <code>DAY_OF_MONTH</code> calendar field.
 * @see #set(int,int)
 * @see #set(int,int,int,int,int)
 * @see #set(int,int,int,int,int,int)
 */
 public final void set(int year, int month, int date)
 {
 set(YEAR, year);
 set(MONTH, month);
 set(DATE, date);
 }

從上面的理解中,月份是從0開始的即0~11 代表 1月。。。。。12月

接著date又是從1開始的,為什麼同一個方法設計的如此怪異?

 

程式揭祕

1.標準的(西曆)日曆只有12 個月,該方法呼叫肯定應該丟擲一IllegalArgumentException 異常,對嗎?它是應該這麼做,但是它並沒有這麼做。Calendar 類直接將其替換為下一年,即:2019

有兩種方法可以訂正這個問題。你可以將cal.set 呼叫的第二個引數由12 改為11,但是這麼做容易引起混淆,因為數字11 會讓讀者誤以為是11 月。更好的方式是使用Calendar 專為此目的而定義的常量,即Calendar.DECEMBER

2. Date.getDay 返回的是Date例項所表示的星期日期,而不是月份日期。這個返回值是基於0 的,從星期天開始計算,即:4

有兩種方法可以訂正這個問題。你可以呼叫Date.date 這一名字極易讓人混淆的方法,它返回的是月份日期。然而,與大多數Date 方法一樣,它已經被棄用了,

因此你最好是將Date 徹底拋棄,直接呼叫Calendar 的get(Calendar.DAY_OF_MONTH)方法。

上例只是掀開了Calendar 和Date 缺陷的冰山一角。這些API 簡直就是雷區。Calendar 其他的嚴重問題包括弱型別(幾乎每樣事物都是一個int)、過於複雜的狀態空間、拙劣的結構、不一致的命名以及不一致的雨衣等。在使用Calendar和Date 的時候一定要當心,千萬要記著查閱API 文件。

對API 設計者來說,其教訓是:如果你不能在第一次設計時就使它正確,那麼至少應該在第二次設計時應該使它正確,絕對不能留到第三次設計時去處理。如果你對某個API 的首次嘗試出現了嚴重問題,那麼你的客戶可能會原諒你,並且會再給你一次機會。如果你第二次嘗試又有問題,你可能會永遠堅持這些錯誤了。

 

java時間後世之Joda Time

JDK在8之前的版本,對日期時間的處理相當麻煩,有些方法設計非常反人類。而Joda-Time使用起來不僅方便,而且可讀性強。雖然JDK 8引用了新的時間處理類,而且參與設計的人也正是Joda-Time的作者,但是由於各種原因,很多專案還是使用的JDK7,使用Joda-Time還是一個不錯的選擇。

Joda-Time提供了一組Java類包用於處理包括ISO8601標準在內的date和time。可以利用它把JDK Date和Calendar類完全替換掉,而且仍然能夠提供很好的整合。

Joda-Time主要的特點包括:

1. 易於使用:Calendar讓獲取"正常的"的日期變得很困難,使它沒辦法提供簡單的方法,而Joda-Time能夠 直接進行訪問域並且索引值1就是代表January。

2. 易於擴充套件:JDK支援多日曆系統是通過Calendar的子類來實現,這樣就顯示的非常笨重而且事實 上要實現其它日曆系統是很困難的。Joda-Time支援多日曆系統是通過基於Chronology類的外掛體系來實現。

3. 提供一組完整的功能:它打算提供 所有關係到date-time計算的功能.Joda-Time當前支援8種日曆系統,而且在將來還會繼續新增,有著比JDK Calendar更好的整體效能等等。

joda time示例

//jdk 
Calendar calendar=Calendar.getInstance(); 
calendar.set(2012, Calendar.NOVEMBER, 15, 18, 23,55); 
 
//Joda-time 
DateTime dateTime=new DateTime(2012, 12, 15, 18, 23,55); 

 

更詳細的參考:https://www.joda.org/joda-time/

參考資料:

【1】https://www.iteye.com/blog/persevere-1755237

【2】java