Java 时间操作全解析:从传统类到 Java 8 新 API
在 Java 中,时间操作是日常开发的常见需求,包括获取当前时间、格式化时间、计算时间差等。随着 Java 版本的演进,时间 API 也从早期的 Date、Calendar 发展到 Java 8 引入的 java.time 系列(如 LocalDateTime、ZonedDateTime),后者解决了传统 API 的线程不安全、设计混乱等问题。本文将全面介绍 Java 中操作时间的主要方式及最佳实践。
传统时间类(Java 8 之前)
1. java.util.Date
Date 是最早期的时间类,存储自 1970 年 1 月 1 日 00:00:00(UTC)以来的毫秒数,但大部分方法已被废弃(如 getYear()、getMonth()),仅保留少数核心方法(如 getTime())。
示例:创建 Date 对象
1 2 3 4 5 6 7 8 9
| import java.util.Date;
public class DateDemo { public static void main(String[] args) { Date now = new Date(); System.out.println(now); System.out.println(now.getTime()); } }
|
- 缺点:
- 线程不安全(无同步机制)。
- 设计混乱(年从 1900 开始,月从 0 开始)。
- 不支持时区和历法扩展。
2. java.util.Calendar
Calendar 是为了弥补 Date 的缺陷而设计的抽象类,提供了更丰富的时间操作方法(如获取年月日、加减时间),但仍存在线程安全问题。
示例:使用 Calendar 获取年月日时分秒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import java.util.Calendar;
public class CalendarDemo { public static void main(String[] args) { Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR); int month = cal.get(Calendar.MONTH) + 1; int day = cal.get(Calendar.DATE);
int hour = cal.get(Calendar.HOUR_OF_DAY); int minute = cal.get(Calendar.MINUTE); int second = cal.get(Calendar.SECOND);
System.out.printf("%d-%02d-%02d %02d:%02d:%02d%n", year, month, day, hour, minute, second); } }
|
- 常用字段:
Calendar.YEAR:年
Calendar.MONTH:月(0~11)
Calendar.DATE/DAY_OF_MONTH:日
Calendar.HOUR_OF_DAY:24 小时制小时
Calendar.HOUR:12 小时制小时
Calendar.MINUTE:分钟
Calendar.SECOND:秒
Calendar.MILLISECOND:毫秒
3. java.text.SimpleDateFormat
SimpleDateFormat 用于时间的格式化(Date → 字符串)和解析(字符串 → Date),但线程不安全(多线程并发使用可能导致异常)。
示例:时间格式化与解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date;
public class SimpleDateFormatDemo { public static void main(String[] args) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date now = new Date(); String formatted = sdf.format(now); System.out.println("格式化后:" + formatted);
String timeStr = "2025-12-31 23:59:59"; Date parsedDate = sdf.parse(timeStr); System.out.println("解析后:" + parsedDate); } }
|
格式化字符含义
| 字符 |
描述 |
示例 |
y |
年(4 位) |
yyyy → 2025 |
M |
月(数字) |
MM → 08 |
d |
日 |
dd → 14 |
H |
24 小时制小时 |
HH → 15 |
h |
12 小时制小时 |
hh → 03 |
m |
分钟 |
mm → 45 |
s |
秒 |
ss → 30 |
S |
毫秒 |
SSS → 123 |
E |
星期(中文) |
E → 星期四 |
a |
上 / 下午(A.M./P.M.) |
a → 下午 |
z |
时区 |
z → CST |
D |
一年中的第几天 |
D → 226 |
w |
一年中的第几周 |
w → 33 |
Java 8 新时间 API(java.time)
Java 8 引入的 java.time 包(基于 JSR-310)是时间操作的推荐方案,解决了传统类的线程不安全、设计混乱等问题,核心类包括:
LocalDate:仅含日期(年、月、日)。
LocalTime:仅含时间(时、分、秒、毫秒)。
LocalDateTime:含日期和时间(无时区)。
ZonedDateTime:含日期、时间和时区。
DateTimeFormatter:线程安全的时间格式化器。
1. LocalDateTime:处理本地日期时间(无时区)
示例:获取年月日时分秒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.time.LocalDateTime;
public class LocalDateTimeDemo { public static void main(String[] args) { LocalDateTime now = LocalDateTime.now();
int year = now.getYear(); int month = now.getMonthValue(); int day = now.getDayOfMonth(); int hour = now.getHour(); int minute = now.getMinute(); int second = now.getSecond(); int nano = now.getNano();
System.out.printf("%d-%02d-%02d %02d:%02d:%02d.%03d%n", year, month, day, hour, minute, second, nano / 1_000_000); } }
|
时间增减与修改
1 2 3 4 5 6 7 8 9 10 11
| LocalDateTime now = LocalDateTime.now();
LocalDateTime modified = now.plusDays(1).minusHours(2);
LocalDateTime updated = now.withYear(2026).withMonth(10);
System.out.println("原时间:" + now); System.out.println("增减后:" + modified); System.out.println("修改后:" + updated);
|
DateTimeFormatter 替代了 SimpleDateFormat,支持格式化和解析,且线程安全,可直接定义为静态变量复用。
示例:格式化与解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import java.time.LocalDateTime; import java.time.format.DateTimeFormatter;
public class DateTimeFormatterDemo { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) { LocalDateTime now = LocalDateTime.now();
String formatted = FORMATTER.format(now); System.out.println("格式化后:" + formatted);
String timeStr = "2025-01-01 00:00:00"; LocalDateTime parsed = LocalDateTime.parse(timeStr, FORMATTER); System.out.println("解析后:" + parsed); } }
|
3. ZonedDateTime:处理时区
ZonedDateTime 包含时区信息,可用于跨时区时间转换(如 UTC 与北京时间的转换)。
示例:时区转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import java.time.ZonedDateTime; import java.time.ZoneId;
public class ZonedDateTimeDemo { public static void main(String[] args) { ZonedDateTime utcTime = ZonedDateTime.now(ZoneId.of("UTC")); ZonedDateTime beijingTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("UTC 时间:" + utcTime); System.out.println("北京时区:" + beijingTime);
ZonedDateTime newYorkTime = beijingTime.withZoneSameInstant(ZoneId.of("America/New_York")); System.out.println("纽约时区:" + newYorkTime); } }
|
4. 时间间隔计算(Duration 与 Period)
Duration:计算两个时间(LocalTime/LocalDateTime)之间的间隔(秒、纳秒)。
Period:计算两个日期(LocalDate)之间的间隔(年、月、日)。
示例:计算时间差
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.time.Duration; import java.time.LocalDateTime; import java.time.Period; import java.time.LocalDate;
public class DurationPeriodDemo { public static void main(String[] args) { LocalDateTime start = LocalDateTime.of(2025, 8, 1, 10, 0, 0); LocalDateTime end = LocalDateTime.of(2025, 8, 1, 12, 30, 0); Duration duration = Duration.between(start, end); System.out.println("相差小时:" + duration.toHours()); System.out.println("相差分钟:" + duration.toMinutes());
LocalDate date1 = LocalDate.of(2025, 1, 1); LocalDate date2 = LocalDate.of(2025, 12, 31); Period period = Period.between(date1, date2); System.out.println("相差月数:" + period.getMonths()); System.out.println("相差天数:" + period.getDays()); } }
|
传统类与 Java 8 新 API 的对比
| 特性 |
传统类(Date/Calendar) |
Java 8 新 API(java.time) |
| 线程安全 |
不安全(SimpleDateFormat 等) |
安全(所有类均为不可变对象) |
| 设计合理性 |
混乱(月从 0 开始,年偏移 1900) |
直观(月从 1 开始,无偏移) |
| 时区支持 |
弱(需手动处理) |
强(ZonedDateTime 原生支持) |
| 格式化 |
SimpleDateFormat(线程不安全) |
DateTimeFormatter(线程安全) |
| 时间计算 |
繁琐(add() 方法) |
简洁(plusXxx()/minusXxx()) |
| 推荐使用 |
不推荐(仅兼容旧代码) |
推荐(新代码首选) |
最佳实践
- 优先使用 Java 8 新 API:
LocalDateTime、ZonedDateTime、DateTimeFormatter 等,避免传统类的线程安全和设计问题。
- 线程安全:
SimpleDateFormat 线程不安全,多线程环境下应使用 DateTimeFormatter 或 ThreadLocal<SimpleDateFormat>(不推荐)。
- 时区处理:涉及跨时区时间时,使用
ZonedDateTime 而非 LocalDateTime,避免时区混淆。
- 格式化模式:日期用
yyyy(4 位年)而非 yy(2 位年,易歧义);24 小时制用 HH,12 小时制用 hh 并配合 a(上 / 下午)。