在 Java 开发中,日期时间处理是一个高频且重要的场景,无论是日志记录、数据统计还是业务逻辑判断,都离不开对时间的操作。本文将从 JDK7 及以前的传统日期时间类讲起,再到 JDK8 新增的时间 API,通过实例代码详细解析各类的使用方法与最佳实践。
一、JDK7 及以前的日期时间类
JDK7 及更早版本中,主要通过Date、SimpleDateFormat和Calendar三个类处理日期时间,虽然功能基础,但在实际开发中仍需掌握其核心用法。
1. Date 类:时间的基础表示
Date类是 Java 中最基础的时间表示类,精确到毫秒级,主要用于存储和表示一个具体的时间点。
核心用法:
- 创建时间对象:
- 空参构造
new Date():表示系统当前时间 - 有参构造
new Date(long millis):表示从 "时间原点"(1970 年 1 月 1 日 00:00:00 UTC)开始计算的指定毫秒数对应的时间
- 空参构造
- 修改与获取时间:
setTime(long millis):修改时间为指定毫秒数getTime():获取时间对应的毫秒数(从时间原点开始计算)
代码示例:
// 表示当前时间
Date now = new Date();
System.out.println(now);
// 表示时间原点(1970-01-01 00:00:00 UTC)
Date epoch = new Date(0L);
System.out.println(epoch);
// 修改时间为原点后1秒
epoch.setTime(1000L);
System.out.println(epoch); // 输出:Thu Jan 01 08:00:01 CST 1970(时区影响)
2. SimpleDateFormat:时间的格式化与解析
SimpleDateFormat是处理时间格式化(Date→String)和解析(String→Date)的核心类,支持自定义时间格式。
核心知识点:
- 格式模式符:
y:年(如yyyy表示 4 位年)M:月(如MM表示 2 位月)d:日(如dd表示 2 位日)H:24 小时制小时(hh表示 12 小时制)m:分;s:秒;E:星期
- 常用方法:
format(Date date):将日期对象格式化为字符串parse(String source):将字符串解析为日期对象(需处理ParseException)
代码示例:
// 格式化:Date→String
Date date = new Date(0L);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss EE");
String formatted = sdf.format(date);
System.out.println(formatted); // 输出:1970年01月01日 08:00:00 星期四
// 解析:String→Date
String dateStr = "2020-07-13 20:20:20";
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date parsedDate = parser.parse(dateStr);
System.out.println(parsedDate);
实战案例:日期格式转换
// 将"2002-11-10"转换为"2002年11月10日"
String birthday = "2002-11-10";
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf1.parse(birthday);
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日");
System.out.println(sdf2.format(date)); // 输出:2002年11月10日
3. Calendar:日历字段的精细化操作
Calendar是一个抽象类,提供了对年、月、日、时、分、秒等日历字段的精细化操作,弥补了Date类无法直接操作单个字段的不足。
核心特性:
- 获取实例:通过
Calendar.getInstance()获取(因是抽象类,不能直接new) - 字段操作:
get(int field):获取指定字段值(如Calendar.YEAR获取年份)set(int field, int value):修改指定字段值add(int field, int amount):为指定字段增加 / 减少值(正数增加,负数减少)
- 注意事项:
- 月份范围是 0-11(1 月为 0,12 月为 11)
- 星期范围是 1-7(1 表示星期日,7 表示星期六)
代码示例:
// 获取当前日历对象
Calendar cal = Calendar.getInstance();
// 设置时间为1970年1月1日
cal.setTime(new Date(0L));
// 获取字段值
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 月份+1以符合日常习惯
int day = cal.get(Calendar.DAY_OF_MONTH);
int week = cal.get(Calendar.DAY_OF_WEEK);
System.out.println(year + "年" + month + "月" + day + "日 " + getWeek(week));
// 输出:1970年1月1日 星期四
// 修改字段:设置为2020年7月13日
cal.set(Calendar.YEAR, 2020);
cal.set(Calendar.MONTH, 6); // 7月对应6
cal.set(Calendar.DAY_OF_MONTH, 13);
// 增加1个月
cal.add(Calendar.MONTH, 1); // 变为2020年8月13日
实战案例:秒杀活动时间判断
// 秒杀时间:2020-07-13 00:00:00 至 00:10:00
// 判断用户下单时间是否在秒杀范围内
public static void main(String[] args) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date wangBuy = sdf.parse("2020年7月13日 00:01:00"); // 小王下单时间
Date hanBuy = sdf.parse("2020年7月13日 00:11:00"); // 小韩下单时间
judgeSeckill("小王", wangBuy.getTime()); // 输出:小王参加成功
judgeSeckill("小韩", hanBuy.getTime()); // 输出:小韩参加失败
}
public static void judgeSeckill(String name, long buyTime) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
long startTime = sdf.parse("2020年07月13日 00:00:00").getTime();
long endTime = sdf.parse("2020年07月13日 00:10:00").getTime();
if (buyTime > startTime && buyTime < endTime) {
System.out.println(name + "参加成功");
} else {
System.out.println(name + "参加失败");
}
}
二、JDK8 新增的日期时间类
JDK8 引入了全新的日期时间 API(位于java.time包下),解决了传统 API 的线程不安全、设计混乱等问题,其核心类均为不可变对象,线程安全且易用。
1. 核心类概览
JDK8 日期时间类按功能可分为以下几类:
| 类别 | 核心类 | 功能描述 |
|---|---|---|
| 时区相关 | ZoneId、ZonedDateTime | 处理时区及带时区的日期时间 |
| 时间戳 | Instant | 表示 UTC 时间戳(类似Date,但更精确) |
| 本地日期时间 | LocalDate、LocalTime、LocalDateTime | 不带时区的日期、时间、日期时间 |
| 格式化工具 | DateTimeFormatter | 替代SimpleDateFormat,线程安全 |
| 时间间隔 | Duration、Period、ChronoUnit | 计算时间差(时分秒 / 年月日 / 全单位) |
2. 常用类详解
(1)ZoneId 与 ZonedDateTime:时区处理
ZoneId表示时区,ZonedDateTime表示带时区的完整日期时间,解决了传统Date类时区处理繁琐的问题。
// 获取所有支持的时区
Set<String> allZones = ZoneId.getAvailableZoneIds();
// 系统默认时区
ZoneId defaultZone = ZoneId.systemDefault(); // 如Asia/Shanghai
// 带时区的时间操作
ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime newYorkTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
// 修改时间(不可变对象,返回新实例)
ZonedDateTime modified = shanghaiTime.withYear(2025).plusDays(10);
(2)Instant:高精度时间戳
Instant表示 UTC 时间线上的一个点,精度可达纳秒,类似Date但更强大,常用来记录事件发生的精确时间。
// 当前UTC时间戳
Instant now = Instant.now();
// 从时间原点开始的1秒
Instant oneSecondLater = Instant.ofEpochMilli(1000L);
// 转换为带时区的时间
ZonedDateTime chinaTime = now.atZone(ZoneId.of("Asia/Shanghai"));
// 时间比较
boolean isBefore = Instant.ofEpochMilli(0L).isBefore(Instant.ofEpochMilli(1000L)); // true
(3)LocalDate/LocalTime/LocalDateTime:本地日期时间
这三个类用于表示不带时区的日期时间,方法直观,适合处理本地场景的时间(如 "今天下午 3 点开会")。
// 本地日期(年-月-日)
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(2002, 9, 3); // 2002-09-03
// 本地时间(时:分:秒)
LocalTime nowTime = LocalTime.now();
LocalTime meetingTime = LocalTime.of(15, 30); // 15:30
// 本地日期时间(年-月-日 时:分:秒)
LocalDateTime dt = LocalDateTime.of(2023, 10, 1, 8, 0);
// 常用操作
LocalDateTime tomorrow = dt.plusDays(1); // 加1天
LocalDateTime lastMonth = dt.minusMonths(1); // 减1个月
boolean isAfter = dt.isAfter(LocalDateTime.of(2023, 9, 1, 0, 0)); // true
(4)DateTimeFormatter:线程安全的格式化工具
DateTimeFormatter替代了SimpleDateFormat,支持格式化和解析所有 JDK8 日期时间类,且线程安全。
(5)时间间隔计算:Duration、Period、ChronoUnit
Duration:计算两个时间(LocalTime/Instant等)的间隔(秒、纳秒)Period:计算两个日期(LocalDate)的间隔(年、月、日)ChronoUnit:支持所有时间单位的间隔计算(最灵活)// Period:计算日期差 LocalDate today = LocalDate.now(); LocalDate birthday = LocalDate.of(2002, 9, 3); Period period = Period.between(birthday, today); System.out.println("相差" + period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天"); // ChronoUnit:计算全单位间隔 long days = ChronoUnit.DAYS.between(birthday, today); // 总天数 long hours = ChronoUnit.HOURS.between(LocalTime.of(9, 0), LocalTime.of(18, 0)); // 9小时
三、JDK7 与 JDK8 日期时间 API 对比
| 特性 | JDK7 及以前 | JDK8 新增 API |
|---|---|---|
| 线程安全 | 非线程安全(如SimpleDateFormat) | 线程安全(所有类均为不可变对象) |
| 易用性 | 方法设计混乱(如Calendar月份 0 开始) | 方法直观(如plusDays()、getYear()) |
| 功能完整性 | 基础功能,时区处理繁琐 | 完善的时区、间隔计算等功能 |
| 精度 | 毫秒级(Date) | 纳秒级(Instant) |
| 推荐使用场景 | 维护旧项目 | 新项目开发、需要线程安全的场景 |
四、总结
日期时间处理是 Java 开发的基础技能,JDK7 的Date、SimpleDateFormat、Calendar虽然老旧,但在维护旧系统时仍需掌握;而 JDK8 的java.time包提供了更优雅、安全、功能完善的 API,推荐在新项目中优先使用。
掌握本文介绍的核心类与方法后,无论是简单的时间格式化、复杂的时区转换,还是时间间隔计算,都能游刃有余。建议在实际开发中根据场景选择合适的 API,并多动手实践以加深理解。
84

被折叠的 条评论
为什么被折叠?



