為啥在程式設計的世界裡,日期時間處理這麼難?

果子爸聊技术發表於2024-04-28

做過開發的同學都有體會,日期時間的處理很麻煩,稍不注意就會出現日期格式不一樣,或者時間差8小時。

那為何日期時間這麼難處理呢?今天我們就來梳理一下在程式設計的世界裡,為啥日期時間這麼難處理。

我們先來熟悉幾個概念
1、時區(Timezone)

由於各地的日出日落時間不同,所以把全球所有地區共分為24個時區,每個時區跨越360/24=15個經度,比如倫敦位於北京的西面,那麼當北京的太陽已經升起的時候,倫敦還要再過 8 小時才能迎來黎明。也就是說,倫敦比北京晚 8 小時。而東京位於北京的東面,所以東京的日出比北京早 1 小時。

一定有人聽過中國的時區是東八區,那這個東八區到底是啥意思呢?

以本初子午線(即0度經線,也稱格林尼治子午線)為基準,向東每跨越15度經度就增加一個時區,向西則相應減去一個時區

中國位於東經約73度至135度之間,理論上跨越了五個時區,即東五區至東九區。然而,為了便於全國範圍內的行政管理、交通排程、通訊交流以及社會生活的統一協調,中國選擇了東經120度所在的東八區作為全國統一的標準時間,即“北京時間”

如果我們想知道當北京是中午 12:00 的時候,東京是什麼時間,可以先用 12:00 減去當前時區 +08:00,換算成倫敦時間 04:00,再加上目標時區 +09:00,就得到了東京時間 13:00

2、格林威治時間(Greenwich Mean Time, GMT)

GMT是基於英國倫敦格林尼治天文臺所在經線(本初子午線)的地方時。作為曾經的世界標準時間,它不考慮夏令時(DST)調整。現在通常用作UTC(協調世界時)的同義詞,儘管在實際應用中可能仍有細微差別

3、協調世界時(Coordinated Universal Time, UTC)

UTC又稱世界統一時間、世界標準時間、國際協調時間。是當前國際通用的時間標準,它基於原子鐘的測量結果,與GMT基本相同,但在需要插入閏秒以保持與地球自轉的長期同步時,兩者會有所區別。UTC時間通常以“UTC”後跟時分秒錶示,如“2024-04-26T12:30:00Z”

4、夏令時(Daylight Saving Time, DST)

一到夏天,白天就變得很長,特別是高緯度地區會更明顯,到了北極或南極,太陽整天都不會落下去,這就是極晝。為了充分利用大自然的饋贈,有些地方會實行夏令時,也就是說到了夏天,就人為把表撥快一個小時,讓人們早點起床、早點睡覺,這樣可以節省一些照明的電費。中國曾經短暫實行過幾年夏令時,不過後來認為它帶來的負面影響超過收益,就取消了。但是世界上仍然有很多地方實施夏令時,當設計全球化應用的時候,必須得考慮它

5、CST

CST這個縮寫它可以同時代表四個不同的時間:

  • China Standard Time ── 中國標準時間(UTC+8)
  • Central Standard Time (USA) ── 美國中央時區(UTC-6)
  • Central Standard Time (Australia) ── 澳大利亞中央時區 (UTC+9)
  • Cuba Standard Time UTC-4:00 ── 古巴標準時區(UTC-4)

因此,使用CST時需注意其具體指代哪個時區,以免產生混淆

6、ISO 8601

這是一個國際標準化組織(International Organization for Standardization, ISO)制定的日期和時間表示法。ISO 8601提供了一種清晰、無歧義的日期和時間格式,如“YYYY-MM-DD”表示日期,“HH:MM:SS”表示時間,兩者結合為“YYYY-MM-DDTHH:MM:SS”(如“2024-04-26T12:30:00”)。該標準還支援時區表示,如“2024-04-26T12:30:00+01:00”表示在UTC基礎上加1小時的時區時間

7、時間戳(Timestamp)

時間戳是一種精確記錄某一事件發生時刻的方法,通常是一個從特定起點(如Unix時間戳的起點是1970年1月1日0時0分0秒(UTC))開始計數的整數或浮點數,單位通常是秒或毫秒。例如,Unix時間戳“1678882200”表示距離該起點的秒數。時間戳常用於計算機系統中記錄事件發生的精確時間點,便於比較和排序

8、網路時間協議(Network Time Protocol,NTP)

NTP是一種網路協議,用於在分散式系統中同步各個裝置的時鐘,以確保所有參與通訊的節點擁有高度一致的時間。NTP透過在客戶端與NTP伺服器之間交換時間戳資訊,透過複雜的演算法計算時鐘偏差和網路延遲,從而調整客戶端系統的時鐘以接近伺服器提供的準確時間。NTP對於確保網路應用中的事務完整性、日誌記錄準確性、分散式系統協作等具有重要意義

9、本地時間

本地時間是指某個特定地點當前所採用的時間,包括了時區偏移和可能的夏令時調整。例如,“北京時間”對應東八區時間,表示為“UTC+08:00”。本地時間通常用於日常生活和商業活動,如“2024年4月26日 10:30 AM(北京)”

怎麼樣?是不是看了上面的梳理,感覺有些頭大,這還沒有算上中國的農曆、還有比如康熙、乾隆多少年,都可以表示日期和時間。

那這麼多時間格式,你在做專案的時候不可能全部會用到,一般如果不涉及到國際業務,可以使用本地時間來簡單化處理。

可以只用儲存時間戳,不儲存時區資訊,這樣可以依賴系統自帶的時區來簡單處理日期時間

Java中是如何處理日期時間的

在Java 8以前,日期和時間的處理主要依賴於java.util.Datejava.util.Calendar這兩個類

java.util.Date

java.util.Date類實際上包含了日期和時間,但它的toString()方法預設輸出的是帶有當前時區的時間,而getYear(), getMonth(), getDate()等方法已經過時,並且月份是從0開始計數的(1表示二月,依此類推)

import java.util.Date;

public class OldDateExample {
    public static void main(String[] args) {
        // 獲取當前日期和時間
        Date currentDate = new Date();

        // 列印當前日期和時間
        System.out.println("Current date and time (in default format): " + currentDate);

        // 轉換為自定義格式,需要用到SimpleDateFormat類
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formattedDate = sdf.format(currentDate);
        System.out.println("Current date and time (formatted): " + formattedDate);
        
        // 設定一個新的日期(此方式已過時,但仍可用於演示)
        Calendar cal = Calendar.getInstance();
        cal.set(2000, Calendar.JANUARY, 1, 0, 0, 0); // 注意月份是Calendar.JANUARY而不是1
        Date specificDate = cal.getTime();
        System.out.println("Specific date (old style): " + sdf.format(specificDate));
    }
}

java.util.Calendar

java.util.Calendar 類是用來處理日期和時間欄位以及完成日期時間欄位運算的抽象類,它為特定的日曆系統提供了方法,如獲取和設定日期欄位,計算與日期相關的值等。但它不是執行緒安全的,並且API使用繁瑣

import java.util.Calendar;
import java.text.SimpleDateFormat;

public class OldCalendarExample {
    public static void main(String[] args) {
        // 建立一個Calendar例項
        Calendar calendar = Calendar.getInstance();

        // 設定日期為2000年1月1日
        calendar.set(Calendar.YEAR, 2000);
        calendar.set(Calendar.MONTH, Calendar.JANUARY); // 注意月份需要減1,因為它是從0開始計數的
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);

        // 獲取Date物件
        Date specificDate = calendar.getTime();

        // 將Date物件格式化為字串
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formattedDate = sdf.format(specificDate);

        System.out.println("Specific date using Calendar: " + formattedDate);
    }
}

這些類在設計和使用上存在一些不足,比如建構函式隱含了當前時區,日期時間的表示不夠直觀,而且API使用起來相對複雜

在實際開發中,強烈建議使用Java 8及以上版本的日期時間API,因為它們更簡潔、直觀且避免了許多老API中存在的陷阱

Java8新版日期時間處理方法

Java 8及以後版本中推薦使用的java.time包下的類來進行日期和時間的操作

  1. 基礎日期時間類

    • LocalDate:表示日期,不包含任何時間資訊,只關注年、月、日。
    • LocalTime:表示時間,不包含日期,只關注小時、分鐘、秒和納秒。
    • LocalDateTime:結合了日期和時間,依然不包含時區資訊,用於表示具體的日期和時間點。
  2. 時區相關類

    • ZonedDateTime:包含日期、時間及時區資訊,用於表示具體某一地點的日期和時間。
    • ZoneId:表示時區,替代了舊版的TimeZone類。
  3. 瞬時時間類

    • Instant:表示時間線上某一特定的瞬間,通常對應 Unix 時間戳,內部以 epoch 秒和納秒錶示。
  4. 日期時間格式化與解析

    • DateTimeFormatter:用於日期和時間的格式化和解析,可自定義日期時間字串的樣式

示例程式碼:

// 建立 LocalDate 和 LocalTime
LocalDate today = LocalDate.now();
LocalTime currentTime = LocalTime.now();

// 建立 LocalDateTime
LocalDateTime now = LocalDateTime.now();

// 建立 ZonedDateTime
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));

// 建立 Instant
Instant currentInstant = Instant.now();

// 格式化和解析日期時間
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedNow = now.format(formatter);
LocalDateTime parsedDateTime = LocalDateTime.parse("2024-04-27 14:30:00", formatter);

// 日期時間操作
LocalDate tomorrow = today.plusDays(1);
LocalTime nextHour = currentTime.plusHours(1);

// 與時區相關的轉換
ZonedDateTime utcTime = zdt.withZoneSameInstant(ZoneId.of("Etc/UTC"));

MySQL中日期時間的處理

我們實際開發當中,大部分時候都需要將日期時間寫入MySQL資料庫中進行持久化,那Java與MySQL之間是如何進行日期時間處理的呢?

在MySQL中處理日期時間,主要透過幾種預定義的資料型別來儲存和操作日期和時間值

  • DATE:僅儲存日期,格式為 'YYYY-MM-DD',範圍從'1000-01-01'到'9999-12-31'
  • TIME:僅儲存時間,格式為 'HH:MM:SS'
  • DATETIME:儲存日期和時間,格式為 'YYYY-MM-DD HH:MM:SS',範圍從'1000-01-01 00:00:00.000000'到'9999-12-31 23:59:59.999999'
  • TIMESTAMP:類似於DATETIME,但自動更新到當前時間戳,並且受時區影響。其儲存範圍與DATETIME相似,但具體行為會根據MySQL的配置有所不同,特別是與伺服器時區相關的自動調整

在Java 8及以後版本中,與MySQL的這些日期時間型別相對應,可以使用java.time包下的類

  • DATE 對應 LocalDate:只包含日期部分,無時間資訊。
  • TIME 對應 LocalTime:只包含時間部分,無日期資訊。
  • DATETIME 可以對應 LocalDateTime:同時包含日期和時間,但不包含時區資訊。如果需要時區資訊,可以使用 ZonedDateTime 結合具體的時區資訊。
  • TIMESTAMP 在Java中通常與 LocalDateTimeInstant 相對應,具體取決於應用場景。如果TIMESTAMP用於記錄帶有時區的絕對時間點,使用 Instant 更合適,因為它也是基於UTC時間的;如果是無時區的日期時間,則使用 LocalDateTime

注意:一般情況下,我們不需要儲存時區,因為大部分專案都不涉及到國際化,只在國內開展業務的話,是不需要考慮時區的,依賴本地伺服器的時區就可以減化日期時間的處理,但如果你的業務涉及到國際化,就必須考慮時區的問題了。

一般推薦將日期時間轉換為UTC時區後儲存到資料庫,從資料庫讀取日期時間資料時,根據需要將其轉換為應用程式的本地時區或使用者指定的時區,以減少時區轉換帶來的問題。

你的專案中是如何處理日期時間和時區的問題呢?歡迎跟我留言來進行討論!

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章