一文搞懂Java8中表示當前的時間類Date、Instant、LocalDateTime、ZonedDateTime

1. 概述

Java8中的時間類主要有:Date、Instant、LocalDateTime(LocalDate、LocalTime)、ZonedDateTime,除去Date,java.time包下的那些時間類都是不可變類,也就是說:其是線程安全的,對其設置只會產生一個新對象。

在這裏,我們要分清楚包含時區信息的類、以及不包含時區信息的類。不包含時區信息的類實際上就類似於一個yyyy-MM-dd HH:mm:ss字符串,需要額外的時區信息才能表達一個時刻,即LocalDateTime、LocalDate、LocalTime。而Date(0時區)、Instant(0時區)、ZonedDateTime都包含有時區信息。

import java.util.Date;
import java.util.TimeZone;

class Scratch {
    public static void main(String[] args) {
        TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
        Date d1 = new Date();
        Instant i1 = Instant.now();
        ZonedDateTime z1 = ZonedDateTime.now();
        LocalDateTime l1 = LocalDateTime.now();

        TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
        Date d2 = new Date();
        Instant i2 = Instant.now();
        ZonedDateTime z2 = ZonedDateTime.now();
        LocalDateTime l2 = LocalDateTime.now();

        TimeZone.setDefault(TimeZone.getTimeZone("Australia/Darwin"));
        Date d3 = new Date();
        Instant i3 = Instant.now();
        ZonedDateTime z3 = ZonedDateTime.now();
        LocalDateTime l3 = LocalDateTime.now();
    }
}

斷點變量圖
看懂上面的代碼以及斷點處的變量圖基本就搞懂了Java8中的時間類的使用了。我勸你可千萬別跳過,好好看看。

2. 類結構

2.1. Date

    private transient long fastTime;

    private transient BaseCalendar.Date cdate;

本質是記錄了0時區的時間,不同時區的人在同一時刻new Date()時,其對象內存放的毫秒數是一樣的(都是0時區)。

2.1.1. 轉換成字符串

因爲System.out.println函數在打印時間時,會取操作系統當前所設置的時區,然後根據這個時區將毫秒數解釋成該時區的時間。或是SimpleDateTimeFormat這種日期格式化類也會在打印時根據時區解釋Date中的毫秒數。

Date date = new Date(1503544630000L);  // 對應的北京時間是2017-08-24 11:17:10
 
SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");     // 北京
bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));  // 設置北京時區
 
SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 東京
tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));  // 設置東京時區
 
SimpleDateFormat londonSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 倫敦
londonSdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));  // 設置倫敦時區
 
System.out.println("毫秒數:" + date.getTime() + ", 北京時間:" + bjSdf.format(date));
System.out.println("毫秒數:" + date.getTime() + ", 東京時間:" + tokyoSdf.format(date));
System.out.println("毫秒數:" + date.getTime() + ", 倫敦時間:" + londonSdf.format(date));

// 輸出
// 毫秒數:1503544630000, 北京時間:2017-08-24 11:17:10
// 毫秒數:1503544630000, 東京時間:2017-08-24 12:17:10
// 毫秒數:1503544630000, 倫敦時間:2017-08-24 04:17:10

2.1.2. 從字符串中讀取時間

2017-8-24 11:17:10解析爲一個Date對象,會根據SimpleDateFormat設置的時區而將其轉換成0時區的Date對象。將一個時間字符串按不同時區來解釋,得到的Date對象的值是不同的。驗證如下:

String timeStr = "2017-8-24 11:17:10"; // 字面時間
SimpleDateFormat bjSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
bjSdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
Date bjDate = bjSdf.parse(timeStr);  // 解析
System.out.println("字面時間: " + timeStr +",按北京時間來解釋:" + bjSdf.format(bjDate) + ", " + bjDate.getTime());
 
SimpleDateFormat tokyoSdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  // 東京
tokyoSdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));  // 設置東京時區
Date tokyoDate = tokyoSdf.parse(timeStr); // 解析
System.out.println("字面時間: " + timeStr +",按東京時間來解釋:"  + tokyoSdf.format(tokyoDate) + ", " + tokyoDate.getTime());

// 輸出爲:
// 字面時間: 2017-8-24 11:17:10,按北京時間來解釋:2017-08-24 11:17:10, 1503544630000
// 字面時間: 2017-8-24 11:17:10,按東京時間來解釋:2017-08-24 11:17:10, 1503541030000

2.2. Instant

    private final long seconds;

    private final int nanos;

本質是記錄了0時區的時間

2.3. LocalDateTime、LocalDate、LocalTime

根據當前設置的時區獲取當前時區的日期、時間,但不會記錄時區信息。

Local*這些時間類實際上就相當於一個字符串,只不過把年月日、時分秒解析出來了而已。
String與LocalDateTime是等價的

2.3.1. LocalDateTime:

    private final LocalDate date;

    private final LocalTime time;

2.3.2. LocalDate:

    private final int year;

    private final short month;

    private final short day;

2.3.3. LocalTime

    private final byte hour;

    private final byte minute;

    private final byte second;

    private final int nano;

2.4. ZonedDateTime

在LocalDateTime的基礎上同時保存了當前的時區,即:根據當前設置的時區獲取當前時區的日期、時間,同時保存當前時區信息。

    private final LocalDateTime dateTime;

    private final ZoneOffset offset;

    private final ZoneId zone;

自帶時區信息,默認獲取當前時區。

3. 記錄時間原理

new Date()Instant.now本質上都是記錄了0時區的時間,即使當前時區不同,其存儲的都是距離1970-01-01 00:00:00所經過的時間。

Local*這些時間類實際上就相當於一個字符串,只不過把年月日、時分秒解析出來了而已。

4. 打印時間

打印時設置的時區信息會對原時間解析出來的字符串有影響,打印過程會轉換原時區時間到現在時區時間,這點一定要注意。

Instant打印時要給DateTimeFormatter設置時區才能打印,否則會報錯。

DateTimeFormatter.ofPattern(pattern).withZone(ZoneId.of("Asia/Shanghai"))

5. 注意點

5.1. DateTimeFormatter.with*()會返回一個新的對象

正確的做法:

DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withZone(ZoneId.of("GMT")).withLocale(Locale.UK)

錯誤的做法

DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
formatter.withZone(ZoneId.of("GMT"));
formatter.withLocale(Locale.UK);

因爲DateTimeFormatter是一個不可變類,所以不可以修改其屬性,同時也就是個線程安全類了。其靈活的創建過程是利用DateTimeFormatterBuilder來實現的。

5.2. DateTimeFormatter.ofPattern()沒有設置打印時的時區

需要設置時區需要DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”).withZone(ZoneId.of(“GMT”))這樣設置。

5.3. SimpleDateFormat並非線程安全

其format()與parse()方法都不是線程安全。

6. 拓展

6.1. Clock類

Clock是帶有時區信息的時間處理類,用它可以獲取不同時區的時間,也可以配合Duration,對時間進行時、分、秒級別的修改

6.2. Duration 和 Period

Duration 和 Period 都是用來表示兩個時間量之間的差值,不同點在於Duration 是基於時間值,而 Period 是基於日期值。

6.3. java.time包下的5個包組成:

  1. java.time – 包含值對象的基礎包
  2. java.time.chrono – 提供對不同的日曆系統的訪問
  3. java.time.format – 格式化和解析時間和日期
  4. java.time.temporal – 包括底層框架和擴展特性
  5. java.time.zone – 包含時區支持的類

6.4. JDBC映射

最新JDBC映射將把數據庫的日期類型和Java 8的新類型關聯起來:

date -> LocalDate
time -> LocalTime
timestamp -> LocalDateTime

6.5. SimpleDateFormat

SimpleDateFormat只能格式化Date。而DateTimeFormatter可以格式化TemporalAccessor(不包括Date,但是包括LocalDate*、Instant、ZonedDateTime等)。

7. 參考資料

  1. java 8的java.time包(非常值得推薦) - 不姓馬的小馬哥 - 簡書
  2. Java中的時間與時區 - frcoder - CSDN
  3. 深入學習 Java 8 全新日期時間庫 java.time(一)- FXBStudy - CSDN
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章