工作這麼久了,對於Java中時間日期的操作一直很蛋疼,一會用Date,一會用Calendar一會用LocalDateTime,始終沒有認真總結過它們的聯繫與區別。迷迷糊糊用了好幾年了,今天終於搞清楚了! ...
工作這麼久了,對於Java中時間日期的操作一直很蛋疼,一會用Date,一會用Calendar一會用LocalDateTime,始終沒有認真總結過它們的聯繫與區別。迷迷糊糊用了好幾年了,今天終於搞清楚了!
一,Java8日期時間API產生的前因後果
1.1 為什麼要重新定義一套日期時間API
- 操作不方便:java中最初的Date不能直接對指定欄位進行加減操作也不支持國際化,後來新增了Calendar,但是Calendar又不支持格式化操作,需要轉換成Date再進行格式化,總之一直在填坑,使用起來一點都不夠優雅。
- 線程不安全:Date,Caleandar,SimpleDateFormat都是可變的,線程不安全的,所以你需要編寫額外的代碼處理線程安全問題。
1.2 Java8重新定義
- 對時間日期相關操作進行細分:時間,日期,日期&時間,時間戳,時間段,日期段,格式化等
- 所有類都是不可變的,線程安全
- 相容舊的日期時間
1.3Java8相容舊版本的Date,同時也規範了日期時間的轉換流程。
一,給人讀的( LocalDateTime & LocalDate & LocalTime)
java8中將時間和日期進行的區分,用LocalDateTime表示日期和時間,LocalDate用來表示日期而LocalTime表示時間。內部實現也非常好理解,LocalDateTime = LocalDate + LocalTime,並且他們的內部api也一致,所以筆者就結合工作中的經驗,介紹他們最常見的用法。
1.1 獲取當前時間
LocalDateTime localDateTime = LocalDateTime.now();
// 列印結果: 2019-12-02T22:09:20.503
1.2 獲取指定時間
// 獲取 2019年12月02號 23 : 59 : 59
LocalDateTime localDateTime2 = LocalDateTime.of(2019, 12, 2, 23, 59, 59);
// 列印結果: 2019-12-02T23:59:59
1.3 日期/時間加減操作
// localDateTime2的基礎上加1天零1s
LocalDateTime localDateTime3 = localDateTime2.plusDays(1).plusSeconds(1);
// 列印結果:2019-12-04T00:00
1.4 獲取指定的欄位(年月日時分秒,納秒,不支持毫秒)
System.out.println("現在是: " + localDateTime.getYear() + " 年中的第 " + localDateTime.getDayOfYear() +" 天");
// 列印結果:現在是: 2019 年中的第 336 天
// 畫外音: 快過年了呀,感覺這一年又沒啥收穫
二,給電腦讀的(Instant)
小知識:地球上不同地區經度不同會劃分時區,以零度經線上為準(格林尼治天文臺舊址,UTC時區)為準,將地球上各個部分分為了24個時區。向西走,每過一個時區,就要把表撥慢1個小時;同理每向東走一個時區,就要把表撥快1個小時。最後,中國處於東8區。
2.1 獲取UTC時間(格林尼治時間)
Instant instant = Instant.now();
// 列印結果: 2019-12-02T14:31:41.661Z
2.2 獲取北京時間(東8區)
// OffsetTime表示有時差的時間,除了UTC時間,都是OffsetTime
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
// 列印結果: 2019-12-02T22:31:41.661+08:00
2.3 獲取毫秒數(1970-01-01T00:00:00Z開始計算)
long epochMilli = instant.toEpochMilli()
// 列印結果:1575297101661
2.4 定義時間戳
Instant instant1 = Instant.ofEpochSecond(59);
// 列印結果: 1970-01-01T00:00:59Z
instant2 = instant1.plusSeconds(99)
// 列印結果:1970-01-01T00:02:38Z
三, 時間間隔(Duration)
3.1 計算日期間隔(參數位置影響結果哦)
Instant instant1 = Instant.now();
Instant instant2 = instant1.plusSeconds(99);
Duration duration1 = Duration.between(instant1, instant2);
Duration duration2 = Duration.between(instant2, instant1);
// 列印結果 duration1:PT1M39S
// 列印結果 duration2:PT-1M-39S
long duration1Seconds = duration1.getSeconds();
long duration2Seconds = duration1.getSeconds();
// 列印結果 duration1Seconds: 90
// 列印結果 duration2Seconds: -90
3.2 操作時間間隔
Duration duration3 = duration1.plusDays(1);
// 列印結果:PT24H1M39S
註意 : 僅支持時間操作(Instant, LocalTime,LocalDateTime),不支持日期(LocalDate)
四,日期間隔(Period)
LocalDate localDate1 = LocalDate.now();
LocalDate localDate2 = localDate1.plusDays(1);
Period period = Period.between(localDate1, localDate2);
long days = period.getDays();
// 列印結果 peroid: P1D
// 列印結果 days: 1
五,日期/時間校正器(TemporalAdjuster)
5.1 獲取指定日期或時間
LocalDateTime localDateTime1 = LocalDateTime.now();
LocalDateTime localDateTime2 = localDateTime.withDayOfMonth(20);
// 列印結果 localDateTime1:2019-12-02T22:57:47.674
// 列印結果 localDateTime2:2019-12-20T22:57:47.674
5.2 獲取下一個固定日期(下一個星期天)
LocalDateTime localDateTime3 = localDateTime.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
// 列印結果 localDateTime33:2019-12-08T23:00:43.101
5.3 自定義矯正器
// 獲取下一個工作日
LocalDateTime localDateTime4 = localDateTime.with((tempDateTime) -> {
LocalDateTime localDateTime5 = (LocalDateTime) tempDateTime;
DayOfWeek dayOfWeek = localDateTime5.getDayOfWeek();
if (dayOfWeek.equals(DayOfWeek.FRIDAY)) {
return localDateTime5.plusDays(3);
} else if (dayOfWeek.equals(DayOfWeek.SATURDAY)) {
return localDateTime5.plusDays(2);
} else {
return localDateTime5.plusDays(1);
}
});
// 列印結果 localDateTime4:2019-12-03T23:00:43.101
六,日期時間格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
String dateStr =dateTimeFormatter.format(localDateTime);
// 列印結果: 2019-12-02 23:08:55
LocalDateTime localDateTime2 = LocalDateTime.parse(dateStr, dateTimeFormatter);
// 列印結果: 2019-12-02T23:08:55
LocalDate localDate = LocalDate.parse(dateStr, dateTimeFormatter);
// 列印結果: 2019-12-02
七,基於Instant進行轉換
java8api對於時間戳,日期時間以及老版本的Date對象之間的轉換也進行了相容和適配,所有的轉換操作都可以基於Instant對象進行。由於LocalDate,LocalTime和LocalDateTime三個類的操作完全一樣,所以下文仍使用LocalDateTime演示。
7.1 時間戳轉LocalDate,LocalDate,LocalDateTime
long timestamp = Instant.now().toEpochMilli();
LocalDateTime localDateTime = Instant.ofEpochMilli(timestamp).atOffset(ZoneOffset.ofHours(8)).toLocalDateTime();
// 列印結果:2019-12-02T23:20:25.791
7.2 LocalDate,LocalDate,LocalDateTime轉時間戳
LocalDateTime localDateTime = LocalDateTime.now();
long timestamp = localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli();
// 列印結果:1575300368099
7.3 相容舊版本Date
LocalDateTime localDateTime3 = LocalDateTime.now();Date date =
Date.from(localDateTime.atZone(ZoneOffset.ofHours(8)).toInstant());LocalDateTime localDateTime4 =
localDateTime3.atZone(ZoneOffset.ofHours(8)).toLocalDateTime();
// 列印結果 date:Mon Dec 02 23:32:53 CST 2019
// 列印結果 lcoalDateTime4:2019-12-02T23:32:53.188
八, Q&A
上一篇問題:在java中通常使用synchronized來實現方法同步,AQS中通過CAS保證了修改同步狀態的一致性問題,那麼對比synchronized,cas有什麼優勢不同與優勢呢?你還知道其他無鎖併發的策略嗎?
8.1 Answer
Java中的無鎖併發策略可以分為三種:
- 基於樂觀鎖的CAS操作
- Copy On Write:寫時複製是指:在併發訪問的情景下,當需要修改JAVA中Containers的元素時,不直接修改該容器,而是先複製一份副本,在副本上進行修改。修改完成之後,將指向原來容器的引用指向新的容器(副本容器)
- ThreadLocal:線程本地存儲,就是為每一個線程創建一個變數,只有本線程可以在該變數中查看和修改值。
8.2 Question
這是一道送分題:正如上文提到的,Java8之前的日期時間以及格式化類是線程不安全的,你知道怎麼編寫測試代碼嗎?
如果優雅得獲取昨天0點整的毫秒值?
學習Java過程中可能遇到問題和困惑,關註我vx公眾號 “cruder” ,後臺留言,筆者幫你一起解決!(需要學習資料的請關註後後臺留言,主要都是java相關,java基礎,併發,mysql,redis,es,mq等都都有!)