iT邦幫忙

2023 iThome 鐵人賽

DAY 3
0
Software Development

救救我啊我救我!CRUD 工程師的惡補日記系列 第 3

Java 8 推出的日期時間套件(上)

  • 分享至 

  • xImage
  •  

當時是 2018 年吧,筆者在前公司從零開發新產品,至今對於日期時間一律都是使用「java.util.Date」類別來處理。若想做增減,還要搭配 Apache 提供的 DateUtils 來完成。

後來才在網路上得知如「LocalDateTime」這種 Java 8 推出的類別。這兩天就來介紹它們。除了了解操作方式,也要知道如何在 Spring Boot 的 controller 接收它們。

此篇亦轉載到個人部落格


一、時間戳 Timestamp

在建立 Java 的 Date 物件時,透過建構子能傳入 long 整數,直接定義它的時間。這個整數,我們稱之為時間戳(timestamp)。這個 timestamp,是從 UTC 時間 1970-01-01 00:00:00 開始計算,通常以秒(second)或毫秒(millisecond)為單位,不因時區而有差別。而 Date 的建構子是接收毫秒。

Unix Time Stamp 這個網站可看到目前的 timestamp,也能將其與日期時間互換。

附帶一提,在表示時區時,常會看到 GMT 或 UTC 的字眼。這兩者測量時間的方式不同,但由於差距不到一秒,因此在生活中混用是沒有什麼大問題。

Ref:雜智社GMT?UTC?到底有什麼差別:時間的故事下集

二、Date 的缺點

(一)未將日期與時間分開

Date 所儲存的是 timestamp,同時代表了日期與時間。若某些情境只需要其中一個,便會形成多餘資料。例如想要紀錄出生年月日是 1996-01-01,則 Date 物件會被表示成類似 1996-01-01 00:00:00 的樣子,明明我們不需要時分秒的。

(二)是可變的(mutable)

Date 可透過呼叫如 setYearsetHour 等方法,來改變該物件所代表的 timestamp。若該物件被傳遞到其他程式,意外地遭到修改,這其實是不安全的。

(三)取得資訊時不方便

Date 本身帶有如 getYeargetMonth 等方法,來單獨取出年、月等資訊,但由於被 deprecated 了,我們就不討論。

透過 Calendar 可以幫忙做這件事,用法如下。

var calendar = Calendar.getInstance();
// calendar.setTime(new Date()); // optional

System.out.println(calendar.get(Calendar.MONTH)); // 7
System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); // 25
System.out.println(calendar.get(Calendar.HOUR)); // 11
System.out.println(calendar.get(Calendar.MINUTE)); // 49

Calendar 並未提供如 getYear 之類的方法,而是呼叫 get 再自行傳入參數,較麻煩。

還有一點是月份值是從 0 開始。意即在 8 月呼叫 calendar.get(Calendar.MONTH),反而會得到 7。

(四)無時區概念

Date 本身沒有沒有時區的概念,畢竟儲存的 timestamp,不因時區而有差異。即便呼叫 toString 的結果會因時區而有不同,但那是從電腦系統取得時區的緣故。

若要進行時區處理,需透過 SimpleDateFormat 的輔助。

var now = new Date();

// 目前時區
var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(now)); // 2023-08-25 11:22:39

// 變換時區
sdf.setTimeZone(TimeZone.getTimeZone("America/NewYork"));
System.out.println(sdf.format(now)); // 2023-08-25 03:22:39

(五)SimpleDateFormat 執行緒不安全

SimpleDateFormat 可以將日期時間的字串與 Date 物件互相轉換。然而在呼叫相關的 formatparse 方法時,會有執行緒不安全的問題。

下面整理一些解法:

  • 在每次使用時,都 new 一個新的 SimpleDateFormat 物件。但這會產生額外的效能開銷。
  • 自己寫一個 util method,藉由 synchronized 關鍵字,限制同時間只有一個執行緒可用。然而在 server 上會導致 request 被阻塞。
  • 在 Spring Boot 使用「ThreadLocal」,讓每個 request 都有單例(singleton)可用。

Ref:
一文告訴你Java日期時間API到底有多爛
SimpleDateFormat線程不安全及解決辦法

三、無時區的日期時間

看完了 java.util.Date 的缺點,接下來讓我們認識 Java 8 新推出的日期時間套件。它們位於 java.time 的 package 中,概念上都是對 timestamp 值進行封裝,並提供一些方法呼叫。

(一)LocalDate

代表日期,只儲存了年、月、日。

var date1 = LocalDate.now();
System.out.println(date1); // 2023-08-25

var date2 = LocalDate.of(2018, 8, 1);
date2 = date2.plusDays(10); // 增加 10 天

System.out.println(date2.getYear()); // 2018
System.out.println(date2.getMonth()); // AUGUST
System.out.println(date2.getMonthValue()); // 8
System.out.println(date2.getDayOfMonth()); // 11

要注意的是,該物件儲存的日期是不可變的(immutable),因此進行日期的增減後,要將回傳值接起來。

(二)LocalTime

代表時間,除了儲存時、分、秒,也能儲存奈秒。

var time1 = LocalTime.now();
System.out.println(time1); // 12:39:53.561241100

var time2 = LocalTime.of(12, 30, 15);
time2 = time2.minusSeconds(10); // 減少 10 秒

System.out.println(time2.getHour()); // 12
System.out.println(time2.getMinute()); // 30
System.out.println(time2.getSecond()); // 5

(三)LocalDateTime

封裝了上述的 LocalDateLocalTime

var dateTime1 = LocalDateTime.now();
System.out.println(dateTime1); // 2023-08-25T12:53:55.822241500

var dateTime2 = LocalDateTime.of(2023, 8, 24, 12, 30, 15);
dateTime2 = dateTime2.plusDays(180).minusMinutes(5);

System.out.println(dateTime2.getYear()); // 2024
System.out.println(dateTime2.getMonthValue()); // 2
System.out.println(dateTime2.getDayOfMonth()); // 20
System.out.println(dateTime2.getMinute()); // 25

(四)DateTimeFormatter

DateTimeFormatter 可用來進行日期時間物件,與字串之間的互換。而且不必像舊的 SimpleDateFormat 那樣,還要處理 ParseException 這個 checked exception。

var formatter = DateTimeFormatter.ofPattern("yyyyMMdd HHmmss");

var dateTime = LocalDateTime.parse("20230824 123015", formatter);
System.out.println(dateTime); // 2023-08-24T12:30:15
System.out.println(formatter.format(dateTime)); // 20230824 123015

Ref:Java 8 新增的時間系列用法

下一篇文章會介紹具有時區概念的「ZonedDateTime」。以及如何在 Spring Boot 類別以 request body 接收這些時間日期類別。


今日文章到此結束!
最後推廣一下自己的部落格,我是「新手工程師的程式教室」的作者,請多指教/images/emoticon/emoticon41.gif


上一篇
【Java】使用 BigDecimal 進行精確運算
下一篇
Java 8 推出的日期時間套件(下)
系列文
救救我啊我救我!CRUD 工程師的惡補日記50
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言