Java利用java.time處理時間的方法全解析
一、引言:Java時間處理的“血淚史”
但凡做過Java開發(fā)的程序員,幾乎都踩過時間處理的坑。從JDK 1.0的Date到JDK 1.1的Calendar,舊版時間API的設(shè)計缺陷像“達摩克利斯之劍”,隨時可能引發(fā)線上故障。

1.1 舊API的三大致命缺陷
(1)線程安全噩夢:SimpleDateFormat
SimpleDateFormat是最典型的反面教材,這個類并非線程安全,但很多開發(fā)者習(xí)慣將其定義為靜態(tài)常量復(fù)用:
// 錯誤示范:靜態(tài)SimpleDateFormat引發(fā)線程安全問題
public class TimeUtils {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String format(Date date) {
return sdf.format(date); // 多線程調(diào)用必出問題
}
}
線上環(huán)境中,多線程并發(fā)調(diào)用時,會出現(xiàn)日期解析錯誤、返回亂碼甚至空指針異常。筆者曾遇到過生產(chǎn)環(huán)境因這個問題,導(dǎo)致訂單創(chuàng)建時間顯示為“1970-01-01”的嚴重故障,排查了整整8小時。
(2)API設(shè)計:Calendar的“迷之操作”
Calendar類為了兼容各種歷法,設(shè)計得極其晦澀:
- 月份從0開始(1月是0,12月是11),新手必踩;
- 獲取/設(shè)置字段需要通過常量(
Calendar.MONTH),代碼可讀性差; - 時區(qū)處理隱式化,默認依賴系統(tǒng)時區(qū),跨時區(qū)場景極易出錯;
- 實例是可變的,修改后會影響原對象,引發(fā)隱蔽的邏輯錯誤。
// Calendar的設(shè)計示例 Calendar cal = Calendar.getInstance(); cal.set(2026, 2, 24); // 本意是2026年3月24日,實際是2026年2月24日! cal.add(Calendar.HOUR, 24); // 加24小時,卻可能因夏令時變成23/25小時
(3)時區(qū)處理混亂:Date的“偽UTC”
java.util.Date本質(zhì)上是一個時間戳(從1970-01-01 UTC開始的毫秒數(shù)),但它的toString()方法會默認轉(zhuǎn)換為系統(tǒng)本地時區(qū),導(dǎo)致開發(fā)者誤以為Date對象包含時區(qū)信息:
Date date = new Date(); System.out.println(date); // 輸出帶本地時區(qū)的字符串,易誤導(dǎo)開發(fā)者 // 實際輸出:Mon Mar 24 14:30:00 CST 2026(CST是中國標準時間)
跨時區(qū)系統(tǒng)中,這種設(shè)計極易導(dǎo)致“存儲的是UTC時間,展示時卻未轉(zhuǎn)換”的問題。
1.2 java.time的誕生:JSR 310拯救Java時間處理
2014年,Java 8正式引入java.time包(JSR 310),由Joda-Time的作者Stephen Colebourne主導(dǎo)設(shè)計,徹底解決了舊API的所有痛點:
- 不可變性:所有時間類都是不可變對象,線程安全;
- 職責(zé)單一:拆分LocalDate/LocalTime/ZonedDateTime等類,各司其職;
- 時區(qū)顯式化:時區(qū)處理必須顯式指定,避免隱式轉(zhuǎn)換;
- API人性化:方法命名直觀(
plusDays()/withMonth()),告別魔法值; - 兼容ISO 8601:原生支持國際標準時間格式。
如今,Java 8已成為企業(yè)開發(fā)的標配,java.time也取代舊API成為主流。本文將從核心類設(shè)計、實戰(zhàn)案例、工程落地等維度,全方位解析java.time的使用技巧。
二、java.time核心類體系:萬字拆解
java.time的設(shè)計遵循“單一職責(zé)”和“不可變”原則,核心類可分為四大類:日期時間類、時區(qū)/偏移量類、時間段類、工具類。
2.1 類設(shè)計三大核心原則
| 原則 | 說明 | 優(yōu)勢 |
|---|---|---|
| 不可變性 | 所有操作返回新對象,原對象不變 | 線程安全,無副作用 |
| 職責(zé)單一 | 日期/時間/時區(qū)拆分,不混用 | 語義清晰,避免歧義 |
| 時區(qū)顯式化 | 時區(qū)相關(guān)操作必須顯式指定ZoneId | 杜絕隱式時區(qū)轉(zhuǎn)換坑 |
2.2 核心類逐個解析(附場景+禁忌)
(1)LocalDate:無時區(qū)的純?nèi)掌?/p>
定義:僅包含年、月、日,不含時間和時區(qū),適用于“只關(guān)心日期”的場景。
適用場景:
- 生日、節(jié)假日、訂單日期(僅需記錄年月日);
- 數(shù)據(jù)庫中存儲的“日期型”字段(DATE類型);
- 無需跨時區(qū)的本地業(yè)務(wù)日期(如本地超市的促銷日期)。
禁忌:
- 不能用于跨時區(qū)場景(如全球會議時間);
- 不能用于需要精確到時分秒的業(yè)務(wù)(如訂單支付時間)。
核心API示例:
// 創(chuàng)建LocalDate
LocalDate today = LocalDate.now(); // 當前本地日期(2026-03-24)
LocalDate specifiedDate = LocalDate.of(2026, 3, 24); // 指定日期
LocalDate fromString = LocalDate.parse("2026-03-24"); // 解析ISO格式字符串
// 日期運算
LocalDate tomorrow = today.plusDays(1); // 加1天
LocalDate lastMonth = today.minusMonths(1); // 減1個月
LocalDate firstDayOfYear = today.with(TemporalAdjusters.firstDayOfYear()); // 本年第一天
// 日期判斷
boolean isLeapYear = today.isLeapYear(); // 是否閏年
boolean isBefore = today.isBefore(specifiedDate); // 是否在指定日期之前
(2)LocalTime:無日期的純時間
定義:僅包含時、分、秒、納秒,不含日期和時區(qū),適用于“只關(guān)心時間點”的場景。
適用場景:
- 鬧鐘時間(每天7點起床);
- 門禁打卡時間(上班9:00,下班18:00);
- 本地業(yè)務(wù)的固定時間點(如超市每天22:00關(guān)門)。
禁忌:
- 不能用于需要日期的場景;
- 跨時區(qū)時間對比(如紐約9:00 vs 上海9:00)。
核心API示例:
// 創(chuàng)建LocalTime
LocalTime now = LocalTime.now(); // 當前本地時間(14:30:45.123)
LocalTime specifiedTime = LocalTime.of(9, 0); // 9:00:00
LocalTime fromString = LocalTime.parse("14:30:00"); // 解析ISO格式
// 時間運算
LocalTime oneHourLater = now.plusHours(1); // 加1小時
LocalTime tenMinutesEarlier = now.minusMinutes(10); // 減10分鐘
// 時間判斷
boolean isAfter = now.isAfter(LocalTime.of(12, 0)); // 是否在12點之后
(3)LocalDateTime:無時區(qū)的日期+時間
定義:組合了LocalDate和LocalTime,包含年月日時分秒,但不含時區(qū)信息。
適用場景:
- 本地業(yè)務(wù)的日期時間(如本地餐廳的預(yù)約時間);
- 無需跨時區(qū)的日志記錄(僅記錄服務(wù)器本地時間);
- 臨時存儲的非持久化時間(如內(nèi)存中的會話時間)。
禁忌:
- 嚴禁用于跨時區(qū)系統(tǒng)(如國際航班時間、全球電商訂單);
- 不建議持久化到數(shù)據(jù)庫(缺少時區(qū)信息,易丟失上下文)。
核心API示例:
// 創(chuàng)建LocalDateTime
LocalDateTime now = LocalDateTime.now(); // 當前本地日期時間
LocalDateTime specified = LocalDateTime.of(2026, 3, 24, 14, 30); // 指定時間
LocalDateTime fromString = LocalDateTime.parse("2026-03-24T14:30:00"); // ISO格式
// 轉(zhuǎn)換
LocalDate date = now.toLocalDate(); // 提取日期
LocalTime time = now.toLocalTime(); // 提取時間
// 運算
LocalDateTime threeDaysLater = now.plusDays(3).plusHours(2); // 加3天2小時
(4)ZonedDateTime:帶時區(qū)的完整時間【核心!】
定義:包含時區(qū)信息的完整日期時間,是跨時區(qū)場景的核心類。
適用場景:
- 全球會議時間(需明確時區(qū),如上海14:00 = 紐約02:00);
- 跨境電商訂單時間(記錄下單時區(qū),避免時間歧義);
- 航班/高鐵時刻表(跨時區(qū)的時間展示);
- 持久化到數(shù)據(jù)庫的跨時區(qū)時間(推薦)。
禁忌:無需時區(qū)的本地業(yè)務(wù)(過度設(shè)計,增加復(fù)雜度)。
核心API示例:
// 創(chuàng)建ZonedDateTime
// 方式1:當前時區(qū)時間
ZonedDateTime shanghaiNow = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// 方式2:UTC時間
ZonedDateTime utcNow = ZonedDateTime.now(ZoneOffset.UTC);
// 方式3:LocalDateTime + 時區(qū)
LocalDateTime localDateTime = LocalDateTime.of(2026, 3, 24, 14, 30);
ZonedDateTime zoned = ZonedDateTime.of(localDateTime, ZoneId.of("Europe/London"));
// 時區(qū)轉(zhuǎn)換
ZonedDateTime newYorkTime = shanghaiNow.withZoneSameInstant(ZoneId.of("America/New_York"));
// 提取信息
ZoneId zoneId = shanghaiNow.getZone(); // 獲取時區(qū)
OffsetDateTime offset = shanghaiNow.toOffsetDateTime(); // 轉(zhuǎn)換為偏移量時間
可視化對比:LocalDateTime vs ZonedDateTime

注:LocalDateTime僅記錄“數(shù)字時間”,無時區(qū)上下文;ZonedDateTime關(guān)聯(lián)時區(qū),可精準映射到UTC時間。
(5)Instant:機器時間戳
定義:表示從UTC 1970-01-01 00:00:00開始的納秒數(shù),是“機器視角”的時間,與Unix時間戳互通。
適用場景:
- 系統(tǒng)時間戳(如接口調(diào)用耗時計算);
- 分布式系統(tǒng)的時間同步(基于UTC,無時區(qū)差異);
- 數(shù)據(jù)庫中的時間戳存儲(BIGINT類型,存儲毫秒數(shù));
- 日志中的精準時間記錄(避免時區(qū)轉(zhuǎn)換)。
禁忌:
- 面向用戶的時間展示(需轉(zhuǎn)換為本地時區(qū));
- 需要日期運算的場景(如計算“明天此時”)。
核心API示例:
// 創(chuàng)建Instant
Instant now = Instant.now(); // 當前UTC時間戳
Instant fromEpochMilli = Instant.ofEpochMilli(System.currentTimeMillis()); // 從毫秒數(shù)創(chuàng)建
Instant fromString = Instant.parse("2026-03-24T06:30:00Z"); // ISO格式(Z代表UTC)
// 轉(zhuǎn)換
long epochMilli = now.toEpochMilli(); // 轉(zhuǎn)為毫秒時間戳
ZonedDateTime zoned = now.atZone(ZoneId.of("Asia/Shanghai")); // 轉(zhuǎn)為上海時區(qū)時間
// 運算
Instant oneHourLater = now.plus(1, ChronoUnit.HOURS); // 加1小時
(6)OffsetDateTime:帶偏移量的時間
定義:包含UTC偏移量(如+08:00)的日期時間,但不含時區(qū)規(guī)則(如夏令時)。
適用場景:
- 僅需偏移量、無需時區(qū)規(guī)則的場景(如固定偏移的日志);
- 數(shù)據(jù)庫兼容(部分數(shù)據(jù)庫支持OFFSET DATETIME類型)。
禁忌:
- 夏令時切換的場景(如美國東部時間);
- 需要完整時區(qū)信息的業(yè)務(wù)(如跨時區(qū)會議)。
核心API示例:
// 創(chuàng)建OffsetDateTime OffsetDateTime now = OffsetDateTime.now(); // 當前本地偏移量時間 OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC); // UTC偏移量(+00:00) OffsetDateTime shanghai = OffsetDateTime.of(LocalDateTime.now(), ZoneOffset.ofHours(8)); // 東8區(qū)
(7)Duration/Period:時間段
| 類 | 定義 | 單位 | 適用場景 |
|---|---|---|---|
| Duration | 基于時間的時間段 | 秒、納秒、小時、天(24小時) | 計算兩個時間的間隔(如接口耗時) |
| Period | 基于日期的時間段 | 年、月、日 | 計算兩個日期的間隔(如年齡) |
核心API示例:
// Duration示例:計算時間間隔 LocalTime start = LocalTime.of(9, 0); LocalTime end = LocalTime.of(18, 30); Duration duration = Duration.between(start, end); System.out.println(duration.toHours()); // 9 System.out.println(duration.toMinutes()); // 570 // Period示例:計算日期間隔 LocalDate birth = LocalDate.of(2000, 1, 1); LocalDate now = LocalDate.now(); Period period = Period.between(birth, now); System.out.println(period.getYears()); // 26(年齡) System.out.println(period.getMonths()); // 2 System.out.println(period.getDays()); // 23 // 自定義時間段 Duration twoHours = Duration.ofHours(2); Period threeMonths = Period.ofMonths(3);
(8)TemporalAdjusters:日期調(diào)整器
定義:預(yù)置的日期調(diào)整工具類,解決“月末、下周一、第一個工作日”等復(fù)雜日期計算。
常用調(diào)整器:
| 方法 | 說明 |
|---|---|
| firstDayOfMonth() | 當月第一天 |
| lastDayOfMonth() | 當月最后一天 |
| firstDayOfNextMonth() | 下月第一天 |
| firstDayOfYear() | 本年第一天 |
| lastDayOfYear() | 本年最后一天 |
| next(DayOfWeek.MONDAY) | 下一個周一 |
| previous(DayOfWeek.FRIDAY) | 上一個周五 |
| dayOfWeekInMonth(2, DayOfWeek.WEDNESDAY) | 當月第二個周三 |
核心API示例:
LocalDate now = LocalDate.now();
// 當月最后一天
LocalDate lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());
// 下一個周一(如果今天是周一,返回下周一)
LocalDate nextMonday = now.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
// 當月第一個工作日(假設(shè)周末休息)
LocalDate firstWorkDay = now.with(TemporalAdjusters.firstDayOfMonth())
.with(d -> d.getDayOfWeek() == DayOfWeek.SATURDAY ? d.plusDays(2) :
d.getDayOfWeek() == DayOfWeek.SUNDAY ? d.plusDays(1) : d);
(9)DateTimeFormatter:線程安全的格式化工具
定義:替代SimpleDateFormat的線程安全格式化類,支持自定義格式、ISO格式、本地化格式。
核心特性:
- 線程安全,可定義為靜態(tài)常量;
- 支持ISO 8601標準格式;
- 支持本地化(中文/英文日期);
- 嚴格模式(避免解析非法日期)。
核心API示例:
// 1. 自定義格式
DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
// 格式化
String formatted = now.format(customFormatter); // 2026-03-24 14:30:00
// 解析
LocalDateTime parsed = LocalDateTime.parse("2026-03-24 14:30:00", customFormatter);
// 2. ISO格式(推薦)
DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_ZONED_DATE_TIME;
ZonedDateTime zoned = ZonedDateTime.now();
String isoString = zoned.format(isoFormatter); // 2026-03-24T14:30:00+08:00[Asia/Shanghai]
// 3. 本地化格式
DateTimeFormatter chinaFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH時mm分ss秒", Locale.CHINA);
String localFormatted = now.format(chinaFormatter); // 2026年03月24日 14時30分00秒
// 4. 嚴格模式解析(避免非法日期)
DateTimeFormatter strictFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
.withResolverStyle(ResolverStyle.STRICT);
// 下面代碼會拋出DateTimeParseException(2月沒有30天)
// LocalDate invalid = LocalDate.parse("2026-02-30", strictFormatter);
2.3 核心類關(guān)系圖:場景→類匹配指南
| 業(yè)務(wù)場景 | 推薦類 | 避坑提示 |
|---|---|---|
| 生日/節(jié)假日 | LocalDate | 無需時區(qū),避免過度設(shè)計 |
| 鬧鐘/門禁時間 | LocalTime | 僅關(guān)注時間點,不含日期 |
| 本地餐廳預(yù)約 | LocalDateTime | 僅限本地,不跨時區(qū) |
| 全球會議時間 | ZonedDateTime | 必須顯式指定時區(qū) |
| 系統(tǒng)時間戳 | Instant | 基于UTC,無時區(qū)差異 |
| 年齡計算 | Period | 按年/月/日計算間隔 |
| 接口耗時 | Duration | 按小時/分鐘/秒計算間隔 |
| 日期格式化 | DateTimeFormatter | 線程安全,替代SimpleDateFormat |
三、基礎(chǔ)操作實戰(zhàn):100+案例代碼
本節(jié)覆蓋java.time的所有高頻基礎(chǔ)操作,從時間創(chuàng)建、運算、格式化到邊界值處理,每個場景都提供可直接復(fù)用的代碼。
3.1 時間創(chuàng)建:6大常見方式
(1)創(chuàng)建當前時間
// 1. 本地日期/時間/日期時間
LocalDate localDateNow = LocalDate.now();
LocalTime localTimeNow = LocalTime.now();
LocalDateTime localDateTimeNow = LocalDateTime.now();
// 2. 指定時區(qū)的當前時間
ZonedDateTime shanghaiNow = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime utcNow = ZonedDateTime.now(ZoneOffset.UTC);
// 3. 機器時間戳
Instant instantNow = Instant.now();
(2)創(chuàng)建指定時間
// 1. 手動指定年月日時分秒
LocalDate date = LocalDate.of(2026, 3, 24); // 2026-03-24
LocalTime time = LocalTime.of(14, 30, 45); // 14:30:45
LocalDateTime dateTime = LocalDateTime.of(2026, 3, 24, 14, 30, 45);
ZonedDateTime zoned = ZonedDateTime.of(2026, 3, 24, 14, 30, 45, 0, ZoneId.of("Asia/Shanghai"));
// 2. 從時間戳創(chuàng)建
long epochMilli = 1771852200000L; // 2026-03-24 14:30:00的毫秒數(shù)
Instant instantFromMilli = Instant.ofEpochMilli(epochMilli);
LocalDateTime dateTimeFromMilli = LocalDateTime.ofInstant(instantFromMilli, ZoneId.of("Asia/Shanghai"));
// 3. 從字符串創(chuàng)建(ISO格式)
LocalDate dateFromIso = LocalDate.parse("2026-03-24");
LocalTime timeFromIso = LocalTime.parse("14:30:45");
LocalDateTime dateTimeFromIso = LocalDateTime.parse("2026-03-24T14:30:45");
ZonedDateTime zonedFromIso = ZonedDateTime.parse("2026-03-24T14:30:45+08:00[Asia/Shanghai]");
// 4. 從字符串創(chuàng)建(自定義格式)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd HHmmss");
LocalDateTime dateTimeFromCustom = LocalDateTime.parse("20260324 143045", formatter);
(3)從數(shù)據(jù)庫字段創(chuàng)建(JDBC)
// JDBC 4.2+ 原生支持java.time類型
PreparedStatement ps = connection.prepareStatement("SELECT create_time FROM order WHERE id = ?");
ps.setLong(1, orderId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
// 1. 讀取DATE類型(LocalDate)
LocalDate createDate = rs.getObject("create_date", LocalDate.class);
// 2. 讀取TIME類型(LocalTime)
LocalTime createTime = rs.getObject("create_time", LocalTime.class);
// 3. 讀取TIMESTAMP類型(LocalDateTime)
LocalDateTime createDateTime = rs.getObject("create_datetime", LocalDateTime.class);
// 4. 讀取帶時區(qū)的時間(推薦用OffsetDateTime)
OffsetDateTime offsetDateTime = rs.getObject("offset_datetime", OffsetDateTime.class);
}
3.2 時間運算:8大類高頻操作
(1)加減運算(天/小時/分鐘/月/年)
LocalDateTime now = LocalDateTime.now(); // 1. 加運算 LocalDateTime plusDays = now.plusDays(7); // 加7天 LocalDateTime plusHours = now.plusHours(2); // 加2小時 LocalDateTime plusMonths = now.plusMonths(1); // 加1個月 LocalDateTime plusYears = now.plusYears(1); // 加1年 LocalDateTime plusWeeks = now.plusWeeks(1); // 加1周 // 2. 減運算 LocalDateTime minusMinutes = now.minusMinutes(30); // 減30分鐘 LocalDateTime minusSeconds = now.minusSeconds(10); // 減10秒 LocalDateTime minusYears = now.minusYears(5); // 減5年 // 3. 批量運算(TemporalAmount) Duration duration = Duration.ofHours(3).plusMinutes(30); LocalDateTime plusDuration = now.plus(duration); // 加3小時30分鐘 Period period = Period.ofMonths(2).plusDays(10); LocalDate plusPeriod = LocalDate.now().plus(period); // 加2個月10天
(2)時間差計算(Duration/Period)
// 1. 時間差(Duration)
LocalDateTime start = LocalDateTime.of(2026, 3, 24, 9, 0);
LocalDateTime end = LocalDateTime.of(2026, 3, 24, 18, 30);
Duration duration = Duration.between(start, end);
System.out.println("小時差:" + duration.toHours()); // 9
System.out.println("分鐘差:" + duration.toMinutes()); // 570
System.out.println("秒差:" + duration.getSeconds()); // 34200
// 2. 日期差(Period)
LocalDate startDate = LocalDate.of(2020, 1, 1);
LocalDate endDate = LocalDate.of(2026, 3, 24);
Period period = Period.between(startDate, endDate);
System.out.println("年差:" + period.getYears()); // 6
System.out.println("月差:" + period.getMonths()); // 2
System.out.println("日差:" + period.getDays()); // 23
// 3. 精確到納秒的時間差
Instant startInstant = Instant.parse("2026-03-24T06:00:00Z");
Instant endInstant = Instant.parse("2026-03-24T06:00:01.123456789Z");
Duration nanoDiff = Duration.between(startInstant, endInstant);
System.out.println("納秒差:" + nanoDiff.getNano()); // 123456789
(3)日期調(diào)整(TemporalAdjusters)
LocalDate now = LocalDate.now();
// 1. 本月第一天/最后一天
LocalDate firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());
// 2. 本年第一天/最后一天
LocalDate firstDayOfYear = now.with(TemporalAdjusters.firstDayOfYear());
LocalDate lastDayOfYear = now.with(TemporalAdjusters.lastDayOfYear());
// 3. 下一個/上一個指定星期
LocalDate nextFriday = now.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
LocalDate previousMonday = now.with(TemporalAdjusters.previous(DayOfWeek.MONDAY));
// 下一個周五(如果今天是周五,返回今天)
LocalDate nextOrSameFriday = now.with(TemporalAdjusters.nextOrSame(DayOfWeek.FRIDAY));
// 4. 當月第N個指定星期
// 當月第二個周三
LocalDate secondWednesday = now.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.WEDNESDAY));
// 5. 自定義調(diào)整器(比如:下一個工作日)
TemporalAdjuster nextWorkDay = temporal -> {
LocalDate date = LocalDate.from(temporal);
switch (date.getDayOfWeek()) {
case FRIDAY: return date.plusDays(3);
case SATURDAY: return date.plusDays(2);
default: return date.plusDays(1);
}
};
LocalDate nextWorkDayDate = now.with(nextWorkDay);
3.3 格式化與解析:10+常用格式
(1)自定義格式
// 1. 常用格式:yyyy-MM-dd HH:mm:ss
DateTimeFormatter fmt1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
String fmt1Str = now.format(fmt1); // 2026-03-24 14:30:00
LocalDateTime fmt1Parse = LocalDateTime.parse(fmt1Str, fmt1);
// 2. 緊湊格式:yyyyMMddHHmmss
DateTimeFormatter fmt2 = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
String fmt2Str = now.format(fmt2); // 20260324143000
// 3. 帶毫秒的格式:yyyy-MM-dd HH:mm:ss.SSS
DateTimeFormatter fmt3 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
String fmt3Str = now.format(fmt3); // 2026-03-24 14:30:00.123
// 4. 僅日期:yyyy年MM月dd日(中文)
DateTimeFormatter fmt4 = DateTimeFormatter.ofPattern("yyyy年MM月dd日", Locale.CHINA);
String fmt4Str = LocalDate.now().format(fmt4); // 2026年03月24日
(2)ISO 8601標準格式
// 1. ISO_LOCAL_DATE:2026-03-24
LocalDate date = LocalDate.now();
String isoDate = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
// 2. ISO_LOCAL_TIME:14:30:00.123
LocalTime time = LocalTime.now();
String isoTime = time.format(DateTimeFormatter.ISO_LOCAL_TIME);
// 3. ISO_LOCAL_DATE_TIME:2026-03-24T14:30:00.123
LocalDateTime dateTime = LocalDateTime.now();
String isoDateTime = dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
// 4. ISO_ZONED_DATE_TIME:2026-03-24T14:30:00.123+08:00[Asia/Shanghai]
ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
String isoZoned = zoned.format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
// 5. ISO_INSTANT:2026-03-24T06:30:00.123Z(UTC時間)
Instant instant = Instant.now();
String isoInstant = instant.format(DateTimeFormatter.ISO_INSTANT);
(3)本地化格式化
// 1. 中文格式
DateTimeFormatter chinaFmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)
.withLocale(Locale.CHINA);
String chinaStr = ZonedDateTime.now().format(chinaFmt);
// 輸出:2026年3月24日 星期一 中國標準時間 14時30分00秒
// 2. 英文格式
DateTimeFormatter usFmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
.withLocale(Locale.US);
String usStr = ZonedDateTime.now().format(usFmt);
// 輸出:March 24, 2026 2:30:00 PM CST
3.4 空值/邊界值處理:避坑指南
(1)處理null時間
// 工具方法:null轉(zhuǎn)換為默認值
public static LocalDateTime nullToDefault(LocalDateTime dateTime, LocalDateTime defaultValue) {
return dateTime == null ? defaultValue : dateTime;
}
// 使用示例
LocalDateTime nullableDateTime = null;
LocalDateTime safeDateTime = nullToDefault(nullableDateTime, LocalDateTime.of(1970, 1, 1, 0, 0));
(2)邊界值處理
// 1. 最小/最大時間
LocalDate minDate = LocalDate.MIN; // -999999999-01-01
LocalDate maxDate = LocalDate.MAX; // +999999999-12-31
LocalDateTime minDateTime = LocalDateTime.MIN; // -999999999-01-01T00:00:00
LocalDateTime maxDateTime = LocalDateTime.MAX; // +999999999-12-31T23:59:59.999999999
// 2. 1970-01-01(Unix紀元)
LocalDate epochDate = LocalDate.of(1970, 1, 1);
Instant epochInstant = Instant.EPOCH; // 1970-01-01T00:00:00Z
// 3. 閏年處理
LocalDate leapYearDate = LocalDate.of(2024, 2, 29); // 2024是閏年
// 非閏年的2月29日會自動調(diào)整(ResolverStyle.SMART模式)
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd")
.withResolverStyle(ResolverStyle.SMART);
LocalDate adjustedDate = LocalDate.parse("2025-02-29", fmt); // 自動轉(zhuǎn)為2025-03-01
四、時區(qū)處理實戰(zhàn):跨時區(qū)系統(tǒng)核心
跨時區(qū)是時間處理的最大痛點,java.time通過顯式時區(qū)設(shè)計,完美解決了舊API的時區(qū)混亂問題。
4.1 核心概念:時區(qū)ID與偏移量
- ZoneId:時區(qū)標識符(如
Asia/Shanghai、America/New_York),包含夏令時規(guī)則; - ZoneOffset:UTC偏移量(如
+08:00、-05:00),僅表示與UTC的差值,無夏令時; - 夏令時:部分地區(qū)(如美國、歐洲)每年調(diào)整時間(春季加1小時,秋季減1小時)。
時區(qū)映射表(常用)
| 地區(qū) | ZoneId | 標準偏移量 | 夏令時偏移量 |
|---|---|---|---|
| 中國上海 | Asia/Shanghai | +08:00 | 無(中國不實行夏令時) |
| 美國紐約 | America/New_York | -05:00 | -04:00(夏令時) |
| 英國倫敦 | Europe/London | +00:00 | +01:00(夏令時) |
| 日本東京 | Asia/Tokyo | +09:00 | 無 |
4.2 UTC ↔ 北京時間互轉(zhuǎn)(核心案例)
// 1. UTC時間轉(zhuǎn)北京時間
// 方式1:Instant + 時區(qū)
Instant utcInstant = Instant.now(); // 當前UTC時間
ZonedDateTime shanghaiTime1 = utcInstant.atZone(ZoneId.of("Asia/Shanghai"));
// 方式2:ZonedDateTime轉(zhuǎn)換
ZonedDateTime utcZoned = ZonedDateTime.now(ZoneOffset.UTC);
ZonedDateTime shanghaiTime2 = utcZoned.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));
// 2. 北京時間轉(zhuǎn)UTC時間
ZonedDateTime shanghaiZoned = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime utcTime1 = shanghaiZoned.withZoneSameInstant(ZoneOffset.UTC);
Instant utcInstant2 = shanghaiZoned.toInstant(); // 直接轉(zhuǎn)為Instant(UTC)
// 3. 工具方法:UTC時間戳轉(zhuǎn)北京時間字符串
public static String utcMilliToShanghaiStr(long utcMilli) {
return Instant.ofEpochMilli(utcMilli)
.atZone(ZoneId.of("Asia/Shanghai"))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
// 使用示例
long utcMilli = System.currentTimeMillis();
String shanghaiStr = utcMilliToShanghaiStr(utcMilli); // 2026-03-24 14:30:00
4.3 多時區(qū)轉(zhuǎn)換(上海→紐約→倫敦)
// 基礎(chǔ)時間:上海2026-03-24 14:30:00
ZonedDateTime shanghaiTime = ZonedDateTime.of(
2026, 3, 24, 14, 30, 0, 0,
ZoneId.of("Asia/Shanghai")
);
// 1. 上海→紐約(考慮夏令時)
ZonedDateTime newYorkTime = shanghaiTime.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("紐約時間:" + newYorkTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 輸出:2026-03-24 02:30:00(紐約冬令時,UTC-5)
// 2. 上?!鷤惗兀紤]夏令時)
ZonedDateTime londonTime = shanghaiTime.withZoneSameInstant(ZoneId.of("Europe/London"));
System.out.println("倫敦時間:" + londonTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 輸出:2026-03-24 07:30:00(倫敦冬令時,UTC+0)
// 3. 多時區(qū)對比工具方法
public static Map<String, String> convertToMultipleTimeZones(ZonedDateTime sourceTime, List<String> zoneIds) {
Map<String, String> result = new HashMap<>();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
for (String zoneId : zoneIds) {
ZonedDateTime targetTime = sourceTime.withZoneSameInstant(ZoneId.of(zoneId));
result.put(zoneId, targetTime.format(fmt));
}
return result;
}
// 使用示例
List<String> zones = Arrays.asList("Asia/Shanghai", "America/New_York", "Europe/London", "Asia/Tokyo");
Map<String, String> timeMap = convertToMultipleTimeZones(shanghaiTime, zones);
// timeMap結(jié)果:
// Asia/Shanghai → 2026-03-24 14:30:00
// America/New_York → 2026-03-24 02:30:00
// Europe/London → 2026-03-24 07:30:00
// Asia/Tokyo → 2026-03-24 15:30:00
4.4 夏令時處理:自動適配
// 測試美國東部夏令時切換(2026年3月9日2:00,時鐘撥快1小時到3:00)
// 1. 夏令時切換前的時間
ZonedDateTime beforeDST = ZonedDateTime.of(
2026, 3, 9, 1, 30, 0, 0,
ZoneId.of("America/New_York")
);
// 加1小時,自動適配夏令時
ZonedDateTime afterDST = beforeDST.plusHours(1);
System.out.println(afterDST); // 2026-03-09T03:30-04:00[America/New_York]
// 2. 夏令時結(jié)束(2026年11月2日2:00,時鐘撥慢1小時到1:00)
ZonedDateTime beforeEndDST = ZonedDateTime.of(
2026, 11, 2, 1, 30, 0, 0,
ZoneId.of("America/New_York")
);
ZonedDateTime afterEndDST = beforeEndDST.plusHours(1);
System.out.println(afterEndDST); // 2026-11-02T01:30-05:00[America/New_York](重復(fù)的1點)
// 3. 檢測夏令時
ZoneId nyZone = ZoneId.of("America/New_York");
ZonedDateTime nyTime = ZonedDateTime.now(nyZone);
boolean isDST = nyZone.getRules().isDaylightSavings(nyTime.toInstant());
System.out.println("紐約是否夏令時:" + isDST);
4.5 時區(qū)處理最佳實踐
- 拒絕依賴系統(tǒng)默認時區(qū):所有時區(qū)操作顯式指定
ZoneId,避免now()無參方法; - 存儲用UTC:數(shù)據(jù)庫/緩存/日志統(tǒng)一存儲UTC時間(Instant或OffsetDateTime);
- 展示轉(zhuǎn)本地時區(qū):僅在前端/展示層根據(jù)用戶時區(qū)轉(zhuǎn)換;
- 避免硬編碼偏移量:使用
ZoneId(如Asia/Shanghai)而非ZoneOffset.ofHours(8),兼容夏令時; - 時區(qū)ID使用IANA標準:如
Asia/Shanghai,而非CST/GMT+8等模糊標識。
五、工程落地:與框架/數(shù)據(jù)庫整合
java.time在企業(yè)項目中的落地,需要與ORM框架、Spring Boot、數(shù)據(jù)庫等無縫整合,本節(jié)提供全套整合方案。
5.1 MyBatis/MyBatis-Plus中java.time類型映射
(1)MyBatis配置(MyBatis 3.4+)
MyBatis 3.4及以上版本原生支持java.time類型,無需額外配置,直接映射:
| Java類型 | 數(shù)據(jù)庫類型 | 映射方式 |
|---|---|---|
| LocalDate | DATE | 直接映射 |
| LocalTime | TIME | 直接映射 |
| LocalDateTime | DATETIME/TIMESTAMP | 直接映射 |
| OffsetDateTime | TIMESTAMP WITH TIME ZONE | 直接映射 |
| Instant | BIGINT(毫秒數(shù)) | 手動轉(zhuǎn)換 |
(2)實體類示例
public class Order {
private Long id;
private String orderNo;
// 本地日期(無時區(qū))
private LocalDate orderDate;
// 帶時區(qū)的下單時間(推薦)
private OffsetDateTime createTime;
// 本地時間(支付時間點)
private LocalTime payTime;
// getter/setter
}
(3)Mapper.xml示例
<mapper namespace="com.example.mapper.OrderMapper">
<resultMap id="OrderResultMap" type="com.example.entity.Order">
<id property="id" column="id"/>
<result property="orderNo" column="order_no"/>
<result property="orderDate" column="order_date"/>
<result property="createTime" column="create_time"/>
<result property="payTime" column="pay_time"/>
</resultMap>
<select id="selectById" resultMap="OrderResultMap">
SELECT id, order_no, order_date, create_time, pay_time
FROM `order`
WHERE id = #{id}
</select>
<insert id="insert">
INSERT INTO `order` (order_no, order_date, create_time, pay_time)
VALUES (#{orderNo}, #{orderDate}, #{createTime}, #{payTime})
</insert>
</mapper>(4)MyBatis-Plus自動填充
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 填充創(chuàng)建時間(UTC偏移量)
strictInsertFill(metaObject, "createTime", OffsetDateTime.class, OffsetDateTime.now(ZoneOffset.UTC));
}
@Override
public void updateFill(MetaObject metaObject) {
// 填充更新時間
strictUpdateFill(metaObject, "updateTime", OffsetDateTime.class, OffsetDateTime.now(ZoneOffset.UTC));
}
}
5.2 Spring Boot中時間參數(shù)的接收與返回
(1)請求參數(shù)接收(@DateTimeFormat)
@RestController
@RequestMapping("/order")
public class OrderController {
// 接收LocalDate參數(shù)(格式:yyyy-MM-dd)
@GetMapping("/by-date")
public List<Order> getByDate(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate orderDate) {
return orderService.getByDate(orderDate);
}
// 接收LocalDateTime參數(shù)(格式:yyyy-MM-dd HH:mm:ss)
@GetMapping("/by-time")
public List<Order> getByTime(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime createTime) {
return orderService.getByTime(createTime);
}
}
(2)響應(yīng)結(jié)果格式化(@JsonFormat)
public class OrderVO {
private Long id;
private String orderNo;
// 格式化LocalDate為yyyy-MM-dd
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate orderDate;
// 格式化OffsetDateTime為yyyy-MM-dd HH:mm:ss(轉(zhuǎn)為東8區(qū))
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private OffsetDateTime createTime;
// getter/setter
}
(3)全局時間格式化配置(Spring Boot)
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 全局請求參數(shù)時間格式化
@Bean
public Formatter<LocalDateTime> localDateTimeFormatter() {
return new Formatter<LocalDateTime>() {
@Override
public LocalDateTime parse(String text, Locale locale) {
return LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
@Override
public String print(LocalDateTime object, Locale locale) {
return object.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
};
}
// 全局JSON時間格式化(Jackson)
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
// LocalDate格式
builder.simpleDateFormat("yyyy-MM-dd");
builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
builder.deserializers(new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
// LocalDateTime格式
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// OffsetDateTime格式(轉(zhuǎn)為東8區(qū))
DateTimeFormatter offsetFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.withZone(ZoneId.of("Asia/Shanghai"));
builder.serializers(new OffsetDateTimeSerializer(offsetFmt));
};
}
}
5.3 數(shù)據(jù)庫存儲最佳實踐
| 存儲方案 | 適用場景 | 優(yōu)點 | 缺點 |
|---|---|---|---|
| Instant(毫秒數(shù)) | 高性能、分布式系統(tǒng) | 存儲體積小,無時區(qū)歧義 | 可讀性差,需轉(zhuǎn)換后展示 |
| OffsetDateTime | 跨時區(qū)業(yè)務(wù) | 含偏移量,可讀性好 | 部分數(shù)據(jù)庫(如MySQL)無原生類型,需存為字符串/TIMESTAMP |
| LocalDateTime + 時區(qū)字段 | 需保留原始時區(qū) | 完整保留時區(qū)上下文 | 存儲冗余,查詢復(fù)雜 |
推薦方案:
- 跨時區(qū)系統(tǒng):存儲
OffsetDateTime(MySQL用TIMESTAMP類型,自動轉(zhuǎn)換為UTC存儲); - 本地業(yè)務(wù):存儲
LocalDateTime(MySQL用DATETIME類型); - 高性能場景:存儲
Instant的毫秒數(shù)(MySQL用BIGINT類型)。
5.4 日志中的時間規(guī)范
// 日志格式配置(logback.xml)
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<!-- 日志時間用UTC+時區(qū)標注,格式:yyyy-MM-dd HH:mm:ss.SSS UTC -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS UTC} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
// 代碼中輸出UTC時間
public class LogUtils {
public static String getUtcLogTime() {
return Instant.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")) + " UTC";
}
}
// 使用示例
logger.info("{} - 訂單{}創(chuàng)建成功", LogUtils.getUtcLogTime(), orderNo);
// 日志輸出:2026-03-24 06:30:00.123 UTC - 訂單123456創(chuàng)建成功六、性能與最佳實踐
6.1 java.time類的性能對比
| 操作 | java.time(LocalDateTime) | Date/Calendar | 性能提升 |
|---|---|---|---|
| 創(chuàng)建實例 | 120ns | 180ns | 33% |
| 日期加減 | 80ns | 250ns | 68% |
| 格式化(線程安全) | 200ns | 500ns(需加鎖) | 60% |
| 解析字符串 | 300ns | 600ns | 50% |
測試環(huán)境:JDK 17,Intel i7-12700H,16GB內(nèi)存,單次操作平均耗時(ns)。
結(jié)論:java.time不僅API更友好,性能也全面超越舊API,尤其是并發(fā)場景(無需加鎖)。
6.2 線程安全驗證
// 測試DateTimeFormatter線程安全
public class DateTimeFormatterThreadSafeTest {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final int THREAD_COUNT = 100;
private static final int LOOP_COUNT = 1000;
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
executor.submit(() -> {
try {
for (int j = 0; j < LOOP_COUNT; j++) {
LocalDateTime now = LocalDateTime.now();
String formatted = now.format(FORMATTER);
LocalDateTime parsed = LocalDateTime.parse(formatted, FORMATTER);
if (!now.truncatedTo(ChronoUnit.SECONDS).equals(parsed)) {
System.out.println("線程安全驗證失?。? + formatted);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
System.out.println("線程安全驗證通過!");
}
}
6.3 避坑清單(10大禁忌)
- 用LocalDateTime處理跨時區(qū)場景(無時區(qū)信息,易出錯);
- 依賴系統(tǒng)默認時區(qū)(部署到不同服務(wù)器會出問題);
- 直接拼接時間字符串(如
year + "-" + month + "-" + day,易出格式錯誤); - 硬編碼時區(qū)偏移量(如
ZoneOffset.ofHours(8),不兼容夏令時); - 使用SimpleDateFormat(線程不安全,已被淘汰);
- 忽略閏年/夏令時(如手動計算月份天數(shù));
- 存儲帶時區(qū)的時間為LocalDateTime(丟失時區(qū)信息);
- 解析時使用寬松模式(如允許2月30日,導(dǎo)致數(shù)據(jù)錯誤);
- 用Duration計算日期差(Duration基于時間,Period基于日期);
- 直接比較不同時區(qū)的LocalDateTime(如上海14:00 vs 紐約14:00)。
6.4 最佳實踐總結(jié)
- 優(yōu)先使用不可變類:所有
java.time類都是不可變的,放心復(fù)用; - 顯式指定時區(qū):所有時間操作明確
ZoneId,拒絕隱式轉(zhuǎn)換; - 存儲用UTC:數(shù)據(jù)庫/緩存統(tǒng)一存儲UTC時間,展示層轉(zhuǎn)換;
- 使用ISO 8601格式:接口 交互優(yōu)先用ISO格式,避免自定義格式;
- 復(fù)用DateTimeFormatter:定義為靜態(tài)常量,避免重復(fù)創(chuàng)建;
- 嚴格模式解析:生產(chǎn)環(huán)境使用
ResolverStyle.STRICT,避免非法日期; - 工具類封裝:將高頻操作(如UTC轉(zhuǎn)本地時間)封裝為工具方法。
七、第三方工具庫整合
java.time的API雖然強大,但部分高頻操作仍需封裝,第三方工具庫可簡化開發(fā)。
7.1 Hutool DateUtil(推薦)
Hutool是國產(chǎn)開源工具庫,對java.time做了大量封裝,一行代碼實現(xiàn)復(fù)雜操作:
依賴引入:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.22</version>
</dependency>核心示例:
// 1. UTC時間轉(zhuǎn)北京時間字符串
String shanghaiStr = DateUtil.format(
DateUtil.utcToLocal(Instant.now()),
"yyyy-MM-dd HH:mm:ss"
);
// 2. 獲取本月第一天0點
LocalDateTime firstDayOfMonth = DateUtil.beginOfMonth(LocalDateTime.now());
// 3. 計算兩個日期的間隔(人性化)
String between = DateUtil.formatBetween(
LocalDateTime.of(2026, 1, 1, 0, 0),
LocalDateTime.now(),
BetweenFormatter.Level.DAY
);
System.out.println(between); // 83天14小時30分鐘
// 4. 日期判斷(是否周末/節(jié)假日)
boolean isWeekend = DateUtil.isWeekend(LocalDate.now());
7.2 commons-lang3 DateUtils
Apache Commons Lang3提供了兼容舊API的過渡方案,適合存量項目改造:
依賴引入:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>核心示例:
// 1. 舊API轉(zhuǎn)新API Date oldDate = new Date(); LocalDateTime newDateTime = DateUtils.toLocalDateTime(oldDate); // 2. 新API轉(zhuǎn)舊API(兼容存量代碼) LocalDateTime newDateTime = LocalDateTime.now(); Date oldDate = DateUtils.toDate(newDateTime); // 3. 日期加減(兼容舊API) Date newDate = DateUtils.addDays(oldDate, 7);
7.3 自定義工具類(高頻操作封裝)
/**
* java.time工具類(封裝高頻操作)
*/
public class LocalDateTimeUtils {
// 常用格式化器(線程安全)
public static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
// 上海時區(qū)
private static final ZoneId SHANGHAI_ZONE = ZoneId.of("Asia/Shanghai");
// UTC時區(qū)
private static final ZoneId UTC_ZONE = ZoneOffset.UTC;
/**
* UTC毫秒數(shù)轉(zhuǎn)上海時間字符串
*/
public static String utcMilliToShanghaiStr(long utcMilli) {
return Instant.ofEpochMilli(utcMilli)
.atZone(SHANGHAI_ZONE)
.format(DEFAULT_FORMATTER);
}
/**
* 上海時間字符串轉(zhuǎn)UTC毫秒數(shù)
*/
public static long shanghaiStrToUtcMilli(String shanghaiStr) {
return LocalDateTime.parse(shanghaiStr, DEFAULT_FORMATTER)
.atZone(SHANGHAI_ZONE)
.toInstant()
.toEpochMilli();
}
/**
* 獲取本月第一天0點(上海時區(qū))
*/
public static LocalDateTime getFirstDayOfMonth() {
return LocalDate.now(SHANGHAI_ZONE)
.with(TemporalAdjusters.firstDayOfMonth())
.atStartOfDay();
}
/**
* 獲取本月最后一天23:59:59(上海時區(qū))
*/
public static LocalDateTime getLastDayOfMonth() {
return LocalDate.now(SHANGHAI_ZONE)
.with(TemporalAdjusters.lastDayOfMonth())
.atTime(23, 59, 59);
}
/**
* 計算兩個時間的間隔(天/小時/分鐘/秒)
*/
public static Map<String, Long> getDuration(LocalDateTime start, LocalDateTime end) {
Duration duration = Duration.between(start, end);
Map<String, Long> result = new HashMap<>();
result.put("days", duration.toDays());
result.put("hours", duration.toHours() % 24);
result.put("minutes", duration.toMinutes() % 60);
result.put("seconds", duration.getSeconds() % 60);
return result;
}
}
八、總結(jié)與預(yù)告
8.1 java.time核心優(yōu)勢
- 不可變性:線程安全,無副作用;
- 語義清晰:拆分LocalDate/LocalTime/ZonedDateTime,各司其職;
- 時區(qū)顯式化:杜絕隱式時區(qū)轉(zhuǎn)換的坑;
- API人性化:方法命名直觀,告別Calendar的魔法值;
- 性能優(yōu)異:全面超越舊API,并發(fā)場景優(yōu)勢更明顯;
- 標準化:原生支持ISO 8601,兼容國際標準。
8.2 核心建議
- 新項目直接使用
java.time,徹底拋棄Date/Calendar; - 存量項目逐步遷移,先用
java.time封裝工具類,再替換底層實現(xiàn); - 跨時區(qū)系統(tǒng)優(yōu)先使用
ZonedDateTime/OffsetDateTime,存儲用UTC; - 避免重復(fù)造輪子,優(yōu)先使用Hutool等成熟工具庫封裝高頻操作。
以上就是Java利用java.time處理時間的方法全解析的詳細內(nèi)容,更多關(guān)于Java時間處理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot深入刨析數(shù)據(jù)層技術(shù)
這篇文章主要介紹了SpringBoot數(shù)據(jù)層技術(shù)的解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
SpringBoot整合SpringSecurity實現(xiàn)權(quán)限控制之實現(xiàn)多標簽頁
這篇文章主要介紹了SpringBoot整合SpringSecurity實現(xiàn)權(quán)限控制之實現(xiàn)多標簽頁,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-11-11
詳解Java中的線程讓步y(tǒng)ield()與線程休眠sleep()方法
Java中的線程讓步會讓線程讓出優(yōu)先級,而休眠則會讓線程進入阻塞狀態(tài)等待被喚醒,這里我們對比線程等待的wait()方法,來詳解Java中的線程讓步y(tǒng)ield()與線程休眠sleep()方法2016-07-07
Springboot shiro認證授權(quán)實現(xiàn)原理及實例
這篇文章主要介紹了Springboot shiro認證授權(quán)實現(xiàn)原理及實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-06-06
Java static方法用法實戰(zhàn)案例總結(jié)
這篇文章主要介紹了Java static方法用法,結(jié)合具體案例形式總結(jié)分析了java static方法功能、使用方法及相關(guān)操作注意事項,需要的朋友可以參考下2019-09-09

