1. 程式人生 > >Java日期學習筆記(二):JDK1.8新特性

Java日期學習筆記(二):JDK1.8新特性

Java 8另一個新增的重要特性就是引入了新的時間和日期API,它們被包含在java.time包中。藉助新的時間和日期API可以以更簡潔的方法處理時間和日期。

在介紹本篇文章內容之前,我們先來討論Java 8為什麼要引入新的日期API,與之前的時間和日期處理方式有什麼不同?

在Java 8之前,所有關於時間和日期的API都存在各種使用方面的缺陷,主要有:

  1. Java的java.util.Date和java.util.Calendar類易用性差,不支援時區,而且他們都不是執行緒安全的;
  2. 用於格式化日期的類DateFormat被放在java.text包中,它是一個抽象類,所以我們需要例項化一個SimpleDateFormat物件來處理日期格式化,並且DateFormat也是非執行緒安全,這意味著如果你在多執行緒程式中呼叫同一個DateFormat物件,會得到意想不到的結果。
  3. 對日期的計算方式繁瑣,而且容易出錯,因為月份是從0開始的,從Calendar中獲取的月份需要加一才能表示當前月份。

由於以上這些問題,出現了一些三方的日期處理框架,例如Joda-Time,date4j等開源專案。但是,Java需要一套標準的用於處理時間和日期的框架,於是Java 8中引入了新的日期API。新的日期API是JSR-310規範的實現,Joda-Time框架的作者正是JSR-310的規範的倡導者,所以能從Java 8的日期API中看到很多Joda-Time的特性。

Java 8日期/時間類

Java 8的日期和時間類包含LocalDate、LocalTime、Instant、Duration以及Period,這些類都包含在java.time包中,下面我們看看這些類的用法。

LocalDate和LocalTime

LocalDate類表示一個具體的日期,但不包含具體時間,也不包含時區資訊。可以通過LocalDate的靜態方法of()建立一個例項,LocalDate也包含一些方法用來獲取年份,月份,天,星期幾等:

LocalDate localDate = LocalDate.of(2017, 1, 4);     // 初始化一個日期:2017-01-04
int year = localDate.getYear();                     // 年份:2017
Month month = localDate.getMonth();                 // 月份:JANUARY
int dayOfMonth = localDate.getDayOfMonth();         // 月份中的第幾天:4
DayOfWeek dayOfWeek = localDate.getDayOfWeek();     // 一週的第幾天:WEDNESDAY
int length = localDate.lengthOfMonth();             // 月份的天數:31
boolean leapYear = localDate.isLeapYear();          // 是否為閏年:false

也可以呼叫靜態方法now()來獲取當前日期:

LocalDate now = LocalDate.now();

LocalTime和LocalDate類似,他們之間的區別在於LocalDate不包含具體時間,而LocalTime包含具體時間,例如:

LocalTime localTime = LocalTime.of(17, 23, 52);     // 初始化一個時間:17:23:52
int hour = localTime.getHour();                     // 時:17
int minute = localTime.getMinute();                 // 分:23
int second = localTime.getSecond();                 // 秒:52

LocalDateTime

LocalDateTime類是LocalDate和LocalTime的結合體,可以通過of()方法直接建立,也可以呼叫LocalDate的atTime()方法或LocalTime的atDate()方法將LocalDate或LocalTime合併成一個LocalDateTime:

LocalDateTime ldt1 = LocalDateTime.of(2017, Month.JANUARY, 4, 17, 23, 52);
LocalDate localDate = LocalDate.of(2017, Month.JANUARY, 4);
LocalTime localTime = LocalTime.of(17, 23, 52);
LocalDateTime ldt2 = localDate.atTime(localTime);
LocalDateTime也提供用於向LocalDate和LocalTime的轉化:
LocalDate date = ldt1.toLocalDate();
LocalTime time = ldt1.toLocalTime();

Instant

Instant用於表示一個時間戳,它與我們常使用的System.currentTimeMillis()有些類似,不過Instant可以精確到納秒(Nano-Second),System.currentTimeMillis()方法只精確到毫秒(Milli-Second)。如果檢視Instant原始碼,發現它的內部使用了兩個常量,seconds表示從1970-01-01 00:00:00開始到現在的秒數,nanos表示納秒部分(nanos的值不會超過999,999,999)。Instant除了使用now()方法建立外,還可以通過ofEpochSecond方法建立:

Instant instant = Instant.ofEpochSecond(120, 100000);

ofEpochSecond()方法的第一個引數為秒,第二個引數為納秒,上面的程式碼表示從1970-01-01 00:00:00開始後兩分鐘的10萬納秒的時刻,控制檯上的輸出為:

Duration

Duration的內部實現與Instant類似,也是包含兩部分:seconds表示秒,nanos表示納秒。兩者的區別是Instant用於表示一個時間戳(或者說是一個時間點),而Duration表示一個時間段,所以Duration類中不包含now()靜態方法。可以通過Duration.between()方法建立Duration物件:

LocalDateTime from = LocalDateTime.of(2017, Month.JANUARY, 5, 10, 7, 0);    // 2017-01-05 10:07:00
LocalDateTime to = LocalDateTime.of(2017, Month.FEBRUARY, 5, 10, 7, 0);     // 2017-02-05 10:07:00
Duration duration = Duration.between(from, to);     // 表示從 2017-01-05 10:07:00 到 2017-02-05 10:07:00 這段時間
long days = duration.toDays();              // 這段時間的總天數
long hours = duration.toHours();            // 這段時間的小時數
long minutes = duration.toMinutes();        // 這段時間的分鐘數
long seconds = duration.getSeconds();       // 這段時間的秒數
long milliSeconds = duration.toMillis();    // 這段時間的毫秒數
long nanoSeconds = duration.toNanos();      // 這段時間的納秒數
Duration物件還可以通過of()方法建立,該方法接受一個時間段長度,和一個時間單位作為引數:
Duration duration1 = Duration.of(5, ChronoUnit.DAYS);       // 5天
Duration duration2 = Duration.of(1000, ChronoUnit.MILLIS);  // 1000毫秒

Period

Period在概念上和Duration類似,區別在於Period是以年月日來衡量一個時間段,比如2年3個月6天:

Period period = Period.of(2, 3, 6);

Period物件也可以通過between()方法建立,值得注意的是,由於Period是以年月日衡量時間段,所以between()方法只能接收LocalDate型別的引數:

// 2017-01-05 到 2017-02-05 這段時間
Period period = Period.between(
LocalDate.of(2017, 1, 5),
LocalDate.of(2017, 2, 5));

日期的操作和格式化

增加和減少日期

Java 8中的日期/時間類都是不可變的,這是為了保證執行緒安全。當然,新的日期/時間類也提供了方法用於建立物件的可變版本,比如增加一天或者減少一天:

LocalDate date = LocalDate.of(2017, 1, 5);          // 2017-01-05
LocalDate date1 = date.withYear(2016);              // 修改為 2016-01-05
LocalDate date2 = date.withMonth(2);                // 修改為 2017-02-05
LocalDate date3 = date.withDayOfMonth(1);           // 修改為 2017-01-01
LocalDate date4 = date.plusYears(1);                // 增加一年 2018-01-05
LocalDate date5 = date.minusMonths(2);              // 減少兩個月 2016-11-05
LocalDate date6 = date.plus(5, ChronoUnit.DAYS);    // 增加5天 2017-01-10

格式化日期

新的日期API中提供了一個DateTimeFormatter類用於處理日期格式化操作,它被包含在java.time.format包中,Java 8的日期類有一個format()方法用於將日期格式化為字串,該方法接收一個DateTimeFormatter型別引數:

LocalDateTime dateTime = LocalDateTime.now();
String strDate1 = dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);    // 20170105
String strDate2 = dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE);    // 2017-01-05
String strDate3 = dateTime.format(DateTimeFormatter.ISO_LOCAL_TIME);    // 14:20:16.998
String strDate4 = dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));   // 2017-01-05
String strDate5 = dateTime.format(DateTimeFormatter.ofPattern("今天是:YYYY年 MMMM DD日 E", Locale.CHINESE)); // 今天是:2017年 一月 05日 星期四

同樣,日期類也支援將一個字串解析成一個日期物件,例如:

String strDate6 = "2017-01-05";
String strDate7 = "2017-01-05 12:30:05";
LocalDate date = LocalDate.parse(strDate6, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDateTime dateTime1 = LocalDateTime.parse(strDate7, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

時區

Java 8中的時區操作被很大程度上簡化了,新的時區類java.time.ZoneId是原有的java.util.TimeZone類的替代品。ZoneId物件可以通過ZoneId.of()方法建立,也可以通過ZoneId.systemDefault()獲取系統預設時區:

ZoneId shanghaiZoneId = ZoneId.of("Asia/Shanghai");
ZoneId systemZoneId = ZoneId.systemDefault();

of()方法接收一個“區域/城市”的字串作為引數,你可以通過getAvailableZoneIds()方法獲取所有合法的“區域/城市”字串:

Set<String> zoneIds = ZoneId.getAvailableZoneIds();

對於老的時區類TimeZone,Java 8也提供了轉化方法:

ZoneId oldToNewZoneId = TimeZone.getDefault().toZoneId();

有了ZoneId,我們就可以將一個LocalDate、LocalTime或LocalDateTime物件轉化為ZonedDateTime物件:

LocalDateTime localDateTime = LocalDateTime.now();
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, shanghaiZoneId);

將zonedDateTime列印到控制檯為:

2018-07-26T16:30:16.038+08:00[Asia/Shanghai]

ZonedDateTime物件由兩部分構成,LocalDateTime和ZoneId,其中2018-07-26T16:30:16.038部分為LocalDateTime,+08:00[Asia/Shanghai]部分為ZoneId。

另一種表示時區的方式是使用ZoneOffset,它是以當前時間和世界標準時間(UTC)/格林威治時間(GMT的偏差來計算,例如:

ZoneOffset zoneOffset = ZoneOffset.of("+09:00");
LocalDateTime localDateTime = LocalDateTime.now();
OffsetDateTime offsetDateTime = OffsetDateTime.of(localDateTime, zoneOffset);

其他曆法

Java中使用的歷法是ISO 8601日曆系統,它是世界民用曆法,也就是我們所說的公曆。平年有365天,閏年是366天。閏年的定義是:非世紀年,能被4整除;世紀年能被400整除。為了計算的一致性,公元1年的前一年被當做公元0年,以此類推。

此外Java 8還提供了4套其他曆法(很奇怪為什麼沒有漢族人使用的農曆),每套曆法都包含一個日期類,分別是:

  • ThaiBuddhistDate:泰國佛教歷
  • MinguoDate:中華民國曆
  • JapaneseDate:日本歷
  • HijrahDate:伊斯蘭曆

每個日期類都繼承ChronoLocalDate類,所以可以在不知道具體曆法的情況下也可以操作。不過這些曆法一般不常用,除非是有某些特殊需求情況下才會使用。

這些不同的歷法也可以用於向公曆轉換:

LocalDate date = LocalDate.now();
JapaneseDate jpDate = JapaneseDate.from(date);

由於它們都繼承ChronoLocalDate類,所以在不知道具體曆法情況下,可以通過ChronoLocalDate類操作日期:

Chronology jpChronology = Chronology.ofLocale(Locale.JAPANESE);
ChronoLocalDate jpChronoLocalDate = jpChronology.dateNow();

我們在開發過程中應該儘量避免使用ChronoLocalDate,儘量用與曆法無關的方式操作時間,因為不同的歷法計算日期的方式不一樣,比如開發者會在程式中做一些假設,假設一年中有12個月,如果是中國農曆中包含了閏月,一年有可能是13個月,但開發者認為是12個月,多出來的一個月屬於明年的。再比如假設年份是累加的,過了一年就在原來的年份上加一,但日本天皇在換代之後需要重新紀年,所以過了一年年份可能會從1開始計算。

在實際開發過程中建議使用LocalDate,包括儲存、操作、業務規則的解讀;除非需要將程式的輸入或者輸出本地化,這時可以使用ChronoLocalDate類。

常見例子

示例 1、在Java 8中獲取今天的日期

Java 8中的LocalDate用於表示當天日期。和java.util.Date不同,它只有日期,不包含時間。當你僅需要表示日期時就用這個類。

LocalDate today = LocalDate.now(); 
System.out.println("今天的日期是:" + today);

輸出:

今天的日期是:2016-04-18

上面的程式碼建立了當天的日期,不含時間資訊。打印出的日期格式非常友好,不像老的Date類打印出一堆沒有格式化的資訊。

示例 2、在Java 8中獲取當前的年、月、日資訊

LocalDate類提供了獲取年、月、日的快捷方法,其例項還包含很多其它的日期屬性。通過呼叫這些方法就可以很方便的得到需要的日期資訊,不用像以前一樣需要依賴java.util.Calendar類了。

LocalDate today = LocalDate.now(); 
int year = today.getYear(); 
int month = today.getMonthValue(); 
int day = today.getDayOfMonth(); 
System.out.printf("當前的年 : %d  月 : %d  日 : %d%n", year, month, day);

輸出:

當前的年 : 2016  月 : 4  日 : 18

看到了吧,在Java 8中得到年、月、日資訊是這麼簡單直觀,想用就用,沒什麼需要記的。對比看看以前Java是怎麼處理年月日資訊的吧。

示例 3、在Java 8中獲取特定日期

在第一個例子裡,我們通過靜態工廠方法now()非常容易地建立了當天日期,你還可以呼叫另一個有用的工廠方法LocalDate.of()建立任意日期,該方法需要傳入年、月、日做引數,返回對應的LocalDate例項。這個方法的好處是沒再犯老API的設計錯誤,比如年度起始於1900,月份是從0開始等等。日期所見即所得,就像下面這個例子表示了1月14日,沒有任何隱藏機關。

LocalDate dateOfBirth = LocalDate.of(2016, 4, 18); 
System.out.println("你的出生日期是:" + dateOfBirth);

輸出:

你的出生日期是:2016-04-18

可以看到建立的日期完全符合預期,與你寫入的2016年4月18日完全一致。

示例 4、在Java 8中判斷兩個日期是否相等

現實生活中有一類時間處理就是判斷兩個日期是否相等。你常常會檢查今天是不是個特殊的日子,比如生日、紀念日或非交易日。這時就需要把指定的日期與某個特定日期做比較,例如判斷這一天是否是假期。下面這個例子會幫助你用Java 8的方式去解決,你肯定已經想到了,LocalDate過載了equal方法,請看下面的例子:

LocalDate today = LocalDate.now(); 
LocalDate date1 = LocalDate.of(2016, 4, 18); 
if (date1.equals(today)) { 
    System.out.printf("今天 %s 和 date1 %s 是同一天!%n", today, date1);
}

輸出:

今天 2016-04-18 和 date1 2016-04-18 是同一天!

這個例子中我們比較的兩個日期相同。注意,如果比較的日期是字元型的,需要先解析成日期物件再作判斷。對比Java老的日期比較方式,你會感到清風拂面。

示例 5、在Java 8中檢查像生日這種週期性事件

Java中另一個日期時間的處理就是檢查類似每月賬單、結婚紀念日、EMI日或保險繳費日這些週期性事件。如果你在電子商務網站工作,那麼一定會有一個模組用來在聖誕節、感恩節這種節日時向客戶傳送問候郵件。Java中如何檢查這些節日或其它週期性事件呢?答案就是MonthDay類。這個類組合了月份和日,去掉了年,這意味著你可以用它判斷每年都會發生事件。和這個類相似的還有一個YearMonth類。這些類也都是不可變並且執行緒安全的值型別。下面我們通過MonthDay來檢查週期性事件:

LocalDate today = LocalDate.now(); 
LocalDate dateOfBirth = LocalDate.of(2016, 4, 18); 
MonthDay birthday = MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth()); 
MonthDay currentMonthDay = MonthDay.from(today);
if(currentMonthDay.equals(birthday)){ 
    System.out.println("好高興今天是您的生日!!");
}else{
    System.out.println("對不起,今天不是您的生日!!");
}

輸出:

好高興今天是您的生日!!

只要當天的日期和生日匹配,無論是哪一年都會打印出祝賀資訊。你可以把程式整合進系統時鐘,看看生日時是否會受到提醒,或者寫一個單元測試來檢測程式碼是否執行正確。

示例 6、在Java 8中獲取當前時間

與Java 8獲取日期的例子很像,獲取時間使用的是LocalTime類,一個只有時間沒有日期的LocalDate的近親。可以呼叫靜態工廠方法now()來獲取當前時間。預設的格式是hh:mm:ss:nnn。對比一下Java 8之前獲取當前時間的方式。

LocalTime time = LocalTime.now(); 
System.out.println("當前時間是:" + time);

輸出:

當前時間是:23:43:42.200

可以看到當前時間就只包含時間資訊,沒有日期。

示例 7、如何在現有的時間上增加小時

通過增加小時、分、秒來計算將來的時間很常見。Java 8除了不變型別和執行緒安全的好處之外,還提供了更好的plusHours()方法替換add(),並且是相容的。注意,這些方法返回一個全新的LocalTime例項,由於其不可變性,返回後一定要用變數賦值。

LocalTime time = LocalTime.now(); 
LocalTime newTime = time.plusHours(2); // 新增兩小時 
System.out.println("當前時間:" + time + ",兩小時後的時間: " +  newTime);

輸出:

當前時間:23:50:56.195,兩小時後的時間: 01:50:56.195

可以看到,新的時間在當前時間23:50:56.195的基礎上增加了2個小時。和舊版Java的增減時間的處理方式對比一下,看看哪種更好。

示例 8、如何計算一週後的日期

和上個例子計算兩小時以後的時間類似,這個例子會計算一週後的日期。LocalDate日期不包含時間資訊,它的plus()方法用來增加天、周、月,ChronoUnit類聲明瞭這些時間單位。由於LocalDate也是不變型別,返回後一定要用變數賦值。

LocalDate today = LocalDate.now(); 
LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS); 
System.out.println("今天是:" + today + ",一週以後的日期: " + nextWeek);

輸出:

今天是:2016-04-18,一週以後的日期: 2016-04-25

可以看到新日期離當天日期是7天,也就是一週。你可以用同樣的方法增加1個月、1年、1小時、1分鐘甚至一個世紀,更多選項可以檢視Java 8 API中的ChronoUnit類。

示例 9、計算一年前或一年後的日期

繼續上面的例子,上個例子中我們通過LocalDate的plus()方法增加天數、週數或月數,這個例子我們利用minus()方法計算一年前的日期。

LocalDate today = LocalDate.now(); 
LocalDate preYear = today.minus(1, ChronoUnit.YEARS); 
LocalDate nextYear = today.plus(1, ChronoUnit.YEARS); 
System.out.println("今天是:" + today + ",一年前的日期: " + preYear + ",一年後的日期: " + nextYear);

輸出:

今天是:2016-04-18,一年前的日期: 2015-04-18,一年後的日期: 2017-04-18

例子結果中得到了兩個日期,一個2015年、一個2017年、分別是2016年的前一年和後一年。

示例 10、使用Java 8的Clock時鐘類

Java 8增加了一個Clock時鐘類用於獲取當時的時間戳,或當前時區下的日期時間資訊。以前用到System.currentTimeInMillis()和TimeZone.getDefault()的地方都可用Clock替換。

// 得到UTC的時區的日期時間clock物件
Clock clock = Clock.systemUTC(); 
System.out.println("Clock : " + clock);
// 得到基於當前時區的日期時間clock物件
Clock defaultClock = Clock.systemDefaultZone(); 
System.out.println("Clock : " + clock);

輸出:

Clock : SystemClock[Z] 

Clock : SystemClock[Z] 

還可以針對clock時鐘做比較,像下面這個例子:

public class MyClass { 
    // 依賴注入
    private Clock clock;
    ...
    public void process(LocalDate eventDate) {
        if (eventDate.isBefore(LocalDate.now(clock)) {
            ...
        }
    }
}

這種方式在不同時區下處理日期時會非常管用。

示例 11、如何用Java判斷日期是早於還是晚於另一個日期

另一個工作中常見的操作就是如何判斷給定的一個日期是大於某天還是小於某天?在Java 8中,LocalDate類有兩類方法isBefore()和isAfter()用於比較日期。呼叫isBefore()方法時,如果給定日期小於當前日期則返回true。

LocalDate today = LocalDate.now();
LocalDate tomorrow = LocalDate.of(2016, 4, 19); 
if (tomorrow.isAfter(today)) { 
    System.out.println("明天晚於今天!");
}
LocalDate yesterday = today.minus(1, ChronoUnit.DAYS); 
if (yesterday.isBefore(today)) { 
    System.out.println("昨天先於今天!");
}

輸出:

明天晚於今天!

昨天先於今天!

在Java 8中比較日期非常方便,不需要使用額外的Calendar類來做這些基礎工作了。

示例 12、在Java 8中處理時區

Java 8不僅分離了日期和時間,也把時區分離出來了。現在有一系列單獨的類如ZoneId來處理特定時區,ZoneDateTime類來表示某時區下的時間。這在Java 8以前都是GregorianCalendar類來做的。下面這個例子展示瞭如何把本時區的時間轉換成另一個時區的時間。

// Java 8中某時區下的日期和時間
ZoneId america = ZoneId.of("America/New_York"); 
LocalDateTime localtDateAndTime = LocalDateTime.now(); 
ZonedDateTime dateAndTimeInNewYork  = ZonedDateTime.of(localtDateAndTime, america ); 
System.out.println("Current date and time in a particular timezone : " + dateAndTimeInNewYork);

輸出:

Current date and time in a particular timezone : 2016-04-19T23:10:09.251-04:00[America/New_York] 

和以前使用GMT的方式轉換本地時間對比一下。注意,在Java 8以前,一定要牢牢記住時區的名稱,不然就會丟擲下面的異常:

Exception in thread "main" java.time.zone.ZoneRulesException: Unknown time-zone ID: ASIA/Tokyo 

        at java.time.zone.ZoneRulesProvider.getProvider(ZoneRulesProvider.java:272)

        at java.time.zone.ZoneRulesProvider.getRules(ZoneRulesProvider.java:227)

        at java.time.ZoneRegion.ofId(ZoneRegion.java:120)

        at java.time.ZoneId.of(ZoneId.java:403)

        at java.time.ZoneId.of(ZoneId.java:351)

示例 13、如何表示信用卡到期這類固定日期,答案就在YearMonth

與MonthDay檢查重複事件的例子相似,YearMonth是另一個組合類,用於表示信用卡到期日、FD到期日、期貨期權到期日等。還可以用這個類得到當月共有多少天,YearMonth例項的lengthOfMonth()方法可以返回當月的天數,在判斷2月有28天還是29天時非常有用。

YearMonth currentYearMonth = YearMonth.now(); 
System.out.printf("該月的天數 %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth()); 
YearMonth creditCardExpiry = YearMonth.of(2018, Month.FEBRUARY); 
System.out.printf("您的信用卡到期是: %s%n", creditCardExpiry);

輸出:

該月的天數 2016-04: 30

您的信用卡到期是: 2018-02

根據上述資料,你可以提醒客戶信用卡快要到期了,個人認為這個類非常有用。

示例 14、如何在Java 8中檢查閏年

LocalDate類有一個很實用的方法isLeapYear()判斷該例項是否是一個閏年,如果你還是想重新發明輪子,這有一個程式碼示例,純Java邏輯編寫的判斷閏年的程式。

LocalDate today = LocalDate.now(); 
if (today.isLeapYear()) { 
    System.out.println("今年是閏年!");
} else {
    System.out.println("今年不是閏年!");
}

輸出:

今年是閏年!

你可以多寫幾個日期來驗證是否是閏年,最好是寫JUnit單元測試做判斷。

示例 15、計算兩個日期之間的天數和月數

有一個常見日期操作是計算兩個日期之間的天數、週數或月數。在Java 8中可以用java.time.Period類來做計算。下面這個例子中,我們計算了當天和將來某一天之間的月數。

LocalDate today = LocalDate.now(); 
LocalDate java8Release = LocalDate.of(2016, Month.APRIL, 21); 
Period periodToNext = Period.between(today, java8Release); 
System.out.println("2016年4月21日距離今天的天數:" + periodToNext.getDays() );

輸出:

2016年4月21日距離今天的天數:3 

從上面可以看到現在是一月,Java 8的中計算的當前日期是4月18日,中間相隔3天。

示例 16、包含時差資訊的日期和時間

在Java 8中,ZoneOffset類用來表示時區,舉例來說印度與GMT或UTC標準時區相差+05:30,可以通過ZoneOffset.of()靜態方法來 獲取對應的時區。一旦得到了時差就可以通過傳入LocalDateTime和ZoneOffset來建立一個OffSetDateTime物件。

LocalDateTime datetime = LocalDateTime.of(2016, Month.APRIL, 19, 23, 35); 
ZoneOffset offset = ZoneOffset.of("+05:30"); 
OffsetDateTime date = OffsetDateTime.of(datetime, offset); 
System.out.println("包含時差資訊的日期和時間 : " + date);

Output : 

包含時差資訊的日期和時間 : 2016-04-19T23:35+05:30

現在的時間資訊裡已經包含了時區資訊了。注意:OffSetDateTime是對計算機友好的,ZoneDateTime則對人更友好。

示例 17、在Java 8中獲取當前的時間戳

如果你還記得Java 8以前是如何獲得當前時間戳,那麼現在你終於解脫了。Instant類有一個靜態工廠方法now()會返回當前的時間戳,如下所示:

Instant timestamp = Instant.now(); 
System.out.println("時間戳是:" + timestamp);

Output : 

時間戳是:2016-04-18T15:41:06.876Z

時間戳資訊裡同時包含了日期和時間,這和java.util.Date很像。實際上Instant類確實等同於Java 8之前的Date類,你可以使用Date類和Instant類各自的轉換方法互相轉換,例如:Date.from(Instant) 將Instant轉換成java.util.Date,Date.toInstant()則是將Date類轉換成Instant類。

示例 18、在Java 8中如何使用預定義的格式化工具去解析或格式化日期

在Java 8以前的世界裡,日期和時間的格式化非常詭異,唯一的幫助類SimpleDateFormat也是非執行緒安全的,而且用作區域性變數解析和格式化日期時顯得很笨重。幸好執行緒區域性變數能使它在多執行緒環境中變得可用,不過這都是過去時了。Java 8引入了全新的日期時間格式工具,執行緒安全而且使用方便。它自帶了一些常用的內建格式化工具。下面這個例子使用了BASIC_ISO_DATE格式化工具將2016年4月18日格式化成20160418。

String day = "20160418"; 
LocalDate formatted = LocalDate.parse(day, DateTimeFormatter.BASIC_ISO_DATE); 
System.out.printf("從字串中解析的日期: %s 是 %s %n", day, formatted);

輸出 :

從字串中解析的日期: 20160418 是 2016-04-18

很明顯的看出得到的日期和給出的日期是同一天,但是格式不同。

示例 19、如何在Java中使用自定義格式化工具解析日期

上個例子使用了Java內建的格式化工具去解析日期字串。儘管內建格式化工具很好用,有時還是需要定義特定的日期格式,下面這個例子展示瞭如何建立自定義日期格式化工具。例子中的日期格式是“MMM dd yyyy”。可以呼叫DateTimeFormatter的ofPattern()靜態方法並傳入任意格式返回其例項,格式中的字元和以前代表的一樣,M代表月,m代表分。如果格式不規範會丟擲DateTimeParseException異常,不過如果只是把M寫成m這種邏輯錯誤是不會拋異常的。

String day = "2016 04 18"; 
try { 
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
    LocalDate holiday = LocalDate.parse(day, formatter);
    System.out.printf("成功解析字串:%s, 時間是:%s%n", day, holiday);
} catch (DateTimeParseException ex) {
    System.out.printf("%s 解析失敗!", day);
    ex.printStackTrace();
}

Output : 

成功解析字串:2016 04 18, 時間是:2016-04-18

日期值與傳入的字串是匹配的,只是格式不同而已。

示例 20、在Java 8中如何把日期轉換成字串

上 兩個例子都用到了DateTimeFormatter類,主要是從字串解析日期。現在我們反過來,把LocalDateTime日期例項轉換成特定格式的字串。這是迄今為止Java日期轉字串最為簡單的方式了。下面的例子將返回一個代表日期的格式化字串。和前面類似,還是需要建立DateTimeFormatter例項並傳入格式,但這回呼叫的是format()方法,而非parse()方法。這個方法會把傳入的日期轉化成指定格式的字串。

LocalDateTime arrivalDate  = LocalDateTime.now(); 
try { 
    DateTimeFormatter format = DateTimeFormatter.ofPattern("MMM dd yyyy  hh:mm a");
    String landing = arrivalDate.format(format);
    System.out.printf("格式化的日期時間:  %s %n", landing);
} catch (DateTimeException ex) {
    System.out.printf("%s 不能格式化!%n", arrivalDate);
    ex.printStackTrace();
}

輸出:

格式化的日期時間:  四月 19 2016  12:02 上午

當前時間被指定的“MMM dd yyyy hh:mm a”格式格式化,格式包含3個代表月的字串,時間後面帶有AM和PM標記。

Java 8日期時間API的重點

通過這些例子,你肯定已經掌握了Java 8日期時間API的新知識點。現在我們來回顧一下這個優雅API的使用要點:

  1. 提供了javax.time.ZoneId獲取時區。
  2. 提供了LocalDate``和LocalTime類。
  3. Java 8的所有日期和時間API都是不可變類並且執行緒安全,而現有的Date和Calendar API中的java.util.Date和SimpleDateFormat是非執行緒安全的。
  4. 主包是java.time,包含了表示日期、時間、時間間隔的一些類。裡面有兩個子包java.time.format用於格式化, java.time.temporal用於更底層的操作。
  5. 時區代表了地球上某個區域內普遍使用的標準時間。每個時區都有一個代號,格式通常由區域/城市構成(Asia/Tokyo),在加上與格林威治或UTC的時差。例如:東京的時差是+09:00。
  6. OffsetDateTime類實際上組合了LocalDateTime類和ZoneOffset類。用來表示包含和格林威治或UTC時差的完整日期(年、月、日)和時間(時、分、秒、納秒)資訊。
  7. DateTimeFormatter類用來格式化和解析時間。與SimpleDateFormat不同,這個類不可變並且執行緒安全,需要時可以給靜態常量賦值。DateTimeFormatter類提供了大量的內建格式化工具,同時也允許你自定義。在轉換方面也提供了parse()將字串解析成日期,如果解析出錯會丟擲DateTimeParseException。DateTimeFormatter類同時還有format()用來格式化日期,如果出錯會丟擲DateTimeException異常。
  8. 再補充一點,日期格式“MMM d yyyy”和“MMM dd yyyy”有一些微妙的不同,第一個格式可以解析“Jan 2 2014”和“Jan 14 2014”,而第二個在解析“Jan 2 2014”就會拋異常,因為第二個格式裡要求日必須是兩位的。如果想修正,你必須在日期只有個位數時在前面補零,就是說“Jan 2 2014”應該寫成 “Jan 02 2014”。

如何使用Java 8的全新日期時間API就介紹到這了。這些簡單的例子對幫助理解新API非常有用。由於這些例子都基於真實任務,你在做Java日期程式設計時不用再東張西望了。我們學會了如何建立並操作日期例項,學習了純日期、以及包含時間資訊和時差資訊的日期、學會了怎樣計算兩個日期的間隔,這些在計算當天與某個特定日期間隔的例子中都有所展示。 我們還學到了在Java 8中如何執行緒安全地解析和格式化日期,不用再使用蹩腳的執行緒區域性變數技巧,也不用依賴Joda Time第三方庫。新API可以作為處理日期時間操作的標準。

參考:

相關推薦

Java日期學習筆記JDK1.8特性

Java 8另一個新增的重要特性就是引入了新的時間和日期API,它們被包含在java.time包中。藉助新的時間和日期API可以以更簡潔的方法處理時間和日期。 在介紹本篇文章內容之前,我們先來討論Java 8為什麼要引入新的日期API,與之前的時間和日期處理方式有什麼不同?

Java基礎JDK1.8特性

JDK1.8新特性 lambda表示式 Lambda lambda作用:lambda是一個語法糖,簡化匿名內部類的使用。 lambda使用條件 引數或者變數必須是介面 介面中只包含一個抽象方法 lambda格式 (引數型別 引數名稱 …)-> { 程

Kotlin學習筆記Kotlin與Java對比

本篇文章我們將通過類比Java語法的方式學習Kotlin的基本語法和常用操作 基本型別 Java Kotlin 位寬 double Double 64 float Float 32 long L

javascript學習筆記定義函數、調用函數、參數、返回值、局部和全局變量

兩個 cnblogs bsp 結果 value ava ase com 調用 定義函數、調用函數、參數、返回值 關鍵字function定義函數,格式如下: function 函數名(){ 函數體 } 調用函數、參數、返回值的規則和c語言規則類似。 1 <!DOC

CSS學習筆記特性

code 背景色 左移 line tex lin 安裝 其中 cas 一、顏色特性 1. 前景色:color 用種方式指定前景色,3種方式分別是rgb顏色,#16進制編碼,顏色名稱: color: rgb(100,100,100); color: #ee3e80; col

Unity3D之Mecanim動畫系統學習筆記模型導入

leg character ... sdk ocs 物體 mat 版本 sset 我們要在Unity3D中使用上模型和動畫,需要經過下面幾個階段的制作,下面以一個人形的模型開發為準來介紹。 模型制作 模型建模(Modelling) 我們的美術在建模時一般會制作一個稱為

kubernetes學習筆記bashborad安裝配置

tag log struct recommend ide col create part describe 官方推薦方法: 連接:https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashb

python3學習筆記Python初識

區別 說明 from 學習筆記 情況 不能 col 需要 學習 一、算法 在開始認真地編程之前,首先來解釋下什麽是計算機程序設計。簡單地說,它就是告訴計算機要做什麽。計算機可以做很多事情,但是它不會自己思考,需要我們告訴它具體細節,並且使用計算機能夠理解的語言把算法告

Linux學習筆記實戰-根據微服務端口號關閉進程

java 地方 img linux學習 區分 殺死進程 項目組 cannot home 前言 現在項目組基本都用Springboot,每個服務占用一個端口號,有時需要選擇性的關閉,但在任務管理器上他們的名稱都是java.exe,無法區分,這才學以致用。 killPort.s

Guava學習筆記基礎Joiner,Objects,Splitter及Strings

nonnull obj expect null dto 字符 情況 core cte 添加Maven依賴 JoinerTest import com.google.common.base.Joiner; import org.junit.Assert; import org

opencv 視覺項目學習筆記 基於 svm 和 knn 車牌識別

its ++ eas -a rect() repr poi obj std 車牌識別的屬於常見的 模式識別 ,其基本流程為下面三個步驟: 1) 分割: 檢測並檢測圖像中感興趣區域; 2)特征提取: 對字符圖像集中的每個部分進行提取; 3)分類: 判斷圖像快是不是車牌或者 每

安卓開發學習筆記Android Stuidio無法引用Intent來創建對象,出現cannot resolve xxx

編譯器 port stact 消失 click first 紅色 xxx font 筆者在進行安卓開發時,發現自己的代碼語法完全沒有問題。尤其是創建intent對象的時候,語法完全是正確的,但是Android Stuidio卻顯示報錯,Intent類顯示為紅色,如圖所示:

Django學習筆記使用Template讓HTML、CSS參與網頁建立

Django學習筆記(二):使用Template讓HTML、CSS參與網頁建立 通過本文章實現: 瞭解Django中Template的使用 讓HTML、CSS等參與網頁建立 利用靜態檔案應用網頁樣式 一、Template的使用 Template是Django利用渲染器將模板檔案與資料內容結合

Javaweb學習筆記servlet初體驗、HTTP協議

目錄 1.Servlet體驗 1.1servlet的繼承體系 1.2手動開發動態web資源 1.3工具開發動態資源 2.HTTP協議 2.1概念 2.2請求資訊 2.2.1請求行 2.2.2請求頭 2.2.3空行與實體內容 2.3HttpServlet

學習筆記使用K近鄰演算法檢測Web異常操作

使用全量比較,而不是最頻繁和最不頻繁的比較。 1.資料蒐集        我們使用詞集的模型,將全部命令去重後形成一個大型向量空間,每個命令代表一個特徵,首先通過遍歷全部命令,生成對應詞集。 with open(filename) as f: fo

Spring MVC 學習筆記@RequestMapping用法詳解

一、@RequestMapping 簡介 在Spring MVC 中使用 @RequestMapping 來對映請求,也就是通過它來指定控制器可以處理哪些URL請求,相當於Servlet中在web.xml中配置 <servlet>  

d3.js學習筆記完整的柱狀圖示例

本示例採用d3的3.x版本庫,示例程式碼如下: d3.fullAxisExample = function() { var width = 240; var height = 240; //在 body 裡新增一個 SVG 畫布

微信小程式——學習筆記邏輯層1

邏輯層將資料進行處理後傳送給檢視層,同時接受檢視層的事件反饋。 用App()函式註冊一個小程式。 當小程式初始化完成時,會觸發 onLaunch(全域性只觸發一次) 當小程式啟動,或從後臺進入前臺顯示,會觸發 onShow 當小程式從前臺進入後臺,會觸發 onHide 當小程式發生指令碼錯

Java Web學習筆記

Servlet的註冊與執行: Servlet程式必須通過Servlet容器來啟動執行,並且儲存目錄有特殊要求,需要儲存在< WEB應用程式目錄 >\WEB-INF\classes\目錄中。 Servlet程式必須在WEB應用程式的web.xml檔案中進行註冊和對映其訪問路徑

Java NIO 學習筆記----聚集和分散,通道到通道

目錄: Java NIO 學習筆記(一)----概述,Channel/Buffer Java NIO 學習筆記(二)----聚集和分散,通道到通道 Scatter / Gather 通道的聚集和分散操作 NIO 具有內建的 scatter/gather 支援,用於描述讀取和寫入通道的操作。 分散(