前言
本文主要針對Date、Calendar、Instant、LocalDate、LocalTime和LocalDateTime的使用做了介紹并進行了對比,同時對simpleDateFormat和simpleDateFormat進行了對比和介紹。篇幅較長可針對具體模塊選擇性閱讀。
Date
對于Date類型最常用的操作想必就是new Date()了
public static void main(String[] args) {
System.out.println(new Date());
}
這時候我們會得到一個當前時間的一個Date類型的字段輸出字樣為Tue Jun 09 19:20:37 CST 2020 一個帶有年月日時分秒的時間。
其中的CST可視為美國、澳大利亞、古巴或中國的標準時間,在這邊就是中國的標準時間了。
當然我們對于這樣格式的時間的做法是通過JAVA.text.SimpleDateFormat類來進行格式化之后返回頁面展示
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
System.out.println(simpleDateFormat.format(new Date()));
}
輸出為2020-06-09 19:43:21這個就是我們最為熟悉的一個時間字段格式化之后的結果了。
當然這里要友情提醒的是SimpleDateFormat這個類并不是線程安全的,在高并發場景下需要謹慎使用。Date類型自帶有很多的函數具體如下:
可以發現很多都已經變成過時函數,雖然還可以使用但是并不保證在將來某個更新中被刪除的可能性,我們也是不推薦使用這種過時的函數的。
這時候java.util.Calendar是一個不錯的類可以幫我們解決很多問題。
相信一開始接觸java的小伙伴一定被Calendar的強大征服過,但是用久了慢慢地我們會發現其實這個類并沒有想象中那么強大,首先一個問題就是它并不支持時區,其次它也不是線程安全的。
所以考慮到它的種種缺陷,java8使用了新的時間和日期API LocalDateTime
Instant
Instant表示的是時間線上的一個點,也就是時刻,可以和Date做一個比較。
比較直接的一個不同就是Instant獲取的是UTC的時間,而Date是根據當前服務器所處的環境的默認時區來獲取的當前時間。
//Date案例
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println("new Date() = "+date);
String s = simpleDateFormat.format(date);
System.out.println("SimpleDateFormat 格式化Date后 = "+s);
//Instant案例Instant instant = Instant.now();System.out.println("Instant = "+instant);
System.out.println("Instant +08:00 = "+instant.atOffset(ZoneOffset.ofHours(8)));
輸出
new Date() = Wed Jun 10 15:27:48 CST 2020
SimpleDateFormat 格式化Date后 = 2020-06-10 15:27:48
Instant = 2020-06-10T07:27:48.198Z
Instant +08:00 = 2020-06-10T15:27:48.198+08:00
默認時區是UTC在使用Instant的時候是一個需要注意的點,也是容易忽略的一個點,這里劃重點!相同的問題在LocalDate、LocalTime和LocalDateTime是不存在的。
但是Instant的官方描述來看,它是一個不可變的且線程安全的類
* @implSpec
* This class is immutable and thread-safe. * @since 1.8
*/public final class Instant implements Temporal, TemporalAdjuster, Comparable<Instant>, Serializable {}
有效時間范圍是從-1000000000-01-01T00:00Z~-1000000000-01-01T00:00Z,可以滿足大部分場景下的時刻現實問題。
同時在java8提供了toInsatant()和from()兩個方法用于Date和Instant之間的來回轉換
System.out.println("toInstant() = "+date.toInstant());
System.out.println("from() = "+Date.from(instant));
輸出
toInstant() = 2020-06-10T07:45:42.440Z
from() = Wed Jun 10 15:45:42 CST 2020
可以看到相互轉換過程中的時區問題不需要我們考慮,會自動+08:00或者-08:00。
比較頭疼的一個事情就是java8沒有針對Instant提供一個可供自定義的格式化類,所以這邊我的解決方法是轉換成LocalDateTime,再使用DateTimeFormatter來完成格式化。
System.out.println("Instant = " + LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
輸出
Instant = 2020-06-10 16:02:26
這樣就比較容易的解決了格式化的問題,當然你也可以自定義一個@config來完成對Instant的格式化,也不失為一種一勞永逸的方案。LocalDateTime和DateTimeFormatter在后面的內容中會做詳細的介紹。
在講解LocalDateTime之前我們先分別介紹一下LocalDate和LocalTime,以便于能更深入地理解LocalDateTime。
LocalDate和LocalTime
LocalDate
LocalDate首先是一個不可變類,默認格式為yyyy-MM-dd,其次它是一個只獲取年月日的類,側重點在日歷而不是時間(這里我們需要把日歷和時間這兩個概念區分開)。
使用LocalDate.now()可以獲取當前年月日,也可以使用LocalDate.of(year,month,dayOfMonth)來指定日期
public static void main(String[] args) {
LocalDate today = LocalDate.now(); System.out.println("LocalDate.now() = "+today);
today = LocalDate.of(2020,06,10);
LocalDate laterDate = today.plusDays(30);
System.out.println("LocalDate.of() = "+today.toString());
System.out.println("plusDays = "+laterDate.toString());
System.out.println("new Date() = "+ new Date());
}
輸出
LocalDate.now() = 2020-06-09
LocalDate.of() = 2020-06-10
plusDays = 2020-07-10
new Date() = Tue Jun 09 20:31:22 CST 2020
從這個例子可以對比看出LocalDate和Date的不同。
同時作為一個訪問器方法,LocalDate每次都是生成一個新的對象,而不是改變原有的對象的值。
可以從today.plusDays(30)中輕易地看到。與之類似的就是更改器方法,在java較早的版本中有一個類java.util.GregorianCalendar
public static void main(String[] args) {
GregorianCalendar someDay = new GregorianCalendar(2020,06,9);
System.out.println("before someDay = "+someDay.getTime());
someDay.add(Calendar.DAY_OF_MONTH,30);
System.out.println("after someDay = "+someDay.getTime());
}
輸出
before someDay = Thu Jul 09 00:00:00 CST 2020
after someDay = Sat Aug 08 00:00:00 CST 2020
可以看到someDay的值隨著函數add的調用一直在變化著,這與LocalDate大不一樣,這是需要注意的一個點。
這邊還需要注意一個點是localDate.getDayOfWeek().getValue(),LocalDate對于一周的枚舉計數和Calendar有些不一樣。
LocalDate一周是從周一開始計數對應的value值為1,周日結束對應的value值為7。
而Calendar一周是從周日開始計數對應的value值為1,周六結束對應的value為7,相比較下個人覺得LocalDate更加合理和好用一些。
LocalDate常用的方法如下:
圖片來自java核心技術卷2
LocalTime
LocalTime與LocalDate類似同樣是一個不可變類,默認格式是HH:mm:ss.zzz,可以看到它所關注的是當前的時刻。
public static void main(String[] args) {
LocalTime localTime = LocalTime.now(); System.out.println("LocalTime.now() = "+localTime);
localTime = LocalTime.of(8,8,8,888);
System.out.println("LocalTime.of() = "+localTime);
LocalTime laterTime = localTime.plusHours(2);
System.out.println("localTime.plusHours() = "+laterTime);
//根據時區獲取當前時刻,同理適用與LocalDate
LocalTime newlocalTime = LocalTime.now(ZoneId.of("America/New_York"));
System.out.println("America/New_York Time = "+newlocalTime);
}
輸出
LocalTime.now() = 20:58:54.941
LocalTime.of() = 08:08:08.000000888
localTime.plusHours() = 10:08:08.000000888
America/New_York Time = 08:58:54.944
這里我們會發現,盡管LocalTime默認的格式為HH:mm:ss.zzz,但是納秒級別的精度它也是能支持的08:08:08.000000888。LocalTime常用的方法如下:
java核心技術卷2
LocalDateTime
經過對LocalDate和LocalTime的介紹,LocalDateTime相信大家也已經知道如何使用了。
LocalDateTime也是一個不可變類且線程安全,它的默認格式為yyyy-MM-ddTHH:mm:ss.zzz,顯然在日常的web開發過程中我們都會對這樣的日期格式進行格式化,這就是我這邊特別要提的一點了。
之前我們講過java.text.SimpleDateFormat可以自定義格式化時間格式,但是他并不是線程安全的類,所以java8開始配合LocalDateTime提供了java.time.format.DateTimeFormatter來搞定這個問題。
* @implSpec
* This class is immutable and thread-safe. * * @since 1.8
*/public final class DateTimeFormatter
這是官方對他的介紹,這個類是不可變并且是線程安全的。所以我們可以放心地用了。
但是友情提醒下線程安全+線程安全不一定線程安全,不要誤解了,這里就不展開討論了。下面我們和SimpleDateFormat一起對比著來使用一下。
// LocalDateTime
LocalDateTime localDateTime = LocalDateTime.now();String newLocalDateTime = localDateTime.format(DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss"));
System.out.println("LocalDateTime = "+localDateTime);
System.out.println("DateTimeFormatter 格式化后的時間 = "+newLocalDateTime);
//DateSimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = new Date();
System.out.println("new Date() = "+date);
String s = simpleDateFormat.format(date);
System.out.println("SimpleDateFormat 格式化Date后 = "+s);
輸出
LocalDateTime = 2020-06-10T14:41:02.546
DateTimeFormatter 格式化后的時間 = 2020-06-10 14:41:02
new Date() = Wed Jun 10 14:41:02 CST 2020
SimpleDateFormat 格式化Date后 = 2020-06-10 14:41:02
從代碼量上就可以看到DateTimeFormatter的優勢了,一行搞定。
相比一下SimpleDateFormat每次都要new一個對象,在極端情況下就會導致創建很多實例短時間無法回收而浪費很多內存空間,當然我們也可以通過使用靜態變量通過添加synchronized來達到目的,但是同步塊不可避免的問題就是阻塞。
當然你一定會說我可以使用ThreadLocal來創建副本來解決SimpleDateFormat的線程安全問題。這個是比較好的一個解決方案,如下:
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}};
System.out.println("ThreadLocal = "+threadLocal.get().format(date));
輸出
ThreadLocal = 2020-06-10 14:52:05
顯然結果挺好的,但是使用LocalDateTime.format(DateTimeFormatter.ofPattern("yyy-MM-dd HH:mm:ss"))一行就能解決了,相對而言是比較會更簡潔和方便一些,所以推薦使用DateTimeFormatter。
作者|IT老哥|微信公眾號