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(上 / 下午)。
v1.3.10