Java 中的時(shí)間處理
最初學(xué)習(xí) Java 時(shí)是采用學(xué)校教材和市面上的一些 Java 視頻教程進(jìn)行學(xué)習(xí),到了工作中才發(fā)現(xiàn)有些 API 或包已經(jīng)過(guò)時(shí)了。最近使用 Java 中的日期處理時(shí)發(fā)現(xiàn) java.util.date 與 Calender 相關(guān)包官方已經(jīng)逐漸棄用,因此總結(jié)一下目前 Java 中的日期處理方式。
首先先了解一下為什么 Java 8 之前的日期處理方式為什么難用。對(duì)大多數(shù)初學(xué)者最熟悉的 日期處理方式就是 Java 1中的 java.util.Date 類,這個(gè)類年份起始選擇是 1900 年,月份起始從 0 開(kāi)始。
Date date = new Date( 2020, 2, 18);
它的打印效果如下:
Tue Mar 18 00: 00: 00 CET 2020但它并不直觀,他默認(rèn)返回計(jì)算機(jī)格式的日期。你需要將它轉(zhuǎn)換為我們?nèi)菀撰@取的特定格式,需要使用 java.text.SimpleDateFormat 類進(jìn)行格式轉(zhuǎn)化,但這個(gè)類不是線程安全的,多線程格式轉(zhuǎn)化需要使用同步代碼塊加鎖解決線程安全問(wèn)題。
隨后 Java 1.1 出現(xiàn)了 java.util.Calender 類,但 Calender 類也有涉及缺陷,比如月份也是從 0 開(kāi)始。許多新手在選擇日期處理方式時(shí)會(huì)不知道用哪種,新手在使用 Date 與 Calender 的過(guò)程中會(huì)出現(xiàn)各種難以預(yù)料的錯(cuò)誤,調(diào)試搜索后才會(huì)發(fā)現(xiàn)原來(lái)是設(shè)計(jì)上的缺陷。
直到 Java 8 才出現(xiàn)了目前正在使用的新的日期和時(shí)間庫(kù) java.time.xxx。
Java ?中的最新的日期處理方式
目前網(wǎng)上搜索的 Java 日期處理最多的返回結(jié)果還是 Date 、Calender 類型的問(wèn)題,我也是因?yàn)椴冗^(guò)日期處理的許多坑才發(fā)現(xiàn)自己的知識(shí)已經(jīng)過(guò)時(shí),我相信跟我一樣的工程師不在少數(shù)。
為什么我定義 Java 8 中的日期處理方式是最新的呢,因?yàn)?Java 11 等后續(xù)版本一直在沿用 Java 8 的日期處理方式。Java 8 中出現(xiàn)了新的日期創(chuàng)建與時(shí)間間隔的類。分別是 LocalDate 、LocalTime 、Instant 、Duration 和 Period 。
我將參考 Java 11 的 java.time 文檔講解這些日期處理方式。
使用 LocalDate 、 LocalTime 與 LocalDateTime
LocalDate 存儲(chǔ)沒(méi)有時(shí)間的日期。它存儲(chǔ)的日期類似于 “2010-12-03” ,可以用來(lái)存儲(chǔ)生日。
LocalDate 可以創(chuàng)建 ISO-8601日歷系統(tǒng)中沒(méi)有時(shí)區(qū)的日期,如 2007-12-03。LocalDate 是一個(gè)不可變的日期-時(shí)間對(duì)象,表示日期,通常格式為年-月-日。如下代碼創(chuàng)建一個(gè)LocalDate 對(duì)象并實(shí)驗(yàn)其方法。
LocalDate date = LocalDate.of( 2014, 3, 18); // 2014-03-18
int year = date.getYear(); // 2014
Month month = date.getMonth();// MARCH
int day = date.getDayOfMonth();// 18
DayOfWeek dow = date.getDayOfWeek();// TUESDAY
int len = date.lengthOfMonth(); // 31 (三月的天數(shù))
boolean leap = date.isLeapYear(); // false 不是閏年
LocalDate today = LocalDate.now();
// 從系統(tǒng)時(shí)鐘中獲取當(dāng)前的日期
// 使用TemporalField 讀取LocalDate 的值
int year = date.get( ChronoField.YEAR);
int month = date.get( ChronoField.MONTH_OF_YEAR);
int day = date.get( ChronoField.DAY_OF_MONTH);
一天中的時(shí)間,可以用 LocalTime 來(lái)表示,LocalTime 存儲(chǔ)沒(méi)有日期的時(shí)間。這種存儲(chǔ)時(shí)間格式為 “11:30”,可以用來(lái)存儲(chǔ)營(yíng)業(yè)的時(shí)間。LocalTime 是一個(gè)不可變的日期-時(shí)間對(duì)象,它表示時(shí)間,通常被視為小時(shí)-分鐘-秒。時(shí)間以納秒精度表示。
LocalTime time = LocalTime.of( 13, 45, 20); // 13: 45: 20
int hour = time.getHour(); // 13
int minute = time.getMinute(); // 45
int second = time.getSecond();// 20
LocalDateTime 是 ISO-8601日歷系統(tǒng)中不帶時(shí)區(qū)的日期-時(shí)間,如2007-12-03T10:15:30。LocalDateTime是一個(gè)不可變的日期-時(shí)間對(duì)象,它表示一個(gè)日期-時(shí)間,通常被視為年-月-日-時(shí)-分-秒。還可以訪問(wèn)其他日期和時(shí)間字段,如 day-of-year、day-of-week 和 week-of-year。
// 2014-03-18T13: 45: 20
LocalDateTime dt1 = LocalDateTime.of( 2014, Month.MARCH, 18, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of( date, time);
LocalDateTime dt3 = date.atTime( 13, 45, 20);
LocalDateTime dt4 = date.atTime( time);
LocalDateTime dt5 = time.atDate( date);
// 提取日期和時(shí)間
LocalDate date1 = dt1. toLocalDate(); // 2014-03-18
LocalTime time1 = dt1. toLocalTime(); // 13: 45: 20
ZonedDateTime 的使用
ZonedDateTime ?ISO-8601日歷系統(tǒng)中帶有時(shí)區(qū)的日期-時(shí)間,例如 2007-12-03T10:15:30+01:00 Europe/Paris. 我們可以從下圖看出 LocalDate、LocalTime、LocalDateTime 、ZonedDateTime 的區(qū)別。

ZoneId romeZone = ZoneId.of("Europe/ Rome");
LocalDate date = LocalDate.of( 2014, Month.MARCH, 18);
ZonedDateTime zdt1 = date.atStartOfDay( romeZone);
LocalDateTime dateTime = LocalDateTime.of( 2014, Month.MARCH, 18, 13, 45);
ZonedDateTime zdt2 = dateTime.atZone( romeZone);
Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone( romeZone);ZoomId 對(duì)應(yīng)的Map選項(xiàng)可以從官方文檔中查看 https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/ZoneId.html
LocalDate 與 LocalTime 與字符串格式轉(zhuǎn)化
LocalDate date = LocalDate.parse("2014-03-18");
LocalTime time = LocalTime.parse("13: 45: 20");
我們可以使用指定的 fomatter 程序格式化日期, java.time.format.DateTimeFormatter 提供了兩個(gè)方法:一個(gè)用于格式化,format(DateTimeFormatter formatter),另一個(gè)用于解析,parse(CharSequence text, DateTimeFormatter formatter)。
LocalDate date = LocalDate.now();
String text = date.format(formatter);
LocalDate parsedDate = LocalDate.parse(text, formatter);
LocalDate date = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String text = date.format(formatter);
LocalDate parsedDate = LocalDate.parse(text, formatter);
LocalDate date = LocalDate.of( 2014, 3, 18);
String s1 = date.format( DateTimeFormatter.BASIC_ISO_DATE); //20140318
String s2 = date.format( DateTimeFormatter.ISO_LOCAL_DATE); //2014-03-18
LocalDate date1 = LocalDate.parse(" 20140318", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse(" 2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);
Duration 與 Period
Duration 一個(gè)基于時(shí)間的時(shí)間量,例如'34.5秒'。
它的 between 方法可以計(jì)算兩個(gè) LocalDateTime 或 兩個(gè) LocalTime 或兩個(gè) Instance 之間的間隔。
Duration d1 = Duration.between( time1, time2);
Duration d1 = Duration.between( dateTime1, dateTime2);
Duration d2 = Duration.between( instant1, instant2);
注意 Instance (時(shí)間線上的瞬間點(diǎn))這個(gè)類記錄時(shí)間線上的單個(gè)瞬時(shí)點(diǎn),常在應(yīng)用程序中記錄事件時(shí)間戳。
// 從系統(tǒng)時(shí)鐘中獲取當(dāng)前時(shí)刻。
Instant.now();Period 是 ISO-8601日歷系統(tǒng)中基于日期的時(shí)間量,例如“2年、3個(gè)月和4天”。
Period tenDays = Period.between( LocalDate.of( 2014, 3, 8), LocalDate.of( 2014, 3, 18));
Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);
Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2,6,1);
創(chuàng)建好 Period 和 Duration 對(duì)象后,我們可以使用他們定義好的各種方法查詢兩個(gè)日期之間的時(shí)間差,用于后續(xù)處理邏輯。
總結(jié)
Java 中的日期處理方式目前采用 LocalDate、LocalTime、LocalDateTime,應(yīng)該逐漸拋棄以前的 Date 與 Calender 方式。
參考資料
《Java 8實(shí)戰(zhàn)》 。人民郵電出版社.。[英]厄馬(Raoul-Gabriel Urma)[意] 弗斯科(Mario Fusco)[英] 米克羅夫特(Alan Mycroft).
Java 11 官方文檔
