自屎诘巍:java8
用了好久了慰于,雖然一直知道有新的時(shí)間API
祖驱,但是一直用java.util.Date
用習(xí)慣了秒啦,也沒有特意的去了解java8
的時(shí)間類java.time
秽澳,雖然效果同樣達(dá)到了吝沫,但不是當(dāng)前最適當(dāng)?shù)姆绞脚朊蓿院笠〗逃?xùn)慷蠕。
在java8中,java.time
包下主要包含下面幾個(gè)主要的類:
Instant:時(shí)間戳厘托,相當(dāng)于java.util的Date
LocalDate:只包含日期友雳,比如:2016-10-20
LocalTime:只包含時(shí)間,比如:23:12:10
LocalDateTime:包含日期和時(shí)間铅匹,比如:2016-10-20 23:14:21
Duration:計(jì)算兩個(gè)“時(shí)間”的間隔
Period:用于計(jì)算兩個(gè)“日期”的間隔
ZoneOffset:時(shí)區(qū)偏移量押赊,比如:+8:00
ZonedDateTime:可以得到特定時(shí)區(qū)的日期/時(shí)間
Clock:時(shí)鐘,比如獲取目前美國紐約的時(shí)間
時(shí)間格式化以DateTimeFormatter
代替SimpleDateFormat
DateTimeFormatter:時(shí)間格式化
方法前綴的含義,統(tǒng)一了api:
of:靜態(tài)工廠方法(用類名去調(diào)用)包斑。
parse:靜態(tài)工廠方法流礁,關(guān)注于解析(用類名去調(diào)用)。
now: 靜態(tài)工廠方法罗丰,用當(dāng)前時(shí)間創(chuàng)建實(shí)例(用類名去調(diào)用)
get:獲取某些東西的值神帅。
is:檢查某些東西的是否是true。
with:返回一個(gè)部分狀態(tài)改變了的時(shí)間日期對(duì)象拷貝(單獨(dú)一個(gè)with方法,參數(shù)為TemporalAdjusters類型)萌抵。
plus:返回一個(gè)時(shí)間增加了的找御、時(shí)間日期對(duì)象拷貝(如果參數(shù)是負(fù)數(shù)也能夠有minus方法的效果)。
minus:返回一個(gè)時(shí)間減少了的谜嫉、時(shí)間日期對(duì)象拷貝萎坷。
to:把當(dāng)前時(shí)間日期對(duì)象轉(zhuǎn)換成另外一個(gè),可能會(huì)損失部分狀態(tài)沐兰。
at:把這個(gè)對(duì)象與另一個(gè)對(duì)象組合起來哆档,例如: date.atTime(time)。
format :根據(jù)某一個(gè)DateTimeFormatter格式化為字符串住闯。
通過以上一系列方法瓜浸,java.time
完成了加、減比原、格式化插佛、解析、從日期/時(shí)間中提取單獨(dú)部分等任務(wù)量窘。
java.time
包里面的類實(shí)例如果用了上面的方法而被修改了,那么會(huì)返回一個(gè)新的實(shí)例過來,而不像Calendar那樣可以在同一個(gè)實(shí)例進(jìn)行不同的修改,體現(xiàn)了不可變雇寇。
下面講解具體的類
Instant :時(shí)間戳,相當(dāng)于java.util的Date
Instant
用于表示一個(gè)時(shí)間戳蚌铜,它與我們常使用的System.currentTimeMillis()
有些類似锨侯,不過Instant
可以精確到納秒(Nano-Second
),System.currentTimeMillis()
方法只精確到毫秒(Milli-Second)冬殃。如果查看Instant
源碼囚痴,發(fā)現(xiàn)它的內(nèi)部使用了兩個(gè)常量,seconds
表示從1970-01-01 00:00:00
開始到現(xiàn)在的秒數(shù)审葬,nanos
表示納秒部分(nanos
的值不會(huì)超過999,999,999
)深滚。Instant
除了使用now()
方法創(chuàng)建外奕谭,還可以通過ofEpochSecond
方法創(chuàng)建:
Instant instant = Instant.ofEpochSecond(120, 100000);
ofEpochSecond()
方法的第一個(gè)參數(shù)為秒,第二個(gè)參數(shù)為納秒痴荐,上面的代碼表示從1970-01-01 00:00:00開始后兩分鐘的10萬納秒的時(shí)刻血柳,控制臺(tái)上的輸出為:
1970-01-01T00:02:00.000100Z
Duration : 計(jì)算兩個(gè)“時(shí)間”的間隔
這個(gè)很好理解,看下面的了栗子就懂了
LocalDateTime from = LocalDateTime.of(2019, Month.JANUARY, 21, 15, 56, 0); // 2019-01-21 15:56:00
LocalDateTime to = LocalDateTime.of(2019, Month.FEBRUARY, 21, 15, 56, 0); // 2019-02-21 15:56:00
Duration duration = Duration.between(from, to); // 表示從 2019-01-21 15:56:00 到 2019-02-21 15:56:00
long days = duration.toDays(); // 這段時(shí)間的總天數(shù)
long hours = duration.toHours(); // 這段時(shí)間的小時(shí)數(shù)
long minutes = duration.toMinutes(); // 這段時(shí)間的分鐘數(shù)
long seconds = duration.getSeconds(); // 這段時(shí)間的秒數(shù)
long milliSeconds = duration.toMillis(); // 這段時(shí)間的毫秒數(shù)
long nanoSeconds = duration.toNanos(); // 這段時(shí)間的納秒數(shù)
Duration
對(duì)象還可以通過of()
方法創(chuàng)建蹬昌,該方法接受一個(gè)時(shí)間段長度混驰,和一個(gè)時(shí)間單位作為參數(shù)
Duration duration1 = Duration.of(5, ChronoUnit.DAYS); // 5天
Duration duration2 = Duration.of(1000, ChronoUnit.MILLIS); // 1000毫秒
Period : 用于計(jì)算兩個(gè)“日期”的間隔
Period
在概念上和Duration
類似攀隔,區(qū)別在于Period
是以年月日來衡量一個(gè)時(shí)間段皂贩,比如2年3個(gè)月6天
Period period = Period.of(2, 3, 6);
由于Period
是以年月日衡量時(shí)間段,所以between()方法只能接收LocalDate
類型的參數(shù):
// 2019-01-21 到 2019-02-21 這段時(shí)間
Period period = Period.between(
LocalDate.of(2019, 1, 21),
LocalDate.of(2019, 2, 21));
ZoneId : 時(shí)區(qū)
獲取所有合法的“區(qū)域/城市”字符串 :
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
獲取系統(tǒng)默認(rèn)時(shí)區(qū) :
ZoneId systemZoneId = ZoneId.systemDefault();
創(chuàng)建時(shí)區(qū) :
ZoneId shanghaiZoneId = ZoneId.of("Africa/Bangui");
LocalDateTime:包含日期和時(shí)間昆汹,比如:2016-10-20 23:14:21
獲取當(dāng)前時(shí)間 :
LocalDateTime localDateTime = LocalDateTime.now();//2019-01-21T16:15:52.863
創(chuàng)建特定日期 (多種自定義):
LocalDateTime localDateTime = LocalDateTime.of(2019,01,21,16,22,34);
獲取獲取年明刷、月、日信息 :
LocalDateTime.now().getYear();//2019
LocalDateTime.now().getMonth();//JANUARY
LocalDateTime.now().getDayOfYear();//21
LocalDateTime.now().getDayOfMonth();//21
LocalDateTime.now().getDayOfWeek();//MONDAY
LocalDateTime.now().getHour();//16
能夠自定義時(shí)間 :
LocalDateTime time = LocalDateTime.of(2017, 1, 1, 1, 1,1);
System.out.println(time); //2017-01-01T01:01:01
//使用plus方法增加年份
//改變時(shí)間后會(huì)返回一個(gè)新的實(shí)例nextYearTime
LocalDateTime nextYearTime = time.plusYears(1);
System.out.println(nextYearTime); //2018-01-01T01:01:01
//使用minus方法減年份
LocalDateTime time = LocalDateTime.of(2017, 1, 1, 1, 1,1);
LocalDateTime lastYearTime = time.minusYears(1);
System.out.println(lastYearTime); //2016-01-01T01:01:01
//使用with方法設(shè)置月份
LocalDateTime time = LocalDateTime.of(2017, 1, 1, 1, 1,1);
LocalDateTime changeTime = time.withMonth(12);
System.out.println(changeTime); //2017-12-01T01:01:01
//判斷當(dāng)前年份是否閏年
System.out.println("isLeapYear :" + time.isLeapYear());
//判斷當(dāng)前日期屬于星期幾
LocalDateTime time = LocalDateTime.now();
DayOfWeek dayOfWeek = time.getDayOfWeek();
System.out.println(dayOfWeek); //WEDNESDAY
LocalDate
和LocalTime
與LocalDateTime
類似满粗,不多說
其他使用場景:
判斷兩個(gè)日期是否相等
LocalDate date1 = LocalDate.of(2019, 01, 21);
if(date1.equals(LocalDate.now())){
System.out.printf("Today %s and date1 %s are same date %n", LocalDate.now(), date1);
}
//輸出:Today 2019-01-21 and date1 2019-01-21 are same date
檢查像生日這種周期性事件
類似每月賬單辈末、結(jié)婚紀(jì)念日、保險(xiǎn)繳費(fèi)日這些周期性事件映皆。使用MonthDay
類挤聘。這個(gè)類組合了月份和日,去掉 了年捅彻,這意味著你可以用它判斷每年都會(huì)發(fā)生事件组去。
LocalDate dateOfBirth = LocalDate.of(1993, 01, 21);
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(LocalDate.now());
if(currentMonthDay.equals(birthday)){
System.out.println("Many Many happy returns of the day !!");
}else{
System.out.println("Sorry, today is not your birthday");
}
//輸出: Many Many happy returns of the day !!
判斷日期是早于還是晚于另一個(gè)日期
LocalDate tomorrow = LocalDate.of(2019, 1, 22);
if(tomorrow.isAfter(LocalDate.now())){
System.out.println("Tomorrow comes after today");//Tomorrow comes after today
}
LocalDate yesterday = LocalDate.now().minus(1, ChronoUnit.DAYS);
if(yesterday.isBefore(LocalDate.now())){
System.out.println("Yesterday is day before today");//Yesterday is day before today
}
java8 時(shí)間類與Date類的相互轉(zhuǎn)化
//Date與Instant的相互轉(zhuǎn)化
Instant instant = Instant.now();
Date date = Date.from(instant);
Instant instant2 = date.toInstant();
//Date轉(zhuǎn)為LocalDateTime
Date date2 = new Date();
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date2.toInstant(), ZoneId.systemDefault());
//LocalDateTime轉(zhuǎn)Date
LocalDateTime localDateTime3 = LocalDateTime.now();
Instant instant3 = localDateTime3.atZone(ZoneId.systemDefault()).toInstant();
Date date3 = Date.from(instant);
//LocalDate轉(zhuǎn)Date
//因?yàn)長ocalDate不包含時(shí)間,所以轉(zhuǎn)Date時(shí)步淹,會(huì)默認(rèn)轉(zhuǎn)為當(dāng)天的起始時(shí)間从隆,00:00:00
LocalDate localDate4 = LocalDate.now();
Instant instant4 = localDate4.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
Date date4 = Date.from(instant);
日期的格式化和解析DateTimeFormatter
Java8
的DateTimeFormatter
也是線程安全的,而SimpleDateFormat
并不是線程安全缭裆。
*DateTimeFormatter
和SimpleDateFormat
對(duì)比 *
-
Date轉(zhuǎn)String
//使用Date和SimpleDateFormat SimpleDateFormat simpleDateFormat = new SimpleDateFormat("G yyyy年MM月dd號(hào) E a hh時(shí)mm分ss秒"); String format = simpleDateFormat.format(new Date()); System.out.println(format); //打印: 公元 2017年03月21號(hào) 星期二 下午 06時(shí)38分20秒
//使用jdk1.8 LocalDateTime和DateTimeFormatter
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter pattern = DateTimeFormatter.ofPattern("G yyyy年MM月dd號(hào) E a hh時(shí)mm分ss秒");
String format = now.format(pattern);
System.out.println(format);
//打印: 公元 2017年03月21號(hào) 星期二 下午 06時(shí)38分20秒
-
String轉(zhuǎn)Date
//使用Date和SimpleDateFormat SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); Date date = simpleDateFormat.parse("2017-12-03 10:15:30"); System.out.println(simpleDateFormat.format(date)); //打印 2017-12-03 10:15:30
//使用jdk1.8 LocalDateTime和DateTimeFormatter DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); //嚴(yán)格按照ISO yyyy-MM-dd驗(yàn)證键闺,03寫成3都不行 LocalDateTime dt = LocalDateTime.parse("2017-12-03 10:15:30",pattern); System.out.println(dt.format(pattern));
使用SimpleDateFormat
的正確姿勢(shì)
方法一
在需要執(zhí)行格式化的地方都新建SimpleDateFormat實(shí)例,使用局部變量來存放SimpleDateFormat實(shí)例
public static String formatDate(Date date)throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(date);
}
這種方法可能會(huì)導(dǎo)致短期內(nèi)創(chuàng)建大量的SimpleDateFormat實(shí)例澈驼,如解析一個(gè)excel表格里的字符串日期辛燥。
方法二
為了避免創(chuàng)建大量的SimpleDateFormat實(shí)例,往往會(huì)考慮把SimpleDateFormat實(shí)例設(shè)為靜態(tài)成員變量,共享SimpleDateFormat對(duì)象缝其。這種情況下就得對(duì)SimpleDateFormat添加同步挎塌。
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String formatDate(Date date)throws ParseException{
synchronized(sdf){
return sdf.format(date);
}
}
這種方法的缺點(diǎn)也很明顯,就是在高并發(fā)的環(huán)境下會(huì)導(dǎo)致解析被阻塞氏淑。
方法三(推薦)
要在高并發(fā)環(huán)境下能有比較好的體驗(yàn)勃蜘,可以使用ThreadLocal來限制SimpleDateFormat只能在線程內(nèi)共享,這樣就避免了多線程導(dǎo)致的線程安全問題假残。
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String format(Date date) {
return threadLocal.get().format(date);
}
//打印
public static void main(String[] args) {
System.out.println(format(new Date()));//2019-01-21
}