Java 中的時間日期 API

自從 14 年發佈 Java 8 之後,咱們古老 java.util.Date 終於再也不是咱們 Java 裏操做日期時間的惟一的選擇。java

其實 Java 裏的日期時間的相關 API 一直爲世猿詬病,不只在於它設計分上工不明確,每每一個類既能處理日期又能處理時間,很混亂,還在於某些年月日期的數值映射存儲反人類,例如:0 對應月份一月,11 對應月份十二月,118 對應年份 2018(1900 + 118)等。git

每每咱們獲得某個年月值還須要再作相應的運算才能獲得準確的年月日信息,直到咱們的 Java 8 ,借鑑了第三方開源庫 Joda-Time 的優秀設計,從新設計了一個日期時間 API,相比以前,能夠說好用百倍,相關 API 接口所有位於包 java.time 下。github

古老的日期時間接口

表示時刻信息的 Date

世界上全部的計算機內部存儲時間都使用一個 long 類型的整數,而這個整數的值就是相對於英國格林尼治標準時間(1970年1月1日0時0分0秒)的毫秒數。例如:數組

public static void main(String[] args){
    //January 1, 1970 00:00:00 GMT.
    Date date = new Date(1000);
    System.out.println(date);
}
複製代碼

輸出結果:bash

//1970-1-1 8:00:01
Thu Jan 01 08:00:01 CST 1970
複製代碼

不少人可能會疑惑,1000 表示的是距離標準時間日後 1 秒,那爲何時間卻多走了 八個小時?微信

這和「時區」有關係,若是你位於英國的格林尼治區,那麼結果會如預想同樣,可是咱們位於中國東八區,時間要早八個小時,因此不一樣時區基於的基礎值不一樣。ui

Date 這個類之前真的扮演過不少角色,從它的源碼就能夠看出來,有能夠操做時刻的方法,有能夠操做年月日的方法,甚至它還能管時區。能夠說,日期時間的相關操做有它一我的就足夠了。this

但這個世界就是這樣,你管的東西多了,天然就不能面面俱到,Date 中不少方法的設計並非很合理,以前咱們也說了,甚至有點反人類。因此,如今的 Date 類中接近百分之八十的方法都已廢棄,被標記爲 @Deprecated。spa

sun 公司給 Date 的目前定位是,惟一表示一個時刻,因此它的內部應該圍繞着那個整型的毫秒,而再也不着重於各類年曆時區等信息。設計

Date 容許經過如下兩種構造器實例化一個對象:

private transient long fastTime;

public Date() {
    this(System.currentTimeMillis());
}

public Date(long date) {
    fastTime = date;
}
複製代碼

這裏的 fastTime 屬性存儲的就是時刻所對應的毫秒數,兩個構造器仍是很簡單,若是調用的是無參構造器,那麼虛擬機將以系統當前的時刻值對 fastTime 進行賦值。

還有幾個爲數很少沒有被廢棄的方法:

  • public long getTime() :返回內部存儲的毫秒數
  • public void setTime(long time):從新設置內存的毫秒數
  • public boolean before(Date when):比較給定的時刻是否早於當前 Date 實例
  • public boolean after(Date when):比較給定的時刻是否晚於當前 Date 實例

還有兩個方法是 jdk1.8 之後新增的,用於向 Java 8 新增接口的轉換,待會介紹。

描述年曆的 Calendar

Calendar 用於表示年月日等日期信息,它是一個抽象類,因此通常經過如下四種工廠方法獲取它的實例對象。

public static Calendar getInstance()

public static Calendar getInstance(TimeZone zone)

public static Calendar getInstance(Locale aLocale)

public static Calendar getInstance(TimeZone zone,Locale aLocale)
複製代碼

其實內部最終會調用同一個內部方法:

private static Calendar createCalendar(TimeZone zone,Locale aLocale)
複製代碼

該方法須要兩個參數,一個是時區,一個是國家和語言,也就是說,構建一個 Calendar 實例最少須要提供這兩個參數信息,不然將會使用系統默認的時區或語言信息。

由於不一樣的時區與國家語言對於時刻和年月日信息的輸出是不一樣的,因此這也是爲何一個 Calendar 實例必須傳入時區和國家信息的一個緣由。看個例子:

public static void main(String[] args){


    Calendar calendar = Calendar.getInstance();
    System.out.println(calendar.getTime());

    Calendar calendar1 = Calendar.getInstance
            (TimeZone.getTimeZone("GMT"), Locale.ENGLISH);
    System.out.println( calendar1.get(Calendar.YEAR) + ":" +
                        calendar1.get(Calendar.HOUR) + ":" +
                        calendar1.get(Calendar.MINUTE));
    }
複製代碼

輸出結果:

Sat Apr 21 10:32:20 CST 2018
2018:2:32
複製代碼

能夠看到,第一個輸出爲咱們系統默認時區與國家的當前時間,而第二個 Calendar 實例咱們指定了它位於格林尼治時區(0 時區),結果也顯而易見了,相差了八個小時,那是由於咱們位於東八區,時間早於 0 時區八個小時。

可能有人會疑惑了,爲何第二個 Calendar 實例的輸出要如此複雜的拼接,而不像第一個 Calendar 實例那樣直接調用 getTime 方法簡潔呢?

這涉及到 Calendar 的內部實現,咱們一塊兒看看:

protected long          time;

public final Date getTime() {
    return new Date(getTimeInMillis());
}
複製代碼

和 Date 同樣,Calendar 的內部也維護着一個時刻信息,而 getTime 方法其實是根據這個時刻構建了一個 Date 對象並返回的。

而通常咱們構建 Calendar 實例的時候都不會傳入一個時刻信息,因此這個 time 的值在實例初始化的時候,程序會根據系統默認的時區和當前時間計算獲得一個毫秒數並賦值給 time。

因此,全部未手動修改 time 屬性值的 Calendar 實例的內部,time 的值都是當時系統默認時區的時刻數值。也就是說,getTime 的輸出結果是不會理會當前實例所對應的時區信息的,這也是我以爲 Calendar 設計的一個缺陷所在,由於這樣會致使兩個不一樣時區 Calendar 實例的 getTime 輸出值只取決於實例初始化時系統的運行時刻。

Calendar 中也定義了不少靜態常量和一些屬性數組:

public final static int ERA = 0;

public final static int YEAR = 1;

public final static int MONTH = 2;

public final static int WEEK_OF_YEAR = 3;

public final static int WEEK_OF_MONTH = 4;

public final static int DATE = 5;
....
複製代碼
protected int           fields[];

protected boolean       isSet[];
...
複製代碼

有關日期的全部相關信息都存儲在屬性數組中,而這些靜態常量的值每每表示的就是一個索引值,經過 get 方法,咱們傳入一個屬性索引,返回獲得該屬性的值。例如:

Calendar myCalendar = Calendar.getInstance();
int year = myCalendar.get(Calendar.YEAR);
複製代碼

這裏的 get 方法實際上就是直接取的 fields[1] 做爲返回值,而 fields 屬性數組在 Calendar 實例初始化的時候就已經由系統根據時區和語言計算並賦值了,注意,這裏會根據你指定的時區進行計算,它不像 time 始終是依照的系統默認時區

我的以爲 Calendar 的設計有優雅的地方,也有不合理的地方,畢竟是個「古董」了,終將被替代。

DateFormat 格式化轉換

從咱們以前的一個例子中能夠看到,Calendar 想要輸出一個預期格式的日期信息是很麻煩的,須要本身手動拼接。而咱們的 DateFormat 就是用來處理格式化字符串和日期時間之間的轉換操做的。

DateFormat 和 Calendar 同樣,也是一個抽象類,咱們須要經過工廠方式產生其實例對象,主要有如下幾種工廠方法:

//只處理時間的轉換
public final static DateFormat getTimeInstance()

//只處理日期的轉換
public final static DateFormat getDateInstance()

//既能夠處理時間,也能夠處理日期
public final static DateFormat getDateTimeInstance()
複製代碼

固然,它們各自都有各自的重載方法,具體的咱們待會兒看。

DateFormat 有兩類方法,format 和 parse。

public final String format(Date date)

public Date parse(String source)
複製代碼

format 方法用於將一個日期對象格式化爲字符串,parse 方法用於將一個格式化的字符串裝換爲一個日期對象。例如:

public static void main(String[] args){
    Calendar calendar = Calendar.getInstance();
    DateFormat dateFormat = DateFormat.getDateTimeInstance();
    System.out.println(dateFormat.format(calendar.getTime()));
}
複製代碼

輸出結果:

2018-4-21 16:58:09
複製代碼

顯然,使用工廠構造的 DateFormat 實例並不可以自定義輸出格式化內容,即輸出的字符串格式是固定的,不能知足某些狀況下的特殊需求。通常咱們會直接使用它的一個實現類,SimpleDateFormat。

SimpleDateFormat 容許在構造實例的時候傳入一個 pattern 參數,自定義日期字符的輸出格式。例如:

public static void main(String[] args){    
    DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
    System.out.println(dateFormat.format(new Date()));
}
複製代碼

輸出結果:

2018年04月21日
複製代碼

其中,

  • yyyy:年份用四位進行輸出
  • MM:月份用兩位進行輸出
  • dd:兩位表示日信息
  • HH:兩位來表示小時數
  • mm:兩位表示分鐘數
  • ss:兩位來表示秒數
  • E:表示周幾,若是 Locale 在中國則會輸出 星期x,若是在美國或英國則會輸出英文的星期
  • a:表示上午或下午

固然,對於字符串轉日期也是很方便的,容許自定義模式,但必須遵照本身制定的模式,不然程序將沒法成功解析。例如:

public static void main(String[] args){
    String str = "2018年4月21日 17點17分 星期六";
    DateFormat sDateFormat = new SimpleDateFormat("yyyy年M月dd日 HH點mm分 E");
    sDateFormat.parse(str);
    System.out.println(sDateFormat.getCalendar().getTime());
}
複製代碼

輸出結果:

Sat Apr 21 17:17:00 CST 2018
複製代碼

顯然,程序是正確的解析的咱們的字符串並轉換爲 Calendar 對象存儲在 DateFormat 內部的。

總的來講,Date、Calendar 和 DateFormat 已經可以處理通常的時間日期問題了,可是不可避免的是,它們依然很繁瑣,很差用。

限於篇幅,咱們下篇將對比 Java 8 的新式日期時間 API,你會發現它更加優雅的設計和簡單的操做性。


文章中的全部代碼、圖片、文件都雲存儲在個人 GitHub 上:

(https://github.com/SingleYam/overview_java)

歡迎關注微信公衆號:撲在代碼上的高爾基,全部文章都將同步在公衆號上。

image
相關文章
相關標籤/搜索