JDK8--09:全新的時間API
在JDK8之前,時間有各種問題,最大的問題就是,我們使用的時間格式化類SimpleDateFormat不是執行緒安全的
為了更準確的說明SimpleDateFormat非執行緒安全,演示一個併發做時間格式化的操作
public void test() throws Exception{ //全新的時間API 都不是執行緒安全的 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); Callable<Date> call = new Callable<Date>() { @Overridepublic Date call() throws Exception { return simpleDateFormat.parse("20200601"); } }; ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); List<Future<Date>> list = new ArrayList<>(); for(int i=0;i<10;i++){ list.add(newFixedThreadPool.submit(call)); }for (Future<Date> future : list){ log.info("============={}",future.get()); } newFixedThreadPool.shutdown(); }
執行結果:
就是因為多個執行緒併發使用SimpleDateFormat,因此出現了異常,那麼JDK8之前我們是如何保證SimpleDateFormat執行緒安全的呢?
在JDK8之前,我們使用ThreadLocal鎖定SimpleDateFormat,來保證執行緒安全:
首先,建立一個使用TreadLocal鎖定的SimpleDateFormat
public class DateFormatThreadLocal { private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){ protected DateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd"); } }; public static Date convert(String source) throws Exception{ return df.get().parse(source); } }
其次,在做時間格式化時,使用該類中鎖定的SimpleDateFormat物件
@Test public void test2() throws Exception{ //全新的時間API 都不是執行緒安全的 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd"); Callable<LocalDate> call = new Callable<LocalDate>() { @Override public LocalDate call() throws Exception { return LocalDate.parse("20200601",dateTimeFormatter); } }; ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10); List<Future<LocalDate>> list = new ArrayList<>(); for(int i=0;i<10;i++){ list.add(newFixedThreadPool.submit(call)); } for (Future<LocalDate> future : list){ log.info("============={}",future.get()); } newFixedThreadPool.shutdown(); }
執行結果:
可以發現,程式執行正常,均做了格式化
那麼在JDK8中,提供了全新的時間類,這些類都是執行緒安全的,可以在多執行緒併發時使用,無需擔心執行緒安全問題,無需建立ThreadLocal鎖定的格式化類
首先,介紹JDK8中新增的時間類 :LocalDate LocalTime LocalDateTime
1、時間物件建立
(1)可以使用now建立時間類,時間為當前時間
(2)可以使用of建立指定時間
//建立日期 //LocalDate LocalTime LocalDateTime LocalDateTime ld = LocalDateTime.now(); log.info("LocalDateTime.now()=================={}",ld); LocalDateTime ld1 = LocalDateTime.of(2020,5,31,13,25,36); log.info("LocalDateTime=================={}",ld1);
2、日期運算:JDK8提供了plus*方法可以對日期進行運算操作
//日期運算 plus 加日期 LocalDateTime ld = LocalDateTime.now(); log.info("plusDays=================={}",ld.plusDays(2)); log.info("plusHours=================={}",ld.plusHours(3));
3、get返回值:JDK8提供了get*方法可以返回年份、月份、日期等
//get 返回值 LocalDateTime ld = LocalDateTime.now(); log.info("getMonthValue=================={}",ld.getMonthValue()); log.info("getDayOfMonth=================={}",ld.getDayOfMonth());
4、時間戳操作
上面提到的LocalDate、LocalTime、LocalDateTime輸出的都是指定的時間格式,但是如果我們需要使用時間戳,就需要其他的方式處理,對於時間戳的操作如下:
(1)直接獲取時間戳,此時獲取的時間戳為UTC時間,即世界協調時間(世界標準時間,以北京時間為例,由於北京時間採取的是東八區時間,因此是標準時間+8個小時,即為北京時間)
(2)獲取指定時區的時間戳
(3)轉換為時間戳
(4)指定時間戳起始時間(1970-01-01 00:00:00)後多少時間的時間戳
//時間戳 Instant instant = Instant.now();//預設是UTC時間(世界協調時間) log.info("時間戳===={}",instant); //轉換為東八區時間(北京時間) OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8)); log.info("轉換為東八區時間(北京時間)=={}",offsetDateTime); //轉換為時間戳 long toEpochMilli = instant.toEpochMilli(); log.info("時間戳=={}",toEpochMilli); //改變時間 距離時間戳的時間 1970-01-01 00:00:00 Instant instant1 = Instant.ofEpochSecond(60*23); log.info("距離時間戳的時間=={}",instant1);
執行結果:
通過執行結果可以發現,時間戳和根據時區獲取的時間戳輸出時,仍然是格式化過的時間格式,且是由使用東八區的時間時,才與我電腦上的北京時間一致,同時輸出的時間上有+08:00的輸出;
另外,對於最後一個距離時間戳的時間,我們設定了60*23,即設定了23分鐘,最後輸出時間為:1970-01-01T00:23:00Z
5、計算時間差
(1)Duration:獲取兩個時間的時間差
(2)Period:獲取兩個日期的間隔
//計算時間差 //Duration:獲取兩個時間的時間差 //Period:獲取兩個日期的間隔 Instant instant2 = Instant.now(); Thread.sleep(1000); Instant instant3 = Instant.now(); Duration duration = Duration.between(instant2,instant3); log.info("duration.getSeconds()============={}",duration.getSeconds()); log.info("duration.toMillis()============={}",duration.toMillis()); log.info("duration.toHours()============={}",duration.toHours()); LocalDate localDate = LocalDate.of(2018,10,6); LocalDate localDate1 = LocalDate.now(); log.info("LocalDate.now()============={}",localDate1); Period period = Period.between(localDate,localDate1); log.info("period============={}",period); log.info("period.getYears()============={}",period.getYears()); log.info("period.getMonths()============={}",period.getMonths()); log.info("period.getDays()============={}",period.getDays());
執行結果:
(1)我們在程式碼中,讓程式休眠了1秒,因此兩個時間相差的秒數為1,毫秒數為1001是因為程式執行使用了1毫秒
(2)計算日期差時,我們使用了指定日期2018.10.6和當前日期(2020.6.2)做比較,輸出為P1Y7M27D,表示差了1年7個月27天,然後使用get*獲取時,獲取的即這個輸出的年、月、天
6、時間矯正器
(1)可以獲取指定該年中的天數
(2)獲取下一個指定的日期(下一個週日,JDK已提供)
(3)獲取下一個工作日/結婚紀念日等(JDK未提供)
/** * 時間矯正器 */ @Test public void test4(){ //TemporalAdjuster:時間矯正器 LocalDate localDate2 = LocalDate.now(); log.info("LocalDate.now()============={}",localDate2); //指定時間 LocalDate localDate3 = localDate2.withDayOfYear(55); log.info("withDayOfYear============={}",localDate3); //獲取下一個週日 LocalDate localDate4 = localDate2.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)); log.info("TemporalAdjuster============={}",localDate4); //自定義 下一個工作日 LocalDate localDate5 = localDate2.with((x)->{ LocalDate localDate6 = (LocalDate) x; DayOfWeek dayOfWeek = localDate6.getDayOfWeek(); switch (dayOfWeek){ case FRIDAY: return localDate6.plusDays(3); case TUESDAY: return localDate6.plusDays(2); default: return localDate6.plusDays(1); } }); log.info("下一個工作日============={}",localDate5); }
輸出結果:
2020-06-02 15:08:26.457 INFO 19392 --- [ main] com.example.jdk8demo.Jdk8demoTest2 : LocalDate.now()=============2020-06-02 2020-06-02 15:08:26.461 INFO 19392 --- [ main] com.example.jdk8demo.Jdk8demoTest2 : withDayOfYear=============2020-02-24 2020-06-02 15:08:26.463 INFO 19392 --- [ main] com.example.jdk8demo.Jdk8demoTest2 : TemporalAdjuster=============2020-06-07 2020-06-02 15:08:26.464 INFO 19392 --- [ main] com.example.jdk8demo.Jdk8demoTest2 : 下一個工作日=============2020-06-04
7、時間格式化
/** * 時間日期格式化 * */ @Test public void test6(){ DateTimeFormatter df = DateTimeFormatter.ISO_DATE_TIME; LocalDateTime ld = LocalDateTime.now(); String date = ld.format(df); log.info("format==================={}",date); //自定義 DateTimeFormatter df1 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH時mm分ss秒"); date = ld.format(df1); log.info("format==================={}",date); //解析回原格式,此處要注意一點, HH時mm分ss秒大小寫一定注意,錯了就會反解析失敗 LocalDateTime localDateTime = ld.parse(date,df1); log.info("parse==================={}",localDateTime); }
8、時區操作
(1)獲取所有時區
(2)根據時區獲取時間
(3)計算時區的時間差
/** * 時區操作 * ZoneDate ZoneTime ZoneDateTime */ @Test public void test7(){ //獲取所有時區 Set<String> set = ZoneId.getAvailableZoneIds(); log.info("ZoneId.getAvailableZoneIds()================={}",JSON.toJSONString(set)); //根據時區獲取時間 LocalDateTime localDateTime = LocalDateTime.now(ZoneId.of("Pacific/Fiji")); LocalDateTime localDateTime1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); log.info("根據時區獲取日期=========Pacific/Fiji==={}==========Asia/Shanghai==={}",localDateTime,localDateTime1); //獲取時差 ZonedDateTime zonedDateTime = localDateTime1.atZone(ZoneId.of("Pacific/Fiji")); log.info("Shanghai與Fiji的時差========{}",zonedDateTime); }