Java中為處理日期和時間提供了大量的API,確實有把一件簡單的事情搞復雜的嫌疑,各種類:Date Time Timestamp Calendar...,但是如果能夠看到時間處理的本質就可以輕松hold住這堆東西了。
常用的類
表示類
java.util.Date:能夠准確記錄到毫秒級別的時間表示類,但是其中的各種get set(修改時間或者獲取時間中某一個特殊參數)都已經被廢棄。java.sql.Date:為數據庫提供的日期類,繼承自util包中的Date,但是這個類只能夠操作日期,不能讀取或者修改時間。sql和util中Date內部進行存儲的long,都可以保存到毫秒級別java.sql.Time:為數據庫提供的時間類,和Date相反,它只能獲取和操作時間相關的信息。java.sql.Timestamp:時間戳,繼承util.Date,它不僅能夠完美支持util.Date的功能,而且可以支持到納秒級別(10^-9 s)。
工具類
Calendar:主要用來操作一個Date類型,提供了一系列接口來獲取或修改其中的信息。TimeZone:用來配合Calendar操作Date,主要是考慮時區的問題,值得注意的是,在Date中存儲的信息是一個絕對標准時間(稍后說明),而如果需要進行時區的轉化,那么只需要配合此類即可。SimpleDateFormat:常用的格式化Date的工具,主要是進行String和日期之間的互換。
基本概念
時間的來源
注意,這里並不是討論一個哲學的問題,在大部分的編程語言中,我們都是采用從1970-01-01 00:00:00.000 開始至今的毫秒差作為表示時間的數值,這個時間是絕對公立的,它和時區沒有任何關系。在Java中任何時間的表示類的底層存儲的毫秒數都是一個這樣的標准時間。
在java中獲取當前時間接口是System.currentTimeMillis()。
值得一提的是Java還提供了一個更加精確的時間:System.nanoTime(),獲取一個時間精確到納秒,但是它並不是一個當前的精確時間,而是JVM提供的一個時間,它的主要用途是來精確衡量兩個時間段之間的時間,如計算一段代碼的執行時間:
long startTime = System.nanoTime();
// ... the code being measured ...
long estimatedTime = System.nanoTime() - startTime;
可以比較兩個接口返回的內容:
System.currentTimeMillis():1429108246639
System.nanoTime():1429108246640(ms)089000------->多了6位
UTC和GMT
這兩個標准唯一不同之處在於UTC是基於GMT進行微調之后的一個時間,本文不去深究這兩者的差別,在此認為這兩者是一個東西。
初中地理教過我們,地球是24個時區,東部和西部各12個,時區的基准點是倫敦(基准UTC),往東,會領先UTC,往西,會落后UTC。
如北京屬於東八區,那么我們的時間會領先基准,也就是我們在早上9點時,倫敦是早上1點。如果我們在不同時區接發郵件的時候,可以發現這個問題。
這個時間是我收到一份來自華盛頓的郵件的時間:
2014年1月23日(星期四) 晚上7:29 (UTC-05:00 華盛頓、多倫多、古巴、智利時間)
這里我們可以在郵件時間后面發現UTC-05:00,說明這里是落后UTC基准5個小時。注意,前面的時間是發件人的本地時間,如果轉化成北京所在時區的時間應該是加上13h,那我收到這封郵件的本地時間是:2014-01-24 星期五早上8:29。
再談TimeStamp
前面說了,TimeStamp能夠精確到納秒,那它是怎么做到的呢?由於TimeStamp繼承自Date,它把整數秒存儲在超類中,而在子類中專門用一個long類型存儲零的秒數:nanos
需要注意,除非你顯示去調用TimeStamp的這個構造器:
public Timestamp(int year, int month, int date, int hour, int minute, int second, int nano)
顯示去指定nano的值,否則這個構造器的參數
public Timestamp(long time)
的單位實際上是毫秒。
API的使用
最后再來說說日期時間的操作接口,過程基本如下圖:
SimpleDateFormat <------> Date <---------> Calendar
Date負責存儲一個絕對時間,並對兩邊提供操作接口。Calendar負責對Date中特定信息,比如這個時間是改年的第幾個星期,此外,還可以通過set,add,roll接口來進行日期時間的增減。SimpleDateFormat主要作為一些格式化的輸入輸出。
SimpleDateFormat
SimpleDateFormat的構造器接受一個String pattern,其中的pattern是預定義的:
G 年代標志符
y 年
M 月
d 日
h 時 在上午或下午 (1~12)
H 時 在一天中 (0~23)
m 分
s 秒
S 毫秒
E 星期
D 一年中的第幾天
F 一月中第幾個星期幾
w 一年中第幾個星期
W 一月中第幾個星期
a 上午 / 下午 標記符
k 時 在一天中 (1~24)
K 時 在上午或下午 (0~11)
z 時區
例子1:
SimpleDateFormat DATETIME_FORMATER_WITHWEEK = new SimpleDateFormat(
"yyyy-MM-dd E HH:mm");
java.util.Date date = new java.util.Date();
System.out.println(DATETIME_FORMATER_WITHWEEK.format(date));
//output: 2015-04-15 星期三 23:59
//當然,反過來,我也可以使用這個format將output的字符串轉化成Date
Calendar
Calendar中主要需要了解的各種操作域,感覺這也是Java在做這個API時的一個敗筆,靈活有余,可控性不足,初學者如果亂用域,將會產生各種bug。至於每一個域對應的時間分量,請自行google。
一些常用的filed:
- YEAR:年
- MONTH:月(從0 開始,0 表示1月....11表示12月)
- DAY_OF_MONTH :幾號(等同DATE)
- DAY_OF_WEEK:星期幾
- DAY_OF_YEAR:年里面的天
- DATE:幾號(等同DAY_OF_MONTH)
一個filed通常來說對應了日期時間中的某一個分量,在操作這個分類有些操作會向高位進位,而有的操作則不會【bug高發區域】。
例子2:
SimpleDateFormat DATETIME_FORMATER_WITHWEEK = new SimpleDateFormat(
"yyyy-MM-dd E");
Calendar calendarT = Calendar.getInstance(Locale.CHINA);
System.out.println(DATETIME_FORMATER_WITHWEEK.format(calendarT.getTime()));
calendarT.set(Calendar.MONTH,12);// 月份進位
System.out.println(DATETIME_FORMATER_WITHWEEK.format(calendarT.getTime()));
//output: 2015-04-16 星期四
2016-01-16 星期六
例子3:
SimpleDateFormat DATETIME_FORMATER_WITHWEEK = new SimpleDateFormat(
"yyyy-MM-dd E");
Calendar calendarT = Calendar.getInstance(Locale.CHINA);
System.out.println("原始 :"+DATETIME_FORMATER_WITHWEEK.format(calendarT.getTime()));
calendarT.set(Calendar.DAY_OF_MONTH,1);// 當月第一天
System.out.println("當月第一天:"+DATETIME_FORMATER_WITHWEEK.format(calendarT.getTime()));
//roll不會進位
calendarT.roll(Calendar.DATE,-1);
System.out.println("roll -1:"+DATETIME_FORMATER_WITHWEEK.format(calendarT.getTime()));
calendarT.set(Calendar.DAY_OF_MONTH,1);// 當月第一天
calendarT.add(Calendar.DATE,-1);
//add產生進位
System.out.println("add -1:"+DATETIME_FORMATER_WITHWEEK.format(calendarT.getTime()));
//output:原始 :2015-04-16 星期四
當月第一天:2015-04-01 星期三
roll -1:2015-04-30 星期四
add -1:2015-03-31 星期二
最后還是需要說明一點,獲取當前時間指的是當前本地時間對應的UTC時間,和時區沒有關系!有點繞,沒關系看代碼:
例子4:
Calendar calendar1 = Calendar.getInstance(Locale.CHINA);
Calendar calendar2 = Calendar.getInstance(Locale.GERMAN);
System.out.println(calendar1.getTimeInMillis());
System.out.println(calendar2.getTimeInMillis());
//output:
1429115150117
1429115150117
最后貼一個日期處理開源庫:joda-time
