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個包組成:
- java.time – 包含值對象的基礎包
- java.time.chrono – 提供對不同的日曆系統的訪問
- java.time.format – 格式化和解析時間和日期
- java.time.temporal – 包括底層框架和擴展特性
- 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等)。