除了lambda表達(dá)式,stream以及幾個(gè)小的改進(jìn)之外,Java 8還引入了一套全新的時(shí)間日期API,在本篇教程中我們將通過(guò)幾個(gè)簡(jiǎn)單的任務(wù)示例來(lái)學(xué)習(xí)如何使用Java 8的這套API。Java對(duì)日期,日歷及時(shí)間的處理一直以來(lái)都飽受詬病,尤其是它決定將java.util.Date定義為可修改的以及將SimpleDateFormat實(shí)現(xiàn)成非線程安全的。看來(lái)Java已經(jīng)意識(shí)到需要為時(shí)間及日期功能提供更好的支持了,這對(duì)已經(jīng)習(xí)慣使用Joda時(shí)間日期庫(kù)的社區(qū)而言也是件好事。關(guān)于這個(gè)新的時(shí)間日期庫(kù)的最大的優(yōu)點(diǎn)就在于它定義清楚了時(shí)間日期相關(guān)的一些概念,比方說(shuō),瞬時(shí)時(shí)間(Instant),持續(xù)時(shí)間(duration),日期(date),時(shí)間(time),時(shí)區(qū)(time-zone)以及時(shí)間段(Period)。同時(shí)它也借鑒了Joda庫(kù)的一些優(yōu)點(diǎn),比如將人和機(jī)器對(duì)時(shí)間日期的理解區(qū)分開(kāi)的。Java 8仍然延用了ISO的日歷體系,并且與它的前輩們不同,java.time包中的類是不可變且線程安全的。新的時(shí)間及日期API位于java.time包中,下面是里面的一些關(guān)鍵的類:
1.Instant——它代表的是時(shí)間戳
2.LocalDate——不包含具體時(shí)間的日期,比如2014-01-14。它可以用來(lái)存儲(chǔ)生日,周年紀(jì)念日,入職日期等。
3.LocalTime——它代表的是不含日期的時(shí)間
4.LocalDateTime——它包含了日期及時(shí)間,不過(guò)還是沒(méi)有偏移信息或者說(shuō)時(shí)區(qū)。
5.ZonedDateTime——這是一個(gè)包含時(shí)區(qū)的完整的日期時(shí)間,偏移量是以UTC/格林威治時(shí)間為基準(zhǔn)的。
新的庫(kù)還增加了ZoneOffset及Zoned,可以為時(shí)區(qū)提供更好的支持。有了新的DateTimeFormatter之后日期的解析及格式化也變得煥然一新了。隨便提一句,我是在去年這個(gè)時(shí)候Java正要推出這個(gè)新功能時(shí)寫的這篇文章,所以你會(huì)發(fā)現(xiàn)示例中的時(shí)間都還是去年的。你運(yùn)行下這些例子,它們返回的值肯定都是正確的。
Java8是如何處理時(shí)間及日期的
有人問(wèn)我學(xué)習(xí)一個(gè)新庫(kù)的最佳途徑是什么?我的回答是,就是在實(shí)際項(xiàng)目中那樣去使用它。在一個(gè)真實(shí)的項(xiàng)目中會(huì)有各種各樣的需求,這會(huì)促使開(kāi)發(fā)人員去探索和研究這個(gè)新庫(kù)。簡(jiǎn)言之,只有任務(wù)本身才會(huì)真正促使你去探索及學(xué)習(xí)。java 8的新的日期及時(shí)間API也是一樣。為了學(xué)習(xí)Java 8的這個(gè)新庫(kù),這里我創(chuàng)建了20個(gè)以任務(wù)為導(dǎo)向的例子。我們先從一個(gè)簡(jiǎn)單的任務(wù)開(kāi)始,比如說(shuō)如何用Java 8的時(shí)間日期庫(kù)來(lái)表示今天,接著再進(jìn)一步生成一個(gè)帶時(shí)間及時(shí)區(qū)的完整日期,然后再研究下如何完成一些更實(shí)際的任務(wù),比如說(shuō)開(kāi)發(fā)一個(gè)提醒類的應(yīng)用,來(lái)找出距離一些特定日期比如生日,周日紀(jì)念日,下一個(gè)帳單日,下一個(gè)溢價(jià)日或者信用卡過(guò)期時(shí)間還有多少天。
示例1 如何 在Java 8中獲取當(dāng)天的日期
Java 8中有一個(gè)叫LocalDate的類,它能用來(lái)表示今天的日期。這個(gè)類與java.util.Date略有不同,因?yàn)樗话掌冢瑳](méi)有時(shí)間。因此,如果你只需要表示日期而不包含時(shí)間,就可以使用它。
LocalDate today = LocalDate.now(); System.out.println("Today's Local date : " + today);
Output
Today's Local date : 2014-01-14
你可以看到它創(chuàng)建了今天的日期卻不包含時(shí)間信息。它還將日期格式化完了再輸出出來(lái),不像之前的Date類那樣,打印出來(lái)的數(shù)據(jù)都是未經(jīng)格式化的。
示例2 如何在Java 8中獲取當(dāng)前的年月日
LocalDate類中提供了一些很方便的方法可以用于提取出年月日以及其它的日期屬性。使用這些方法,你可以獲取到任何你所需要的日期屬性,而不再需要使用java.util.Calendar這樣的類了:
LocalDate today = LocalDate.now();
int year = today.getYear();
int month = today.getMonthValue();
int day = today.getDayOfMonth();
System.out.printf("Year : %d Month : %d day : %d \t %n", year, month, day);
Output
Today's Local date : 2014-01-14
Year : 2014 Month : 1 day : 14
可以看到,在Java 8中獲取年月信息非常簡(jiǎn)單,只需使用對(duì)應(yīng)的getter方法就好了,無(wú)需記憶,非常直觀。你可以拿它和Java中老的獲取當(dāng)前年月日的寫法進(jìn)行一下比較。
示例3 在Java 8中如何獲取某個(gè)特定的日期
在第一個(gè)例子中,我們看到通過(guò)靜態(tài)方法now()來(lái)生成當(dāng)天日期是非常簡(jiǎn)單的,不過(guò)通過(guò)另一個(gè)十分有用的工廠方法LocalDate.of(),則可以創(chuàng)建出任意一個(gè)日期,它接受年月日的參數(shù),然后返回一個(gè)等價(jià)的LocalDate實(shí)例。關(guān)于這個(gè)方法還有一個(gè)好消息就是它沒(méi)有再犯之前API中的錯(cuò),比方說(shuō),年只能從1900年開(kāi)始,月必須從0開(kāi)始,等等。這里的日期你寫什么就是什么,比如說(shuō),下面這個(gè)例子中它代表的就是1月14日,沒(méi)有什么隱藏邏輯。
LocalDate dateOfBirth = LocalDate.of(2010, 01, 14);
System.out.println("Your Date of birth is : " + dateOfBirth);
Output : Your Date of birth is : 2010-01-14
可以看出,創(chuàng)建出來(lái)的日期就是我們所寫的那樣,2014年1月14日。
示例4 在Java 8中如何檢查兩個(gè)日期是否相等
如果說(shuō)起現(xiàn)實(shí)中實(shí)際的處理時(shí)間及日期的任務(wù),有一個(gè)常見(jiàn)的就是要檢查兩個(gè)日期是否相等。你可能經(jīng)常會(huì)碰到要判斷今天是不是某個(gè)特殊的日子,比如生日啊,周年紀(jì)念日啊,或者假期之類。有的時(shí)候,會(huì)給你一個(gè)日期,讓你檢查它是不是某個(gè)日子比方說(shuō)假日。下面這個(gè)例子將會(huì)幫助你在Java 8中完成這類任務(wù)。正如你所想的那樣,LocalDate重寫了equals方法來(lái)進(jìn)行日期的比較,如下所示:
LocalDate date1 = LocalDate.of(2014, 01, 14); if(date1.equals(today)){
System.out.printf("Today %s and date1 %s are same date %n", today, date1);
}
Output
today 2014-01-14 and date1 2014-01-14 are same date
在本例中我們比較的兩個(gè)日期是相等的。同時(shí),如果在代碼中你拿到了一個(gè)格式化好的日期串,你得先將它解析成日期然后才能比較。你可以將這個(gè)例子與Java之前比較日期的方式進(jìn)行下比較,你會(huì)發(fā)現(xiàn)它真是爽多了。
示例5 在Java 8中如何檢查重復(fù)事件,比如說(shuō)生日
在Java中還有一個(gè)與時(shí)間日期相關(guān)的實(shí)際任務(wù)就是檢查重復(fù)事件,比如說(shuō)每月的帳單日,結(jié)婚紀(jì)念日,每月還款日或者是每年交保險(xiǎn)費(fèi)的日子。如果你在一家電商公司工作的話,那么肯定會(huì)有這么一個(gè)模塊,會(huì)去給用戶發(fā)送生日祝福并且在每一個(gè)重要的假日給他們捎去問(wèn)候,比如說(shuō)圣誕節(jié),感恩節(jié),在印度則可能是萬(wàn)燈節(jié)(Deepawali)。如何在Java中判斷是否是某個(gè)節(jié)日或者重復(fù)事件?使用MonthDay類。這個(gè)類由月日組合,不包含年信息,也就是說(shuō)你可以用它來(lái)代表每年重復(fù)出現(xiàn)的一些日子。當(dāng)然也有一些別的組合,比如說(shuō)YearMonth類。它和新的時(shí)間日期庫(kù)中的其它類一樣也都是不可變且線程安全的,并且它還是一個(gè)值類(value class)。我們通過(guò)一個(gè)例子來(lái)看下如何使用MonthDay來(lái)檢查某個(gè)重復(fù)的日期:
LocalDate dateOfBirth = LocalDate.of(2010, 01, 14);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(today);
if(currentMonthDay.equals(birthday)){
System.out.println("Many Many happy returns of the day !!");
}else{
System.out.println("Sorry, today is not your birthday");
}
Output: Many Many happy returns of the day !!
雖然年不同,但今天就是生日的那天,所以在輸出那里你會(huì)看到一條生日祝福。你可以調(diào)整下系統(tǒng)的時(shí)間再運(yùn)行下這個(gè)程序看看它是否能提醒你下一個(gè)生日是什么時(shí)候,你還可以試著用你的下一個(gè)生日來(lái)編寫一個(gè)JUnit單元測(cè)試看看代碼能否正確運(yùn)行。
示例6 如何在Java 8中獲取當(dāng)前時(shí)間
這與第一個(gè)例子中獲取當(dāng)前日期非常相似。這次我們用的是一個(gè)叫LocalTime的類,它是沒(méi)有日期的時(shí)間,與LocalDate是近親。這里你也可以用靜態(tài)工廠方法now()來(lái)獲取當(dāng)前時(shí)間。默認(rèn)的格式是hh:mm:ss:nnn,這里的nnn是納秒。可以和Java 8以前如何獲取當(dāng)前時(shí)間做一下比較。
LocalTime time = LocalTime.now(); System.out.println("local time now : " + time);
Output
local time now : 16:33:33.369 // in hour, minutes, seconds, nano seconds
可以看到,當(dāng)前時(shí)間是不包含日期的,因?yàn)長(zhǎng)ocalTime只有時(shí)間,沒(méi)有日期。
示例7 如何增加時(shí)間里面的小時(shí)數(shù)
很多時(shí)候我們需要增加小時(shí),分或者秒來(lái)計(jì)算出將來(lái)的時(shí)間。Java 8不僅提供了不可變且線程安全的類,它還提供了一些更方便的方法譬如plusHours()來(lái)替換原來(lái)的add()方法。順便說(shuō)一下,這些方法返回的是一個(gè)新的LocalTime實(shí)例的引用,因?yàn)長(zhǎng)ocalTime是不可變的,可別忘了存儲(chǔ)好這個(gè)新的引用。
LocalTime time = LocalTime.now();
LocalTime newTime = time.plusHours(2); // adding two hours
System.out.println("Time after 2 hours : " + newTime);
Output :
Time after 2 hours : 18:33:33.369
可以看到當(dāng)前時(shí)間2小時(shí)后是16:33:33.369。現(xiàn)在你可以將它和Java中增加或者減少小時(shí)的老的方式進(jìn)行下比較。一看便知哪種方式更好。
示例8 如何獲取1周后的日期
這與前一個(gè)獲取2小時(shí)后的時(shí)間的例子類似,這里我們將學(xué)會(huì)如何獲取到1周后的日期。LocalDate是用來(lái)表示無(wú)時(shí)間的日期的,它有一個(gè)plus()方法可以用來(lái)增加日,星期,或者月,ChronoUnit則用來(lái)表示這個(gè)時(shí)間單位。由于LocalDate也是不可變的,因此任何修改操作都會(huì)返回一個(gè)新的實(shí)例,因此別忘了保存起來(lái)。
LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS);
System.out.println("Today is : " + today);
System.out.println("Date after 1 week : " + nextWeek);
Output:
Today is : 2014-01-14
Date after 1 week : 2014-01-21
可以看到7天也就是一周后的日期是什么。你可以用這個(gè)方法來(lái)增加一個(gè)月,一年,一小時(shí),一分鐘,甚至是十年,查看下Java API中的ChronoUnit類來(lái)獲取更多選項(xiàng)。
示例9 一年前后的日期
這是上個(gè)例子的續(xù)集。上例中,我們學(xué)習(xí)了如何使用LocalDate的plus()方法來(lái)給日期增加日,周或者月,現(xiàn)在我們來(lái)學(xué)習(xí)下如何用minus()方法來(lái)找出一年前的那天。
LocalDate previousYear = today.minus(1, ChronoUnit.YEARS);
System.out.println("Date before 1 year : " + previousYear);
LocalDate nextYear = today.plus(1, YEARS);
System.out.println("Date after 1 year : " + nextYear);
Output:
Date before 1 year : 2013-01-14
Date after 1 year : 2015-01-14
可以看到現(xiàn)在一共有兩年,一個(gè)是2013年,一個(gè)是2015年,分別是2014的前后那年。
示例10 在Java 8中使用時(shí)鐘
Java 8中自帶了一個(gè)Clock類,你可以用它來(lái)獲取某個(gè)時(shí)區(qū)下當(dāng)前的瞬時(shí)時(shí)間,日期或者時(shí)間。可以用Clock來(lái)替代System.currentTimeInMillis()與 TimeZone.getDefault()方法。
// Returns the current time based on your system clock and set to UTC.
Clock clock = Clock.systemUTC();
System.out.println("Clock : " + clock);
// Returns time based on system clock zone Clock defaultClock =
Clock.systemDefaultZone();
System.out.println("Clock : " + clock);
Output:
Clock : SystemClock[Z]
Clock : SystemClock[Z]
你可以用指定的日期來(lái)和這個(gè)時(shí)鐘進(jìn)行比較,比如下面這樣:
public class MyClass {
private Clock clock; // dependency inject ...
public void process(LocalDate eventDate) {
if(eventDate.isBefore(LocalDate.now(clock)) {
...
}
}
}
如果你需要對(duì)不同時(shí)區(qū)的日期進(jìn)行處理的話這是相當(dāng)方便的。
示例11 在Java中如何判斷某個(gè)日期是在另一個(gè)日期的前面還是后面
這也是實(shí)際項(xiàng)目中常見(jiàn)的一個(gè)任務(wù)。你怎么判斷某個(gè)日期是在另一個(gè)日期的前面還是后面,或者正好相等呢?在Java 8中,LocalDate類有一個(gè)isBefore()和isAfter()方法可以用來(lái)比較兩個(gè)日期。如果調(diào)用方法的那個(gè)日期比給定的日期要早的話,isBefore()方法會(huì)返回true。
LocalDate tomorrow = LocalDate.of(2014, 1, 15); 、if(tommorow.isAfter(today)){
System.out.println("Tomorrow comes after today");
}
LocalDate yesterday = today.minus(1, DAYS);
if(yesterday.isBefore(today)){
System.out.println("Yesterday is day before today");
}
Output:
Tomorrow comes after today
Yesterday is day before today
可以看到在Java 8中進(jìn)行日期比較非常簡(jiǎn)單。不需要再用像Calendar這樣的另一個(gè)類來(lái)完成類似的任務(wù)了。
示例12 在Java 8中處理不同的時(shí)區(qū)
Java 8不僅將日期和時(shí)間進(jìn)行了分離,同時(shí)還有時(shí)區(qū)。現(xiàn)在已經(jīng)有好幾組與時(shí)區(qū)相關(guān)的類了,比如ZonId代表的是某個(gè)特定的時(shí)區(qū),而ZonedDateTime代表的是帶時(shí)區(qū)的時(shí)間。它等同于Java 8以前的GregorianCalendar類。使用這個(gè)類,你可以將本地時(shí)間轉(zhuǎn)換成另一個(gè)時(shí)區(qū)中的對(duì)應(yīng)時(shí)間,比如下面這個(gè)例子:
// Date and time with timezone in Java 8 ZoneId america = ZoneId.of("America/New_York");
LocalDateTime localtDateAndTime = LocalDateTime.now();
ZonedDateTime dateAndTimeInNewYork = ZonedDateTime.of(localtDateAndTime, america );
System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork);
Output :
Current date and time in a particular timezone : 2014-01-14T16:33:33.373-05:00[America/New_York]
可以拿它跟之前將本地時(shí)間轉(zhuǎn)換成GMT時(shí)間的方式進(jìn)行下比較。順便說(shuō)一下,正如Java 8以前那樣,對(duì)應(yīng)時(shí)區(qū)的那個(gè)文本可別弄錯(cuò)了,否則你會(huì)碰到這么一個(gè)異常:
Exception in thread "main" java.time.zone.ZoneRulesException: Unknown time-zone ID: ASIA/Tokyo
at java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:272)
at java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:227)
at java.time.ZoneRegion.ofId(ZoneRegion.java:120)
at java.time.ZoneId.of(ZoneId.java:403)
at java.time.ZoneId.of(ZoneId.java:351)
示例13 如何表示固定的日期,比如信用卡過(guò)期時(shí)間
正如MonthDay表示的是某個(gè)重復(fù)出現(xiàn)的日子的,YearMonth又是另一個(gè)組合,它代表的是像信用卡還款日,定期存款到期日,options到期日這類的日期。你可以用這個(gè)類來(lái)找出那個(gè)月有多少天,lengthOfMonth()這個(gè)方法返回的是這個(gè)YearMonth實(shí)例有多少天,這對(duì)于檢查2月到底是28天還是29天可是非常有用的。
YearMonth currentYearMonth = YearMonth.now(); System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth());
YearMonth creditCardExpiry = YearMonth.of(2018, Month.FEBRUARY);
System.out.printf("Your credit card expires on %s %n", creditCardExpiry);
Output:
Days in month year 2014-01: 31
Your credit card expires on 2018-02
示例14 如何在Java 8中檢查閏年
這并沒(méi)什么復(fù)雜的,LocalDate類有一個(gè)isLeapYear()的方法能夠返回當(dāng)前LocalDate對(duì)應(yīng)的那年是否是閏年。如果你還想重復(fù)造輪子的話,可以看下這段代碼,這是純用Java編寫的判斷某年是否是閏年的邏輯。
if(today.isLeapYear()){
System.out.println("This year is Leap year");
}else {
System.out.println("2014 is not a Leap year");
}
Output: 2014 is not a Leap year
你可以多檢查幾年看看結(jié)果是否正確,最好寫一個(gè)單元測(cè)試來(lái)對(duì)正常年份和閏年進(jìn)行下測(cè)試。
示例15 兩個(gè)日期之間包含多少天,多少個(gè)月
還有一個(gè)常見(jiàn)的任務(wù)就是計(jì)算兩個(gè)給定的日期之間包含多少天,多少周或者多少年。你可以用java.time.Period類來(lái)完成這個(gè)功能。在下面這個(gè)例子中,我們將計(jì)算當(dāng)前日期與將來(lái)的一個(gè)日期之前一共隔著幾個(gè)月。
LocalDate java8Release = LocalDate.of(2014, Month.MARCH, 14);
Period periodToNextJavaRelease =
Period.between(today, java8Release);
System.out.println("Months left between today and Java 8 release : " + periodToNextJavaRelease.getMonths() );
Output:
Months left between today and Java 8 release : 2
可以看到,本月是1月,而Java 8的發(fā)布日期是3月,因此中間隔著2個(gè)月。
示例16 帶時(shí)區(qū)偏移量的日期與時(shí)間
在Java 8里面,你可以用ZoneOffset類來(lái)代表某個(gè)時(shí)區(qū),比如印度是GMT或者UTC5:30,你可以使用它的靜態(tài)方法ZoneOffset.of()方法來(lái)獲取對(duì)應(yīng)的時(shí)區(qū)。只要獲取到了這個(gè)偏移量,你就可以拿LocalDateTime和這個(gè)偏移量創(chuàng)建出一個(gè)OffsetDateTime。
LocalDateTime datetime = LocalDateTime.of(2014, Month.JANUARY, 14, 19, 30);
ZoneOffset offset = ZoneOffset.of("+05:30");
OffsetDateTime date = OffsetDateTime.of(datetime, offset);
System.out.println("Date and Time with timezone offset in Java : " + date);
Output :
Date and Time with timezone offset in Java : 2014-01-14T19:30+05:30
可以看到現(xiàn)在時(shí)間日期與時(shí)區(qū)是關(guān)聯(lián)上了。還有一點(diǎn)就是,OffSetDateTime主要是給機(jī)器來(lái)理解的,如果是給人看的,可以使用ZoneDateTime類。
示例17 在Java 8中如何獲取當(dāng)前時(shí)間戳
如果你還記得在Java 8前是如何獲取當(dāng)前時(shí)間戳的,那現(xiàn)在這簡(jiǎn)直就是小菜一碟了。Instant類有一個(gè)靜態(tài)的工廠方法now()可以返回當(dāng)前時(shí)間戳,如下:
Instant timestamp = Instant.now();
System.out.println("What is value of this instant " + timestamp);
Output :
What is value of this instant 2014-01-14T08:33:33.379Z
可以看出,當(dāng)前時(shí)間戳是包含日期與時(shí)間的,與java.util.Date很類似,事實(shí)上Instant就是Java 8前的Date,你可以使用這兩個(gè)類中的方法來(lái)在這兩個(gè)類型之間進(jìn)行轉(zhuǎn)換,比如Date.from(Instant)是用來(lái)將Instant轉(zhuǎn)換成java.util.Date的,而Date.toInstant()是將Date轉(zhuǎn)換成Instant的。
示例18 如何在Java 8中使用預(yù)定義的格式器來(lái)對(duì)日期進(jìn)行解析/格式化
在Java 8之前,時(shí)間日期的格式化可是個(gè)技術(shù)活,我們的好伙伴SimpleDateFormat并不是線程安全的,而如果用作本地變量來(lái)格式化的話又顯得有些笨重。多虧了線程本地變量,這使得它在多線程環(huán)境下也算有了用武之地,但Java維持這一狀態(tài)也有很長(zhǎng)一段時(shí)間了。這次它引入了一個(gè)全新的線程安全的日期與時(shí)間格式器。它還自帶了一些預(yù)定義好的格式器,包含了常用的日期格式。比如說(shuō),本例 中我們就用了預(yù)定義的BASICISODATE格式,它會(huì)將2014年2月14日格式化成20140114。
String dayAfterTommorrow = "20140116";
LocalDate formatted = LocalDate.parse(dayAfterTommorrow,
DateTimeFormatter.BASIC_ISO_DATE);
System.out.printf("Date generated from String %s is %s %n", dayAfterTommorrow, formatted);
Output :
Date generated from String 20140116 is 2014-01-16
你可以看到生成的日期與指定字符串的值是匹配的,就是日期格式上略有不同。
示例19 如何在Java中使用自定義的格式器來(lái)解析日期
在上例中,我們使用了內(nèi)建的時(shí)間日期格式器來(lái)解析日期字符串。當(dāng)然了,預(yù)定義的格式器的確不錯(cuò)但有時(shí)候你可能還是需要使用自定義的日期格式,這個(gè)時(shí)候你就得自己去創(chuàng)建一個(gè)自定義的日期格式器實(shí)例了。下面這個(gè)例子中的日期格式是”MMM dd yyyy”。你可以給DateTimeFormatter的ofPattern靜態(tài)方法()傳入任何的模式,它會(huì)返回一個(gè)實(shí)例,這個(gè)模式的字面量與前例中是相同的。比如說(shuō)M還是代表月,而m仍是分。無(wú)效的模式會(huì)拋出DateTimeParseException異常,但如果是邏輯上的錯(cuò)誤比如說(shuō)該用M的時(shí)候用成m,這樣就沒(méi)辦法了。
String goodFriday = "Apr 18 2014";
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd yyyy");
LocalDate holiday = LocalDate.parse(goodFriday, formatter);
System.out.printf("Successfully parsed String %s, date is %s%n", goodFriday, holiday);
} catch (DateTimeParseException ex) {
System.out.printf("%s is not parsable!%n", goodFriday);
ex.printStackTrace();
}
Output :
Successfully parsed String Apr 18 2014, date is 2014-04-18
可以看到日期的值與傳入的字符串的確是相符的,只是格式不同。
示例20 如何在Java 8中對(duì)日期進(jìn)行格式化,轉(zhuǎn)換成字符串
在上兩個(gè)例子中,盡管我們用到了DateTimeFormatter類但我們主要是進(jìn)行日期字符串的解析。在這個(gè)例子中我們要做的事情正好相反。這里我們有一個(gè)LocalDateTime類的實(shí)例,我們要將它轉(zhuǎn)換成一個(gè)格式化好的日期串。這是目前為止Java中將日期轉(zhuǎn)換成字符串最簡(jiǎn)單便捷的方式了。下面這個(gè)例子將會(huì)返回一個(gè)格式化好的字符串。與前例相同的是,我們?nèi)孕枋褂弥付ǖ哪J酱?chuàng)建一個(gè)DateTimeFormatter類的實(shí)例,但調(diào)用的并不是LocalDate類的parse方法,而是它的format()方法。這個(gè)方法會(huì)返回一個(gè)代表當(dāng)前日期的字符串,對(duì)應(yīng)的模式就是傳入的DateTimeFormatter實(shí)例中所定義好的。
LocalDateTime arrivalDate = LocalDateTime.now();
try {
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy hh:mm a");
String landing = arrivalDate.format(format);
System.out.printf("Arriving at : %s %n", landing);
} catch (DateTimeException ex) {
System.out.printf("%s can't be formatted!%n", arrivalDate);
ex.printStackTrace();
}
Output : Arriving at : Jan 14 2014 04:33 PM
可以看到,當(dāng)前時(shí)間是用給定的”MMM dd yyyy hh:mm a”模式來(lái)表示的,它包含了三個(gè)字母表示的月份以及用AM及PM來(lái)表示的時(shí)間。
Java 8中日期與時(shí)間API的幾個(gè)關(guān)鍵點(diǎn)
看完了這些例子后,我相信你已經(jīng)對(duì)Java 8這套新的時(shí)間日期API有了一定的了解了。現(xiàn)在我們來(lái)回顧下關(guān)于這個(gè)新的API的一些關(guān)鍵的要素。
1.它提供了javax.time.ZoneId用來(lái)處理時(shí)區(qū)。
2.它提供了LocalDate與LocalTime類
3.Java 8中新的時(shí)間與日期API中的所有類都是不可變且線程安全的,這與之前的Date與Calendar API中的恰好相反,那里面像java.util.Date以及SimpleDateFormat這些關(guān)鍵的類都不是線程安全的。
4.新的時(shí)間與日期API中很重要的一點(diǎn)是它定義清楚了基本的時(shí)間與日期的概念,比方說(shuō),瞬時(shí)時(shí)間,持續(xù)時(shí)間,日期,時(shí)間,時(shí)區(qū)以及時(shí)間段。它們都是基于ISO日歷體系的。
5.每個(gè)Java開(kāi)發(fā)人員都應(yīng)該至少了解這套新的API中的這五個(gè)類:
5.1)Instant 它代表的是時(shí)間戳,比如2014-01-14T02:20:13.592Z,這可以從java.time.Clock類中獲取,像這樣: Instant current = Clock.system(ZoneId.of(“Asia/Tokyo”)).instant();
5.2)LocalDate 它表示的是不帶時(shí)間的日期,比如2014-01-14。它可以用來(lái)存儲(chǔ)生日,周年紀(jì)念日,入職日期等。
5.3)LocalTime – 它表示的是不帶日期的時(shí)間
5.4)LocalDateTime – 它包含了時(shí)間與日期,不過(guò)沒(méi)有帶時(shí)區(qū)的偏移量
5.5)ZonedDateTime – 這是一個(gè)帶時(shí)區(qū)的完整時(shí)間,它根據(jù)UTC/格林威治時(shí)間來(lái)進(jìn)行時(shí)區(qū)調(diào)整
1.這個(gè)庫(kù)的主包是java.time,里面包含了代表日期,時(shí)間,瞬時(shí)以及持續(xù)時(shí)間的類。它有兩個(gè)子package,一個(gè)是java.time.foramt,這個(gè)是什么用途就很明顯了,還有一個(gè)是java.time.temporal,它能從更低層面對(duì)各個(gè)字段進(jìn)行訪問(wèn)。
2.時(shí)區(qū)指的是地球上共享同一標(biāo)準(zhǔn)時(shí)間的地區(qū)。每個(gè)時(shí)區(qū)都有一個(gè)唯一標(biāo)識(shí)符,同時(shí)還有一個(gè)地區(qū)/城市(Asia/Tokyo)的格式以及從格林威治時(shí)間開(kāi)始的一個(gè)偏移時(shí)間。比如說(shuō),東京的偏移時(shí)間就是+09:00。
3.OffsetDateTime類實(shí)際上包含了LocalDateTime與ZoneOffset。它用來(lái)表示一個(gè)包含格林威治時(shí)間偏移量(+/-小時(shí):分,比如+06:00或者 -08:00)的完整的日期(年月日)及時(shí)間(時(shí)分秒,納秒)。
4.DateTimeFormatter類用于在Java中進(jìn)行日期的格式化與解析。與SimpleDateFormat不同,它是不可變且線程安全的,如果需要的話,可以賦值給一個(gè)靜態(tài)變量。DateTimeFormatter類提供了許多預(yù)定義的格式器,你也可以自定義自己想要的格式。當(dāng)然了,根據(jù)約定,它還有一個(gè)parse()方法是用于將字符串轉(zhuǎn)換成日期的,如果轉(zhuǎn)換期間出現(xiàn)任何錯(cuò)誤,它會(huì)拋出DateTimeParseException異常。類似的,DateFormatter類也有一個(gè)用于格式化日期的format()方法,它出錯(cuò)的話則會(huì)拋出DateTimeException異常。
5.再說(shuō)一句,“MMM d yyyy”與“MMm dd yyyy”這兩個(gè)日期格式也略有不同,前者能識(shí)別出”Jan 2 2014″與”Jan 14 2014″這兩個(gè)串,而后者如果傳進(jìn)來(lái)的是”Jan 2 2014″則會(huì)報(bào)錯(cuò),因?yàn)樗谕路萏巶鬟M(jìn)來(lái)的是兩個(gè)字符。為了解決這個(gè)問(wèn)題,在天為個(gè)位數(shù)的情況下,你得在前面補(bǔ)0,比如”Jan 2 2014″應(yīng)該改為”Jan 02 2014″。
關(guān)于Java 8這個(gè)新的時(shí)間日期API就講到這了。這幾個(gè)簡(jiǎn)短的示例 對(duì)于理解這套新的API中的一些新增類已經(jīng)足夠了。由于它是基于實(shí)際任務(wù)來(lái)講解的,因此后面再遇到Java中要對(duì)時(shí)間與日期進(jìn)行處理的工作時(shí),就不用再四處尋找了。我們學(xué)習(xí)了如何創(chuàng)建與修改日期實(shí)例。我們還了解了純?nèi)掌冢掌诩訒r(shí)間,日期加時(shí)區(qū)的區(qū)別,知道如何比較兩個(gè)日期,如何找到某天到指定日期比如說(shuō)下一個(gè)生日,周年紀(jì)念日或者保險(xiǎn)日還有多少天。我們還學(xué)習(xí)了如何在Java 8中用線程安全的方式對(duì)日期進(jìn)行解析及格式化,而無(wú)需再使用線程本地變量或者第三方庫(kù)這種取巧的方式。新的API能勝任任何與時(shí)間日期相關(guān)的任務(wù)。