Java Date全解析:从 JDK7 到 JDK8 的演进与实践

在 Java 开发中,日期时间处理是一个高频且重要的场景,无论是日志记录、数据统计还是业务逻辑判断,都离不开对时间的操作。本文将从 JDK7 及以前的传统日期时间类讲起,再到 JDK8 新增的时间 API,通过实例代码详细解析各类的使用方法与最佳实践。

一、JDK7 及以前的日期时间类

JDK7 及更早版本中,主要通过DateSimpleDateFormatCalendar三个类处理日期时间,虽然功能基础,但在实际开发中仍需掌握其核心用法。

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 日期时间类按功能可分为以下几类:

类别核心类功能描述
时区相关ZoneIdZonedDateTime处理时区及带时区的日期时间
时间戳Instant表示 UTC 时间戳(类似Date,但更精确)
本地日期时间LocalDateLocalTimeLocalDateTime不带时区的日期、时间、日期时间
格式化工具DateTimeFormatter替代SimpleDateFormat,线程安全
时间间隔DurationPeriodChronoUnit计算时间差(时分秒 / 年月日 / 全单位)

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 的DateSimpleDateFormatCalendar虽然老旧,但在维护旧系统时仍需掌握;而 JDK8 的java.time包提供了更优雅、安全、功能完善的 API,推荐在新项目中优先使用。

掌握本文介绍的核心类与方法后,无论是简单的时间格式化、复杂的时区转换,还是时间间隔计算,都能游刃有余。建议在实际开发中根据场景选择合适的 API,并多动手实践以加深理解。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值