
1. Java时间类概述在Java开发中处理日期和时间是常见需求。Java提供了多套时间API从早期的Date、Calendar到Java 8引入的java.time包每套API都有其特点和适用场景。作为开发者我们需要根据项目需求和Java版本来选择合适的时间处理方式。重要提示在新项目中强烈建议使用Java 8引入的java.time API它解决了旧API的诸多设计缺陷提供了更清晰、更安全的日期时间操作方式。2. 传统日期时间API2.1 Date类java.util.Date是Java最早的日期时间类用于表示特定的瞬间精确到毫秒。虽然现在不推荐在新代码中使用但了解它仍有必要因为很多遗留系统还在使用。// 创建当前时间的Date对象 Date now new Date(); System.out.println(now); // 输出类似Mon May 01 14:30:45 CST 2023 // 创建指定时间的Date对象1970年1月1日以来的毫秒数 Date specificDate new Date(1682929845000L);Date类的主要问题包括月份从0开始0表示一月不符合直觉年份计算从1900年开始线程不安全日期计算和格式化功能有限2.2 Calendar类Calendar类是对Date类的改进提供了更丰富的日期操作功能但仍然存在一些问题。// 获取Calendar实例 Calendar calendar Calendar.getInstance(); // 设置日期 calendar.set(2023, Calendar.MAY, 1); // 注意月份从0开始 // 获取日期各部分 int year calendar.get(Calendar.YEAR); int month calendar.get(Calendar.MONTH) 1; // 需要1 int day calendar.get(Calendar.DAY_OF_MONTH); // 日期计算 calendar.add(Calendar.DAY_OF_MONTH, 7); // 加7天Calendar的常见问题月份仍然从0开始可变对象线程不安全API设计不够直观如字段常量2.3 SimpleDateFormatSimpleDateFormat用于日期格式化和解析但它是线程不安全的。// 创建格式化对象 SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); // 格式化日期 String formattedDate sdf.format(new Date()); // 解析字符串为日期 Date parsedDate sdf.parse(2023-05-01 14:30:00);警告在多线程环境中共享SimpleDateFormat实例会导致问题要么每次创建新实例要么使用ThreadLocal。3. Java 8时间APIJava 8引入了全新的java.time包解决了旧API的诸多问题提供了更清晰、更安全的日期时间操作方式。3.1 核心类介绍java.time包主要包含以下类类名描述LocalDate只包含日期年-月-日LocalTime只包含时间时-分-秒-纳秒LocalDateTime包含日期和时间ZonedDateTime包含日期、时间和时区Instant时间戳从1970-01-01T00:00:00Z开始的秒和纳秒Duration时间间隔秒和纳秒Period日期间隔年、月、日DateTimeFormatter线程安全的日期时间格式化类3.2 LocalDate/LocalTime/LocalDateTime// 获取当前日期 LocalDate today LocalDate.now(); System.out.println(当前日期: today); // 2023-05-01 // 创建特定日期 LocalDate nationalDay LocalDate.of(2023, Month.OCTOBER, 1); // 日期计算 LocalDate tomorrow today.plusDays(1); LocalDate nextMonth today.plusMonths(1); LocalDate lastYear today.minusYears(1); // 获取日期各部分 int year today.getYear(); Month month today.getMonth(); int day today.getDayOfMonth(); DayOfWeek dayOfWeek today.getDayOfWeek(); // LocalTime使用类似 LocalTime now LocalTime.now(); LocalTime specificTime LocalTime.of(14, 30, 45); // LocalDateTime组合日期和时间 LocalDateTime currentDateTime LocalDateTime.now();3.3 ZonedDateTime处理带时区的日期时间// 获取系统默认时区的当前时间 ZonedDateTime zonedNow ZonedDateTime.now(); // 获取特定时区的时间 ZonedDateTime tokyoTime ZonedDateTime.now(ZoneId.of(Asia/Tokyo)); // 时区转换 ZonedDateTime newYorkTime zonedNow.withZoneSameInstant(ZoneId.of(America/New_York));3.4 InstantInstant表示时间线上的一个瞬时点通常用于记录事件时间戳Instant now Instant.now(); // 获取当前时间戳 Instant later now.plusSeconds(60); // 60秒后 long epochMilli now.toEpochMilli(); // 转换为毫秒时间戳 // 与Date互转 Date date Date.from(now); Instant instant date.toInstant();3.5 DateTimeFormatter线程安全的日期时间格式化// 预定义格式 DateTimeFormatter isoDateTime DateTimeFormatter.ISO_DATE_TIME; String formatted isoDateTime.format(LocalDateTime.now()); // 自定义格式 DateTimeFormatter formatter DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss); String formattedDate formatter.format(LocalDateTime.now()); // 解析 LocalDateTime parsedDateTime LocalDateTime.parse(2023-05-01 14:30:00, formatter);4. 日期时间操作与计算4.1 日期比较LocalDate date1 LocalDate.of(2023, 5, 1); LocalDate date2 LocalDate.of(2023, 5, 15); // 比较方法 boolean isBefore date1.isBefore(date2); boolean isAfter date1.isAfter(date2); boolean isEqual date1.isEqual(date2); // 比较结果 int compareResult date1.compareTo(date2); // 负、零、正4.2 日期差计算LocalDate startDate LocalDate.of(2023, 1, 1); LocalDate endDate LocalDate.of(2023, 5, 1); // 计算日期间隔 Period period Period.between(startDate, endDate); System.out.println(period.getMonths() 个月 period.getDays() 天); // 计算时间间隔 LocalTime startTime LocalTime.of(9, 0); LocalTime endTime LocalTime.of(17, 30); Duration duration Duration.between(startTime, endTime); System.out.println(duration.toHours() 小时);4.3 时间调整器TemporalAdjuster提供了更复杂的日期调整LocalDate date LocalDate.now(); // 下个周一 LocalDate nextMonday date.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); // 本月最后一天 LocalDate lastDayOfMonth date.with(TemporalAdjusters.lastDayOfMonth()); // 自定义调整器下一个工作日 TemporalAdjuster nextWorkday TemporalAdjusters.ofDateAdjuster(d - { DayOfWeek day d.getDayOfWeek(); int daysToAdd 1; if (day DayOfWeek.FRIDAY) daysToAdd 3; else if (day DayOfWeek.SATURDAY) daysToAdd 2; return d.plusDays(daysToAdd); }); LocalDate nextWorkdayDate date.with(nextWorkday);5. 数据库交互5.1 JDBC 4.2支持JDBC 4.2及以上版本直接支持java.time类型// 写入数据库 PreparedStatement ps connection.prepareStatement( INSERT INTO events (name, event_date, event_time) VALUES (?, ?, ?)); ps.setString(1, Meeting); ps.setObject(2, LocalDate.now()); ps.setObject(3, LocalTime.now()); ps.executeUpdate(); // 从数据库读取 ResultSet rs statement.executeQuery(SELECT event_date FROM events); while (rs.next()) { LocalDate eventDate rs.getObject(event_date, LocalDate.class); }5.2 旧版本JDBC对于不支持java.time的旧JDBC驱动可以转换为java.sql类型// 转换为java.sql.Date java.sql.Date sqlDate java.sql.Date.valueOf(LocalDate.now()); // 转换为java.sql.Timestamp java.sql.Timestamp sqlTimestamp java.sql.Timestamp.valueOf(LocalDateTime.now()); // 反向转换 LocalDate localDate sqlDate.toLocalDate(); LocalDateTime localDateTime sqlTimestamp.toLocalDateTime();6. 常见问题与最佳实践6.1 时区处理// 明确指定时区 ZoneId zone ZoneId.of(Asia/Shanghai); ZonedDateTime zonedDateTime ZonedDateTime.now(zone); // 时区转换 ZonedDateTime newYorkTime zonedDateTime.withZoneSameInstant(ZoneId.of(America/New_York)); // 最佳实践在系统边界处处理时区如数据库交互、API响应6.2 性能优化重用DateTimeFormatter实例它是线程安全的对于频繁使用的时区缓存ZoneId实例考虑使用Instant作为内部时间表示特别是在高性能场景6.3 常见陷阱月份编号旧API中月份从0开始java.time中从1开始线程安全SimpleDateFormat不是线程安全的而DateTimeFormatter是默认时区隐式使用系统默认时区可能导致问题建议显式指定日期初始化LocalDate.of()会验证日期有效性避免非法日期6.4 迁移建议从旧API迁移到java.time时逐步替换先在新代码中使用java.time使用兼容方法进行类型转换特别注意时区处理更新数据库交互代码以利用新API7. 实际应用示例7.1 工作日计算public static long calculateWorkDays(LocalDate start, LocalDate end) { return start.datesUntil(end) .filter(date - date.getDayOfWeek() ! DayOfWeek.SATURDAY date.getDayOfWeek() ! DayOfWeek.SUNDAY) .count(); }7.2 年龄计算public static int calculateAge(LocalDate birthDate) { return Period.between(birthDate, LocalDate.now()).getYears(); }7.3 定时任务时间计算// 计算下一个整点时间 LocalDateTime now LocalDateTime.now(); LocalDateTime nextHour now.withMinute(0).withSecond(0).withNano(0).plusHours(1); // 计算执行延迟 Duration delay Duration.between(now, nextHour); long delayMillis delay.toMillis();8. 总结与个人经验在实际项目中处理日期时间时我总结了以下几点经验统一标准在整个项目中统一使用java.time API避免混用新旧API明确时区尽早确定时区策略特别是在分布式系统中验证输入严格验证用户输入的日期时间特别是边界情况日志记录记录时间时包含时区信息便于排查问题测试覆盖编写全面的测试用例覆盖闰年、闰秒、夏令时等特殊情况对于新项目我强烈建议使用Java 8或更高版本完全采用java.time API在系统设计阶段就考虑时区问题为日期时间操作编写工具类保持一致性最后记住日期时间处理看似简单实则复杂。花时间理解这些API的设计哲学和使用场景将帮助你在实际开发中避免许多常见问题。