在Java开发中,处理日期和时间是常见需求。Java的时间API经历了多次演进,从早期的Date
和Calendar
,到Java 8引入的全新时间API(JSR 310),逐步解决了旧API设计上的缺陷。本文将全面解析Java时间类的发展历程、核心API及最佳实践。
第一代:java.util.Date
(JDK 1.0)
@Deprecated
。第二代:java.util.Calendar
(JDK 1.1)
第三代:Java 8新API(JSR 310)
java.time
(如LocalDateTime
、Instant
、DateTimeFormatter
)。java.util.Date
import java.util.Date;
// 创建当前时间
Date now = new Date();
System.out.println(now); // 输出:Mon May 26 10:30:00 CST 2025
// 时间比较
Date past = new Date(125, 4, 26); // 注意:年份从1900开始,月份从0开始
boolean isAfter = now.after(past); // true
java.util.Calendar
import java.util.Calendar;
// 创建Calendar实例
Calendar calendar = Calendar.getInstance(); // 默认时区和语言环境
// 设置时间
calendar.set(2025, Calendar.MAY, 26, 10, 30, 0); // 月份从0开始
// 获取时间字段
int year = calendar.get(Calendar.YEAR); // 2025
int month = calendar.get(Calendar.MONTH) + 1; // 5(注意:需+1)
int day = calendar.get(Calendar.DAY_OF_MONTH); // 26
// 时间计算
calendar.add(Calendar.DAY_OF_MONTH, 7); // 加7天
java.text.SimpleDateFormat
import java.text.SimpleDateFormat;
import java.util.Date;
// 日期格式化
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formattedDate = sdf.format(now); // 2025-05-26 10:30:00
// 字符串解析为日期
Date parsedDate = sdf.parse("2025-05-26 10:30:00");
SimpleDateFormat
非线程安全,多线程环境需额外同步。Date
同时包含日期和时间,Calendar
月份从0开始。类名 | 描述 |
---|---|
LocalDate |
不可变的日期(年、月、日),如2025-05-26 。 |
LocalTime |
不可变的时间(时、分、秒、纳秒),如10:30:00.123 。 |
LocalDateTime |
不可变的日期和时间,如2025-05-26T10:30:00 。 |
ZonedDateTime |
带时区的日期时间,如2025-05-26T10:30:00+08:00[Asia/Shanghai] 。 |
Instant |
时间线上的一个点(UTC时间),用于系统时间戳。 |
Duration |
表示两个时间点之间的间隔(如2小时30分 )。 |
Period |
表示两个日期之间的间隔(如3年2个月1天 )。 |
DateTimeFormatter |
线程安全的日期时间格式化与解析工具。 |
LocalDate
、LocalTime
与LocalDateTime
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
// 创建当前日期/时间
LocalDate date = LocalDate.now(); // 2025-05-26
LocalTime time = LocalTime.now(); // 10:30:00.123
LocalDateTime dateTime = LocalDateTime.now(); // 2025-05-26T10:30:00.123
// 指定日期/时间
LocalDate specificDate = LocalDate.of(2025, 5, 26); // 2025-05-26
LocalTime specificTime = LocalTime.of(10, 30, 0); // 10:30:00
// 日期计算
LocalDate tomorrow = date.plusDays(1); // 2025-05-27
LocalDate lastMonth = date.minusMonths(1); // 2025-04-26
// 获取日期字段
int year = date.getYear(); // 2025
int dayOfWeek = date.getDayOfWeek().getValue(); // 2(Monday=1)
ZonedDateTime
与时区处理import java.time.ZoneId;
import java.time.ZonedDateTime;
// 当前时区的日期时间
ZonedDateTime zonedDateTime = ZonedDateTime.now(); // 2025-05-26T10:30:00+08:00[Asia/Shanghai]
// 指定时区的日期时间
ZoneId newYorkZone = ZoneId.of("America/New_York");
ZonedDateTime newYorkTime = zonedDateTime.withZoneSameInstant(newYorkZone); // 2025-05-25T21:30:00-04:00[America/New_York]
// 可用时区列表
Set<String> allZones = ZoneId.getAvailableZoneIds();
Instant
与时间戳import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
// 当前时间戳(UTC)
Instant instant = Instant.now(); // 2025-05-26T02:30:00Z
// 转换为本地时间
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
// 格式化时间戳
DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT;
String formattedInstant = formatter.format(instant); // 2025-05-26T02:30:00Z
Duration
与Period
import java.time.Duration;
import java.time.LocalTime;
import java.time.Period;
import java.time.LocalDate;
// Duration示例(时间间隔)
LocalTime start = LocalTime.of(10, 0);
LocalTime end = LocalTime.of(12, 30);
Duration duration = Duration.between(start, end);
System.out.println(duration.toHours()); // 2小时
System.out.println(duration.toMinutes()); // 150分钟
// Period示例(日期间隔)
LocalDate startDate = LocalDate.of(2025, 1, 1);
LocalDate endDate = LocalDate.of(2025, 5, 26);
Period period = Period.between(startDate, endDate);
System.out.println(period.getMonths() + "个月" + period.getDays() + "天"); // 4个月25天
DateTimeFormatter
格式化与解析import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// 格式化
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(formatter); // 2025-05-26 10:30:00
// 解析
String dateStr = "2025-05-26 10:30:00";
LocalDateTime parsedDateTime = LocalDateTime.parse(dateStr, formatter);
// 预定义格式
String isoFormatted = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); // 2025-05-26T10:30:00
import java.util.Date;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
// Date转Instant
Date oldDate = new Date();
Instant instant = oldDate.toInstant();
// Date转LocalDateTime
LocalDateTime localDateTime = oldDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
import java.util.Date;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
// Instant转Date
Instant instant = Instant.now();
Date oldDate = Date.from(instant);
// LocalDateTime转Date
LocalDateTime localDateTime = LocalDateTime.now();
Instant instantFromLocal = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Date oldDateFromLocal = Date.from(instantFromLocal);
plusDays()
、minusMonths()
)。Instant
(UTC时间戳),展示时转换为用户时区。plusXxx()
和minusXxx()
方法,避免月份从0开始的陷阱。DateTimeFormatter
:预定义格式化器,避免重复创建。Java 8为什么引入新的时间API?
Date
同时包含日期和时间、Calendar
月份从0开始)。SimpleDateFormat
非线程安全)。LocalDateTime
和ZonedDateTime
的区别是什么?
LocalDateTime
:不带时区的日期时间(如2025-05-26T10:30:00
)。ZonedDateTime
:带时区的日期时间(如2025-05-26T10:30:00+08:00[Asia/Shanghai]
)。如何将字符串解析为日期?
DateTimeFormatter
:DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate date = LocalDate.parse("2025-05-26", formatter);
如何处理跨时区的日期时间?
ZonedDateTime
或Instant
,明确指定时区(如ZoneId.of("America/New_York")
)。Java时间API从早期的Date
和Calendar
发展到Java 8的新API,逐步解决了设计缺陷和线程安全问题。新API(java.time
包)提供了更清晰、易用的日期时间处理方式,推荐在新项目中优先使用。在与旧系统交互时,可通过Instant
作为桥梁实现新旧API的转换。掌握Java时间API的演进和最佳实践,能有效避免日期时间处理中的常见陷阱,提升代码质量和开发效率。