Java中的時(shí)間日期API知識(shí)點(diǎn)總結(jié)
自從 14 年發(fā)布 Java 8 以后,我們古老 java.util.Date 終于不再是我們 Java 里操作日期時(shí)間的唯一的選擇。
其實(shí) Java 里的日期時(shí)間的相關(guān) API 一直為世猿詬病,不僅在于它設(shè)計(jì)分上工不明確,往往一個(gè)類(lèi)既能處理日期又能處理時(shí)間,很混亂,還在于某些年月日期的數(shù)值映射存儲(chǔ)反人類(lèi),例如:0 對(duì)應(yīng)月份一月,11 對(duì)應(yīng)月份十二月,118 對(duì)應(yīng)年份 2018(1900 + 118)等。
往往我們得到某個(gè)年月值還需要再做相應(yīng)的運(yùn)算才能得到準(zhǔn)確的年月日信息,直到我們的 Java 8 ,借鑒了第三方開(kāi)源庫(kù) Joda-Time 的優(yōu)秀設(shè)計(jì),重新設(shè)計(jì)了一個(gè)日期時(shí)間 API,相比之前,可以說(shuō)好用百倍,相關(guān) API 接口全部位于包 java.time 下。
古老的日期時(shí)間接口
表示時(shí)刻信息的 Date
世界上所有的計(jì)算機(jī)內(nèi)部存儲(chǔ)時(shí)間都使用一個(gè) long 類(lèi)型的整數(shù),而這個(gè)整數(shù)的值就是相對(duì)于英國(guó)格林尼治標(biāo)準(zhǔn)時(shí)間(1970年1月1日0時(shí)0分0秒)的毫秒數(shù)。例如:
public static void main(String[] args){ //January 1, 1970 00:00:00 GMT. Date date = new Date(1000); System.out.println(date); }
輸出結(jié)果:
//1970-1-1 8:00:01 Thu Jan 01 08:00:01 CST 1970
很多人可能會(huì)疑惑,1000 表示的是距離標(biāo)準(zhǔn)時(shí)間往后 1 秒,那為什么時(shí)間卻多走了 八個(gè)小時(shí)?
這和「時(shí)區(qū)」有關(guān)系,如果你位于英國(guó)的格林尼治區(qū),那么結(jié)果會(huì)如預(yù)想一樣,但是我們位于中國(guó)東八區(qū),時(shí)間要早八個(gè)小時(shí),所以不同時(shí)區(qū)基于的基礎(chǔ)值不同。
Date 這個(gè)類(lèi)以前真的扮演過(guò)很多角色,從它的源碼就可以看出來(lái),有可以操作時(shí)刻的方法,有可以操作年月日的方法,甚至它還能管時(shí)區(qū)。可以說(shuō),日期時(shí)間的相關(guān)操作有它一個(gè)人就足夠了。
但這個(gè)世界就是這樣,你管的東西多了,自然就不能面面俱到,Date 中很多方法的設(shè)計(jì)并不是很合理,之前我們也說(shuō)了,甚至有點(diǎn)反人類(lèi)。所以,現(xiàn)在的 Date 類(lèi)中接近百分之八十的方法都已廢棄,被標(biāo)記為 @Deprecated。
sun 公司給 Date 的目前定位是,唯一表示一個(gè)時(shí)刻,所以它的內(nèi)部應(yīng)該圍繞著那個(gè)整型的毫秒,而不再著重于各種年歷時(shí)區(qū)等信息。
Date 允許通過(guò)以下兩種構(gòu)造器實(shí)例化一個(gè)對(duì)象:
private transient long fastTime; public Date() { this(System.currentTimeMillis()); } public Date(long date) { fastTime = date; }
這里的 fastTime 屬性存儲(chǔ)的就是時(shí)刻所對(duì)應(yīng)的毫秒數(shù),兩個(gè)構(gòu)造器還是很簡(jiǎn)單,如果調(diào)用的是無(wú)參構(gòu)造器,那么虛擬機(jī)將以系統(tǒng)當(dāng)前的時(shí)刻值對(duì) fastTime 進(jìn)行賦值。
還有幾個(gè)為數(shù)不多沒(méi)有被廢棄的方法:
- public long getTime() :返回內(nèi)部存儲(chǔ)的毫秒數(shù)
- public void setTime(long time):重新設(shè)置內(nèi)存的毫秒數(shù)
- public boolean before(Date when):比較給定的時(shí)刻是否早于當(dāng)前 Date 實(shí)例
- public boolean after(Date when):比較給定的時(shí)刻是否晚于當(dāng)前 Date 實(shí)例
還有兩個(gè)方法是 jdk1.8 以后新增的,用于向 Java 8 新增接口的轉(zhuǎn)換,待會(huì)介紹。
描述年歷的 Calendar
Calendar 用于表示年月日等日期信息,它是一個(gè)抽象類(lèi),所以一般通過(guò)以下四種工廠方法獲取它的實(shí)例對(duì)象。
public static Calendar getInstance() public static Calendar getInstance(TimeZone zone) public static Calendar getInstance(Locale aLocale) public static Calendar getInstance(TimeZone zone,Locale aLocale)
其實(shí)內(nèi)部最終會(huì)調(diào)用同一個(gè)內(nèi)部方法:
private static Calendar createCalendar(TimeZone zone,Locale aLocale)
該方法需要兩個(gè)參數(shù),一個(gè)是時(shí)區(qū),一個(gè)是國(guó)家和語(yǔ)言,也就是說(shuō),構(gòu)建一個(gè) Calendar 實(shí)例最少需要提供這兩個(gè)參數(shù)信息,否則將會(huì)使用系統(tǒng)默認(rèn)的時(shí)區(qū)或語(yǔ)言信息。
因?yàn)椴煌臅r(shí)區(qū)與國(guó)家語(yǔ)言對(duì)于時(shí)刻和年月日信息的輸出是不同的,所以這也是為什么一個(gè) Calendar 實(shí)例必須傳入時(shí)區(qū)和國(guó)家信息的一個(gè)原因。看個(gè)例子:
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)); }
輸出結(jié)果:
Sat Apr 21 10:32:20 CST 2018 2018:2:32
可以看到,第一個(gè)輸出為我們系統(tǒng)默認(rèn)時(shí)區(qū)與國(guó)家的當(dāng)前時(shí)間,而第二個(gè) Calendar 實(shí)例我們指定了它位于格林尼治時(shí)區(qū)(0 時(shí)區(qū)),結(jié)果也顯而易見(jiàn)了,相差了八個(gè)小時(shí),那是因?yàn)槲覀兾挥跂|八區(qū),時(shí)間早于 0 時(shí)區(qū)八個(gè)小時(shí)。
可能有人會(huì)疑惑了,為什么第二個(gè) Calendar 實(shí)例的輸出要如此復(fù)雜的拼接,而不像第一個(gè) Calendar 實(shí)例那樣直接調(diào)用 getTime 方法簡(jiǎn)潔呢?
這涉及到 Calendar 的內(nèi)部實(shí)現(xiàn),我們一起看看:
protected long time; public final Date getTime() { return new Date(getTimeInMillis()); }
和 Date 一樣,Calendar 的內(nèi)部也維護(hù)著一個(gè)時(shí)刻信息,而 getTime 方法實(shí)際上是根據(jù)這個(gè)時(shí)刻構(gòu)建了一個(gè) Date 對(duì)象并返回的。
而一般我們構(gòu)建 Calendar 實(shí)例的時(shí)候都不會(huì)傳入一個(gè)時(shí)刻信息,所以這個(gè) time 的值在實(shí)例初始化的時(shí)候,程序會(huì)根據(jù)系統(tǒng)默認(rèn)的時(shí)區(qū)和當(dāng)前時(shí)間計(jì)算得到一個(gè)毫秒數(shù)并賦值給 time。
所以,所有未手動(dòng)修改 time 屬性值的 Calendar 實(shí)例的內(nèi)部,time 的值都是當(dāng)時(shí)系統(tǒng)默認(rèn)時(shí)區(qū)的時(shí)刻數(shù)值。也就是說(shuō),getTime 的輸出結(jié)果是不會(huì)理會(huì)當(dāng)前實(shí)例所對(duì)應(yīng)的時(shí)區(qū)信息的,這也是我覺(jué)得 Calendar 設(shè)計(jì)的一個(gè)缺陷所在,因?yàn)檫@樣會(huì)導(dǎo)致兩個(gè)不同時(shí)區(qū) Calendar 實(shí)例的 getTime 輸出值只取決于實(shí)例初始化時(shí)系統(tǒng)的運(yùn)行時(shí)刻。
Calendar 中也定義了很多靜態(tài)常量和一些屬性數(shù)組:
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[]; ...
有關(guān)日期的所有相關(guān)信息都存儲(chǔ)在屬性數(shù)組中,而這些靜態(tài)常量的值往往表示的就是一個(gè)索引值,通過(guò) get 方法,我們傳入一個(gè)屬性索引,返回得到該屬性的值。例如:
Calendar myCalendar = Calendar.getInstance(); int year = myCalendar.get(Calendar.YEAR);
這里的 get 方法實(shí)際上就是直接取的 fields[1] 作為返回值,而 fields 屬性數(shù)組在 Calendar 實(shí)例初始化的時(shí)候就已經(jīng)由系統(tǒng)根據(jù)時(shí)區(qū)和語(yǔ)言計(jì)算并賦值了,注意,這里會(huì)根據(jù)你指定的時(shí)區(qū)進(jìn)行計(jì)算,它不像 time 始終是依照的系統(tǒng)默認(rèn)時(shí)區(qū)。
個(gè)人覺(jué)得 Calendar 的設(shè)計(jì)有優(yōu)雅的地方,也有不合理的地方,畢竟是個(gè)「古董」了,終將被替代。
DateFormat 格式化轉(zhuǎn)換
從我們之前的一個(gè)例子中可以看到,Calendar 想要輸出一個(gè)預(yù)期格式的日期信息是很麻煩的,需要自己手動(dòng)拼接。而我們的 DateFormat 就是用來(lái)處理格式化字符串和日期時(shí)間之間的轉(zhuǎn)換操作的。
DateFormat 和 Calendar 一樣,也是一個(gè)抽象類(lèi),我們需要通過(guò)工廠方式產(chǎn)生其實(shí)例對(duì)象,主要有以下幾種工廠方法:
//只處理時(shí)間的轉(zhuǎn)換 public final static DateFormat getTimeInstance() //只處理日期的轉(zhuǎn)換 public final static DateFormat getDateInstance() //既可以處理時(shí)間,也可以處理日期 public final static DateFormat getDateTimeInstance()
當(dāng)然,它們各自都有各自的重載方法,具體的我們待會(huì)兒看。
DateFormat 有兩類(lèi)方法,format 和 parse。
public final String format(Date date) public Date parse(String source)
format 方法用于將一個(gè)日期對(duì)象格式化為字符串,parse 方法用于將一個(gè)格式化的字符串裝換為一個(gè)日期對(duì)象。例如:
public static void main(String[] args){ Calendar calendar = Calendar.getInstance(); DateFormat dateFormat = DateFormat.getDateTimeInstance(); System.out.println(dateFormat.format(calendar.getTime())); }
輸出結(jié)果:
2018-4-21 16:58:09
顯然,使用工廠構(gòu)造的 DateFormat 實(shí)例并不能夠自定義輸出格式化內(nèi)容,即輸出的字符串格式是固定的,不能滿(mǎn)足某些情況下的特殊需求。一般我們會(huì)直接使用它的一個(gè)實(shí)現(xiàn)類(lèi),SimpleDateFormat。
SimpleDateFormat 允許在構(gòu)造實(shí)例的時(shí)候傳入一個(gè) pattern 參數(shù),自定義日期字符的輸出格式。例如:
public static void main(String[] args){ DateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日"); System.out.println(dateFormat.format(new Date())); }
輸出結(jié)果:
2018年04月21日
其中,
- yyyy:年份用四位進(jìn)行輸出
- MM:月份用兩位進(jìn)行輸出
- dd:兩位表示日信息
- HH:兩位來(lái)表示小時(shí)數(shù)
- mm:兩位表示分鐘數(shù)
- ss:兩位來(lái)表示秒數(shù)
- E:表示周幾,如果 Locale 在中國(guó)則會(huì)輸出 星期x,如果在美國(guó)或英國(guó)則會(huì)輸出英文的星期
- a:表示上午或下午
當(dāng)然,對(duì)于字符串轉(zhuǎn)日期也是很方便的,允許自定義模式,但必須遵守自己制定的模式,否則程序?qū)o(wú)法成功解析。例如:
public static void main(String[] args){ String str = "2018年4月21日 17點(diǎn)17分 星期六"; DateFormat sDateFormat = new SimpleDateFormat("yyyy年M月dd日 HH點(diǎn)mm分 E"); sDateFormat.parse(str); System.out.println(sDateFormat.getCalendar().getTime()); }
輸出結(jié)果:
Sat Apr 21 17:17:00 CST 2018
顯然,程序是正確的解析的我們的字符串并轉(zhuǎn)換為 Calendar 對(duì)象存儲(chǔ)在 DateFormat 內(nèi)部的。
總的來(lái)說(shuō),Date、Calendar 和 DateFormat 已經(jīng)能夠處理一般的時(shí)間日期問(wèn)題了,但是不可避免的是,它們依然很繁瑣,不好用。
限于篇幅,我們下篇將對(duì)比 Java 8 的新式日期時(shí)間 API,你會(huì)發(fā)現(xiàn)它更加優(yōu)雅的設(shè)計(jì)和簡(jiǎn)單的操作性。
相關(guān)文章
Mybatis foreach標(biāo)簽使用不當(dāng)導(dǎo)致異常的原因淺析
這篇文章主要介紹了Mybatis foreach標(biāo)簽使用不當(dāng)導(dǎo)致異常的原因探究,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-12-12Mybatis中<if>和<choose>的區(qū)別及“=”判斷方式
這篇文章主要介紹了Mybatis中<if>和<choose>的區(qū)別及“=”判斷方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06HttpsURLConnection上傳文件流(實(shí)例講解)
下面小編就為大家?guī)?lái)一篇HttpsURLConnection上傳文件流(實(shí)例講解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-07-07Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(36)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07springboot優(yōu)雅獲取前端參數(shù)的方法詳解
現(xiàn)在的項(xiàng)目基本上都是前后端分離的項(xiàng)目,如何打通前后端,接收前端傳過(guò)來(lái)的參數(shù)呢,這篇文章小編就來(lái)和大家詳細(xì)介紹一下springboot如何優(yōu)雅的獲取前端參數(shù)吧2024-03-03spring中JdbcTemplate操作oracle的存儲(chǔ)過(guò)程實(shí)例代碼
JdbcTemplate是Spring對(duì)JDBC的封裝,目的是使JDBC更加易于使用,JdbcTemplate是Spring的一部分,下面這篇文章主要給大家介紹了關(guān)于spring中JdbcTemplate操作oracle的存儲(chǔ)過(guò)程的相關(guān)資料,需要的朋友可以參考下2023-04-04