前言
在 Java 开发中,日期和时间处理一直是令人头疼的问题。传统的 Date
和 Calendar
类不仅复杂,还充满了线程安全和时区处理的坑。Java 8 引入的 java.time
包彻底改变了这一局面,带来了现代化、直观且功能强大的日期时间 API。
本文将带你深入了解 java.time
包的核心功能,从基础的 LocalDate
和 LocalTime
到强大的 Duration
和 Period
,再到灵活的 DateTimeFormatter
,让你轻松掌握日期时间处理的最佳实践。
一、关于java.time包
1.1 简介
java.time
包是 Java 8 中引入的,用于处理日期和时间的新 API。它旨在克服旧版 java.util.Date
和 java.util.Calendar
类中的一些缺点,提供更现代、更简洁、线程安全和不可变的日期和时间处理方式。该 API 基于 ISO-8601 标准,并支持更多的时区、日期/时间格式以及各种常见的时间计算需求。
1.2 核心类和接口
- LocalDate:表示没有时区的日期(年、月、日)。例如,2025年3月28日。
- LocalTime:表示没有时区的时间(时、分、秒)。例如,14:30:00。
- LocalDateTime:表示没有时区的日期和时间(年、月、日、时、分、秒)。例如,2025年3月28日 14:30:00。
- ZonedDateTime:表示带时区的日期和时间。它不仅包含日期和时间信息,还包含时区信息(例如 UTC+08:00)。
- Instant:表示时间戳(从1970年1月1日0时0分0秒到某一时刻的秒数或毫秒数),通常用于与 Unix 时间戳和其他基于秒的时间戳的交互。
- Duration:表示时间段,用于表示两个时间点之间的差异,精度为秒和纳秒。
- Period:表示日期段,用于表示两个日期之间的差异,精度为年、月和日。
- Month:表示一个月(如 JANUARY、FEBRUARY 等),是枚举类型。
- DayOfWeek:表示一周中的某一天(如 MONDAY、TUESDAY 等),也是枚举类型。
java.time包目录结构
二、核心类
2.1 LocalDate类
简介
LocalDate
类是 Java 8 引入的日期和时间 API(java.time 包)的一部分。它表示一个没有时间部分的日期(例如:2025-03-31),适用于处理年、月、日等信息,而不涉及时间、时区等。
LocalDate
代表不带时区的日期。
- 表示不带时区的日期(年-月-日)。
- 示例:
2023-10-01
。
LocalDate
使用 ISO-8601 标准来处理日期,并将日期表示为 YYYY-MM-DD
的格式。ISO-8601日历系统是当今世界大部分地区使用的现代民用日历系统。相当于现在的公历日历体系,其中表示闰年规则永远适用。
方法
常用方法
now()
:获取当前日期。of(int year, int month, int dayOfMonth)
:创建指定日期。plusDays(long days)
:增加天数。minusMonths(long months)
:减少月数。getYear()
,getMonth()
,getDayOfMonth()
:获取日期的各个部分。isLeapYear()
:判断是否为闰年。isBefore(LocalDate other)
:判断当前日期是否在指定日期之前。isAfter(LocalDate other)
:判断当前日期是否在指定日期之后。
示例
ini
import java.time.LocalDate;
public class LocalDateExample {
public static void main(String[] args) {
// 获取当前日期
LocalDate today = LocalDate.now();
System.out.println("今天: " + today);
// 创建指定日期
LocalDate specificDate = LocalDate.of(2025, 3, 31);
System.out.println("指定日期: " + specificDate);
// 日期操作
LocalDate tomorrow = today.plusDays(1);
System.out.println("明天: " + tomorrow);
LocalDate yesterday = today.minusDays(1);
System.out.println("昨天: " + yesterday);
// 判断是否为闰年
boolean isLeapYear = today.isLeapYear();
System.out.println("是否为闰年: " + isLeapYear);
// 日期比较
boolean isBefore = today.isBefore(specificDate);
System.out.println("今天是否在指定日期之前? " + isBefore);
}
}
2.2 LocalTime类
简介
- 表示不带时区的时间(小时:分钟:秒.纳秒)。
- 示例:
15:30:45.123
。
方法
-
常用方法:
now()
:获取当前时间。of(int hour, int minute, int second)
:创建指定时间。plusHours(long hours)
:增加小时数。minusMinutes(long minutes)
:减少分钟数。getHour()
,getMinute()
,getSecond()
:获取时间的各个部分。
示例
java
import java.time.LocalTime;
public class LocalTimeExample {
public static void main(String[] args) {
LocalTime time = LocalTime.now(); // 获取当前时间
System.out.println(time); // 输出当前时间,格式如 14:30:00
LocalTime specificTime = LocalTime.of(14, 30); // 创建特定时间
System.out.println(specificTime); // 输出:14:30
}
}
2.3 LocalDateTime类
关于
简介
LocalDateTime
是 java.time
包中的一个类,用于表示没有时区信息的日期和时间。它结合了 LocalDate
和 LocalTime
,可以表示年份、月份、日期、小时、分钟、秒以及纳秒的信息,但不包含时区。LocalDateTime
是不可变的,意味着它的实例一旦创建后就不能被修改,因此它是线程安全的。
- 表示不带时区的日期和时间(年-月-日T小时:分钟:秒.纳秒)。
- 示例:
2023-10-01T15:30:45.123
。
主要特点
- 无时区 :
LocalDateTime
不包含时区信息,仅表示本地日期和时间。 - 精确到纳秒:提供纳秒级别的精度。
- 不可变和线程安全 :一旦创建,
LocalDateTime
的值不能改变,因此是线程安全的。
关联类对比
类名 | 描述 | 示例值 |
---|---|---|
LocalDate |
仅日期(无时间) | 2023-10-01 |
LocalTime |
仅时间(无日期) | 15:30:45 |
LocalDateTime |
日期 + 时间(无时区) | 2023-10-01T15:30:45 |
ZonedDateTime |
日期时间 + 时区 | 2023-10-01T15:30:45+08:00[Asia/Shanghai] |
Instant |
时间戳(基于 UTC) | 2023-10-01T07:30:45Z |
时间带T
在 LocalDateTime
类中,时间表示格式中的 T 是 ISO 8601 标准规定的日期和时间之间的分隔符。
解释:
在 ISO 8601 格式中,日期和时间通常由字母 "T" 分隔。例如:
makefile
2025-03-28T14:30:00
在这个示例中:
2025-03-28
是日期部分(年-月-日)14:30:00
是时间部分(小时:分钟:秒)
字母 T 用来清楚地标识日期部分和时间部分的分隔,避免歧义。因此,它并不代表任何特殊的含义,而仅仅是作为分隔符来表示日期和时间的结合。
构造方法
LocalDateTime
类提供了多种构造方法来创建日期时间实例:
LocalDateTime.now()
:获取当前系统的日期和时间(根据默认时区)。LocalDateTime.of(int year, int month, int dayOfMonth, int hour, int minute)
:创建指定日期和时间的LocalDateTime
。LocalDateTime.of(int year, int month, int dayOfMonth, int hour, int minute, int second)
:创建指定日期、时间和秒的LocalDateTime
。LocalDateTime.of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond)
:创建指定日期、时间、秒和纳秒的LocalDateTime
。LocalDateTime.parse(CharSequence text)
:通过字符串解析创建LocalDateTime
对象。
解析字符串(ISO 格式)
ini
LocalDateTime dt = LocalDateTime.parse("2023-10-01T15:30:45");
组合 LocalDate + LocalTime
ini
LocalDateTime dt = LocalDate.now().atTime(LocalTime.of(12, 0));
ini
LocalDate date1 = LocalDate.of(2023, 6, 7);
LocalTime time1 = LocalTime.of(11, 30, 0);
LocalDateTime dateTime1 = LocalDateTime.of(date1, time1);
System.out.println(dateTime1);
//2023-06-07T01:00
old
ini
LocalDateTime startTime, endTime;
在您的代码片段中,声明了两个 LocalDateTime 类型的变量 startTime 和 endTime,但是并没有进行初始化赋值操作,因此它们的值默认为 null。
方法
以下是 LocalDateTime
类的常用方法概览:
一、获取当前日期时间
方法名 | 描述 |
---|---|
now() |
获取当前的日期和时间。 |
now(ZoneId zone) |
根据指定的时区获取当前日期和时间。 |
now(Clock clock) |
根据指定的时钟获取当前日期和时间。 |
二、创建特定日期时间
方法名 | 描述 |
---|---|
of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nano) |
创建指定日期和时间的 LocalDateTime 对象。 |
of(int year, int month, int dayOfMonth, int hour, int minute, int second) |
创建指定日期和时间的 LocalDateTime 对象(毫秒和纳秒为 0)。 |
of(int year, int month, int dayOfMonth, int hour, int minute) |
创建指定日期和时间的 LocalDateTime 对象(秒、毫秒和纳秒为 0)。 |
of(int year, Month month, int dayOfMonth, int hour, int minute, int second, int nano) |
使用 Month 枚举创建指定日期和时间的 LocalDateTime 对象。 |
三、解析和格式化
方法名 | 描述 |
---|---|
parse(CharSequence text, DateTimeFormatter formatter) |
使用指定的格式化程序解析字符串,创建 LocalDateTime 对象。 |
format(DateTimeFormatter formatter) |
使用指定的格式化程序将 LocalDateTime 格式化为字符串。 |
四、获取日期和时间部分
方法名 | 描述 |
---|---|
toLocalDate() |
获取 LocalDateTime 对象的日期部分,返回一个 LocalDate 对象。 |
toLocalTime() |
获取 LocalDateTime 对象的时间部分,返回一个 LocalTime 对象。 |
getYear() |
获取年份。 |
getMonthValue() |
获取月份(1-12)。 |
getDayOfMonth() |
获取当月的日期(1-31)。 |
getHour() |
获取小时(0-23)。 |
getMinute() |
获取分钟(0-59)。 |
getSecond() |
获取秒(0-59)。 |
getNano() |
获取纳秒(0-999,999,999)。 |
五、日期时间的加减
方法名 | 描述 |
---|---|
plusYears(long years) |
增加指定的年数。 |
plusMonths(long months) |
增加指定的月数。 |
plusWeeks(long weeks) |
增加指定的周数。 |
plusDays(long days) |
增加指定的天数。 |
plusHours(long hours) |
增加指定的小时数。 |
plusMinutes(long minutes) |
增加指定的分钟数。 |
plusSeconds(long seconds) |
增加指定的秒数。 |
plusNanos(long nanos) |
增加指定的纳秒数。 |
minusYears(long years) |
减少指定的年数。 |
minusMonths(long months) |
减少指定的月数。 |
minusWeeks(long weeks) |
减少指定的周数。 |
minusDays(long days) |
减少指定的天数。 |
minusHours(long hours) |
减少指定的小时数。 |
minusMinutes(long minutes) |
减少指定的分钟数。 |
minusSeconds(long seconds) |
减少指定的秒数。 |
minusNanos(long nanos) |
减少指定的纳秒数。 |
六、比较日期时间
方法名 | 描述 |
---|---|
isBefore(LocalDateTime other) |
判断此日期时间是否在指定的日期时间之前。 |
isAfter(LocalDateTime other) |
判断此日期时间是否在指定的日期时间之后。 |
isEqual(LocalDateTime other) |
判断此日期时间是否等于指定的日期时间。 |
compareTo(LocalDateTime other) |
比较两个日期时间的大小,返回负数、零或正数。 |
七、时间戳转换
方法名 | 描述 |
---|---|
toEpochSecond(ZoneOffset offset) |
将此日期时间转换为从 1970-01-01T00:00:00Z 开始的秒数。 |
ofEpochSecond(long epochSecond, int nano, ZoneOffset offset) |
从秒数和纳秒数创建 LocalDateTime 对象。 |
八、其他方法
方法名 | 描述 |
---|---|
with(TemporalAdjuster adjuster) |
使用指定的调整器调整日期时间。 |
withYear(int year) |
设置年份。 |
withMonth(int month) |
设置月份。 |
withDayOfMonth(int dayOfMonth) |
设置当月的日期。 |
withHour(int hour) |
设置小时。 |
withMinute(int minute) |
设置分钟。 |
withSecond(int second) |
设置秒。 |
withNano(int nano) |
设置纳秒。 |
atZone(ZoneId zone) |
将此日期时间转换为指定时区的 ZonedDateTime 对象。 |
toLocalDate() |
获取日期部分。 |
toLocalTime() |
获取时间部分。 |
这些方法提供了丰富的功能来处理日期和时间,包括获取当前日期时间、创建特定日期时间、解析和格式化、日期时间的加减、比较日期时间以及与时间戳的转换等。通过使用这些方法,可以更方便、更准确地处理日期时间相关的操作。
示例
java
import java.time.LocalDateTime;
public class LocalDateTimeExample {
public static void main(String[] args) {
LocalDateTime dateTime = LocalDateTime.now(); // 获取当前日期和时间
System.out.println(dateTime); // 输出:2025-03-28T14:30:00.123
LocalDateTime specificDateTime = LocalDateTime.of(2025, 3, 28, 14, 30); // 创建特定日期和时间
System.out.println(specificDateTime); // 输出:2025-03-28T14:30
}
}
2.4 DateTimeFormatter
关于
简介
Java 的 DateTimeFormatter
类是 java.time.format
包中用于格式化和解析日期时间对象的核心工具,专为 java.time
(Java 8+ 引入的日期时间 API)设计。它替代了旧的 SimpleDateFormat
,具备线程安全 和更清晰的API设计,支持 ISO-8601 标准及自定义模式。
功能
- 格式化(Formatting) 将
LocalDate
、LocalDateTime
、ZonedDateTime
等对象转换为字符串。 - 解析(Parsing) 将字符串解析为日期时间对象。
- ISO-8601 兼容 直接支持 ISO 格式的日期时间表示。
- 自定义模式 通过模式字符串定义灵活的日期时间格式。
方法
方法 | 说明 | 示例 |
---|---|---|
ofPattern(String pattern) |
创建自定义格式 | DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") |
format(TemporalAccessor temporal) |
格式化日期时间对象 | localDateTime.format(formatter) |
parse(CharSequence text) |
解析字符串为日期时间对象 | LocalDate.parse("2023-10-05", formatter) |
withZone(ZoneId zone) |
指定时区 | formatter.withZone(ZoneId.of("Asia/Shanghai")) |
DateTimeFormatter
预定义了 ISO 标准的格式化常量,例如:
ISO_LOCAL_DATE
:yyyy-MM-dd
(如2023-10-05
)ISO_LOCAL_TIME
:HH:mm:ss.SSS
(如15:30:45.123
)ISO_DATE_TIME
:yyyy-MM-dd'T'HH:mm:ss.SSSZ
(如2023-10-05T15:30:45.123+08:00
)
常见模式符号:
通过模式字符串定义格式:
符号 | 含义 | 示例 |
---|---|---|
yyyy |
4位年份 | 2023 |
MM |
2位月份 | 10 |
dd |
2位日期 | 05 |
HH |
24小时制小时 | 15 |
mm |
分钟 | 30 |
ss |
秒 | 45 |
SSS |
毫秒 | 123 |
Z |
时区偏移 | +0800 |
示例
ini
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
// 定义格式模板
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 格式化 LocalDateTime → String
String formattedDate = now.format(formatter);
System.out.println(formattedDate);
// 解析 String → LocalDateTime
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime parsedDate = LocalDateTime.parse("2024-10-01 15:30", formatter1);
System.out.println(parsedDate);
}
}
2.5 Duration类(时间间隔)
关于
简介
Java 的 Duration
类是 java.time
包中用于表示基于时间的量 (如小时、分钟、秒、纳秒)的不可变类,专为处理时间间隔设计。它与 Period
类(处理基于日期的量,如年、月、日)互补,适用于需要精确时间计算的场景。
- 表示两个时间点之间的时间间隔,单位为秒和纳秒。
- 示例:计算两个
LocalTime
或LocalDateTime
之间的时间差。
核心特性
- 时间单位 :支持秒、纳秒,自动转换为标准单位(如
1 小时 = 3600 秒
)。 - 不可变与线程安全:所有操作返回新对象,适合多线程环境。
- ISO-8601 兼容 :默认遵循
PnYnMnDTnHnMnS
格式(如PT8H30M
表示 8 小时 30 分钟)。 - 精确计算:处理纳秒级精度的时间间隔。
方法
方法 | 说明 | 示例 |
---|---|---|
ofDays(long) / ofHours(long) |
创建指定天、小时的 Duration | Duration.ofHours(2) → PT2H |
ofMinutes(long) / ofSeconds(long) |
创建分钟、秒的 Duration | Duration.ofSeconds(90) → PT1M30S |
between(Temporal start, Temporal end) |
计算两个时间点之间的间隔 | Duration.between(startTime, endTime) |
plus(Duration) / minus(Duration) |
时间间隔的加减 | duration.plus(Duration.ofMinutes(10)) |
toHours() / toMinutes() / toMillis() |
转换为其他时间单位 | duration.toMinutes() → 总分钟数 |
parse(CharSequence) |
解析 ISO-8601 格式字符串 | Duration.parse("PT2H30M") → 2小时30分钟 |
isNegative() |
判断是否为负时间间隔 | duration.isNegative() |
示例
java
import java.time.Duration;
import java.time.LocalDateTime;
public class DurationExample {
public static void main(String[] args) {
// 创建两个时间点
LocalDateTime start = LocalDateTime.of(2025, 3, 31, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 3, 31, 12, 30, 0);
// 计算两个时间点之间的持续时间
Duration duration = Duration.between(start, end);
// 输出持续时间
System.out.println("Duration (in hours): " + duration.toHours()); // 2小时
System.out.println("Duration (in minutes): " + duration.toMinutes()); // 150分钟
System.out.println("Duration (in seconds): " + duration.getSeconds()); // 5400秒
}
}
2.6 Period类(日期间隔)
关于
简介
Java 的 Period
类是 java.time
包中用于表示基于日历的日期间隔(如年、月、日)的不可变类,专为处理与日历相关的周期设计(如"2年3个月10天")。
- 表示两个日期之间的间隔,单位为年、月和日。
- 示例:计算两个
LocalDate
之间的日期差。
核心特性
- 日历单位:支持年、月、日,自动处理不同月份和闰年的差异。
- 不可变与线程安全:所有操作返回新对象,适合多线程环境。
- ISO-8601 兼容 :默认遵循
PnYnMnD
格式(如P1Y2M3D
表示1年2个月3天)。 - 自然日期计算 :加减周期时考虑月份和年的实际天数(如
2024-02-28 + P1M = 2024-03-28
)。
方法
常用方法
方法 | 说明 | 示例 |
---|---|---|
of(int years, int months, int days) |
创建指定年、月、日的 Period | Period.of(1, 2, 3) → P1Y2M3D |
between(LocalDate start, LocalDate end) |
计算两个日期之间的间隔 | Period.between(startDate, endDate) |
plus(Period) / minus(Period) |
周期的加减 | period.plus(Period.ofMonths(1)) |
getYears() / getMonths() / getDays() |
获取年、月、日的数值 | period.getMonths() → 2 |
parse(CharSequence) |
解析 ISO-8601 格式字符串 | Period.parse("P3Y6M") → 3年6个月 |
isNegative() |
判断是否为负周期 | period.isNegative() |
示例
示例1---创建 Period
对象:
java
import java.time.Period;
public class PeriodExample {
public static void main(String[] args) {
// 使用静态方法创建 Period 对象
Period period1 = Period.of(1, 2, 10); // 1年2个月10天
Period period2 = Period.ofYears(3); // 3年
Period period3 = Period.ofMonths(5); // 5个月
Period period4 = Period.ofDays(20); // 20天
// 输出 Period 对象
System.out.println("Period 1: " + period1); // P1Y2M10D
System.out.println("Period 2: " + period2); // P3Y
System.out.println("Period 3: " + period3); // P5M
System.out.println("Period 4: " + period4); // P20D
}
}
示例2---计算两个日期之间的差异
java
import java.time.LocalDate;
import java.time.Period;
public class PeriodExample {
public static void main(String[] args) {
// 创建两个日期
LocalDate startDate = LocalDate.of(2020, 1, 1);
LocalDate endDate = LocalDate.of(2023, 3, 15);
// 计算两个日期之间的差异
Period period = Period.between(startDate, endDate);
// 输出差异
System.out.println("Years: " + period.getYears()); // 3
System.out.println("Months: " + period.getMonths()); // 2
System.out.println("Days: " + period.getDays()); // 14
}
}
三、总结
java.time
的诞生不仅是Java对开发者痛点的回应,更是面向未来时间处理的标杆。其不可变性、线程安全与ISO标准兼容性,使其成为高可靠系统的基石。无论是替换陈旧的Date
,还是应对全球化业务的时区挑战,java.time
都以优雅的设计降低认知成本。作为开发者,掌握这一工具不仅提升代码质量,更能在分布式系统、微服务架构中规避潜在风险。拥抱java.time
,让时间处理从"痛点"变为"亮点",在复杂业务中真正实现时间自由。