新旧日期API全解析!不用头疼 Java 处理?

52 阅读6分钟

点击上方“程序员蜗牛g”,选择“设为星标”

跟蜗牛哥一起,每天进步一点点

程序员蜗牛g

大厂程序员一枚 跟蜗牛一起 每天进步一点点

33篇原创内容

**

公众号

Java新日期/时间API于Java 8中引入,并在Java 21中得到了增强,为现代应用程序中的日期和时间处理提供了强大的解决方案。

传统日期/时间API(java.util.Date、Calendar、SimpleDateFormat)存在以下问题:

  • 可变(非线程安全)
  • 令人困惑(月份从0开始,但日期从1开始)
  • 容易出错(日期和时间混在一个类中)
  • 时间区处理能力弱
  • 可读性差

2.实战案例

2.1 旧日期API

  • 可变性问题
public static void main(String[] args) {  // 当前时间  Date datenew Date();  System.out.println("原始值: " + date);  // 修改同一对象  date.setTime(date.getTime() + 1000000000L);  System.out.println("修改后: " + date) ;}

输出结果

原始值: Fri Oct 7 08:43:44 CST 2025修改后: Tue Oct 18 22:30:24 CST 2025

问题:日期对象被修改——这在多线程下是非常不友好的。

  • 令人困惑的API
public static void main(String[] args) {  Calendar cal = Calendar.getInstance();  // 1月,11日  cal.set(2025, 0, 11);  System.out.println(cal.getTime());}

问题:月份从0开始。许多开发者会无意中引发了错误。

  • 线程不安全的格式化
private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");public static void main(String[] args) throws Exception {  final int max = 100;  final int iterations = 1000;  CyclicBarrier barrier = new CyclicBarrier(max) ;  Runnable task = () -> {    try {      barrier.await(); // 所有线程在此同步      for (int i = 0; i < iterations; i++) {        // 这里调用 format 是不安全的!        FORMAT.format(new Date());      }    } catch (Exception e) {      e.printStackTrace();       System.err.println("线程 " + Thread.currentThread().getName() + " 抛出异常: " + e.getClass().getSimpleName());    }  };  for (int i = 0; i < max; i++) {    new Thread(task, "T-" + i).start();  }}

问题:SimpleDateFormat 并非线程安全的,导致在并发应用中输出结果损坏。

2.2 新日期API(Java8+)

新API位于java.time包中:

  • 不可变性:不会意外修改
  • 职责明确分离:LocalDate → 仅日期;LocalTime → 仅时间;LocalDateTime → 日期时间(无时区);ZonedDateTime → 带时区
  • 流畅且易读的API
  • 不可变且安全
public static void main(String[] args) {  LocalDate today = LocalDate.now();  System.out.println("当前日期: " + today);  // 返回新的对象  LocalDate nextWeek = today.plusWeeks(1);  System.out.println("下一周日期: " + nextWeek);  // 并没有发生变化  System.out.println("日期保持不变: " + today);}

安全:原始内容今日从未被修改。

  • 告别基于 0 的月份混淆
public static void main(String[] args) {  // 月份从1开始  LocalDate date = LocalDate.of(2025, 1, 11) ;   System.out.println(date);}

输出结果

2025-01-11

明确:月份为1至12,符合预期。

  • 线程安全的格式化
public static void main(String[] args) {  DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");  LocalDateTime now = LocalDateTime.now();  String formatted = now.format(formatter);  System.out.println(formatted);}

安全:DateTimeFormatter 是不可变的且线程安全的类。

总结

旧版API(Date、Calendar、SimpleDateFormat)

  1. 在多线程应用中存在可变性缺陷
  2. 令人困惑(月份从0开始计数,职责划分不明确)
  3. 非线程安全
  4. 难以使用

新版API(java.time)

  1. 不可变且线程安全
  2. 类型清晰(LocalDate、LocalTime等)
  3. 易于格式化和解析
  4. 提供完善的时区支持
  5. 可读性更强

2.3 新旧API对比

  • 获取当前日期

传统方法

Date today = new Date();System.out.println(today);

现代方法

LocalDate today = LocalDate.now();System.out.println(today);
  • 为日期添加天数

传统方法

Calendar c = Calendar.getInstance();c.add(Calendar.DAY_OF_MONTH, 5);System.out.println(c.getTime());

现代方法

LocalDate today = LocalDate.now();LocalDate future = today.plusDays(5);System.out.println(future);

不可变的原始日期保持不变。

  • 解析和格式化

传统方法

SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");Date date = sdf.parse("11/01/2025");System.out.println("解析: " + date);String formatted = sdf.format(new Date());System.out.println("格式化: " + formatted);

现代方法

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");LocalDate date = LocalDate.parse("11/01/2025", formatter);System.out.println("解析:"+ date);String formatted = LocalDate.now().format(formatter); System.out.println("格式化: "formatted);

线程安全、不可变且更简洁。

  • 时区处理

传统方法

Calendar c = Calendar.getInstance(TimeZone.getTimeZone("Asia/Shanghai"));System.out.println(c.getTime());

现代方法

ZonedDateTime nyTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));System.out.println(nyTime);

明确、人性化且避免错误。

  • 获取当前时间戳

传统方法

long millis = System.currentTimeMillis();System.out.println(millis);

现代方法

Instant now = Instant.now();System.out.println(now);

提供即时时间戳,适用于持久化存储和API调用。

2.4 最佳实践

  • 始终使用现代日期/时间 API

理论:传统的 java.util.Date 和 Calendar 类存在根本性的设计缺陷,包括可变性问题、API 设计欠佳、月份索引混乱(1 月 = 0)、时区支持有限以及性能问题。

// 传统API// java.util.Date oldDate = new java.util.Date();// java.util.Calendar calendar java.util.Calendar.getInstance();// 现代APILocalDate today = LocalDate.now();LocalDateTime now = LocalDateTime.now();Instant timestamp = Instant.now();
  • 根据使用场景选择正确的类

理论:Java日期/时间API针对不同的时间概念提供了专门的类。使用错误的类可能会导致难以察觉的错误和性能问题。

  1. LocalDate — 不包含时间的日期(生日、截止日期、节假日)
  2. LocalTime — 不包含日期的时间(营业时间、闹钟时间)
  3. LocalDateTime — 不包含时区的日期和时间(适用于同一时区的应用)
  4. ZonedDateTime — 包含时区的日期和时间(适用于全球应用、日程安排)
  5. Instant — UTC时间点(时间戳、日志记录、系统事件)
  6. Duration — 基于时间的量(小时、分钟、秒)
  7. Period — 基于日期的量(年、月、日)
public static void demonstrateClasses() {  // 当前日期和时间  LocalDate today = LocalDate.now();  LocalTime currentTime = LocalTime.now();  LocalDateTime now = LocalDateTime.now();  Instant timestamp = Instant.now();  System.out.println("当前日期: " + today);  System.out.println("当前时间: " + currentTime); // 14:30:45.123  System.out.println("现在时间: " + now); // 2025-05-31T14:30:45.123  System.out.println("UTC timestamp: " + timestamp); // 2025-05-31T09:00:45.1232  LocalDate christmas = LocalDate.of(2025, Month.DECEMBER25);  LocalTime meetingTime = LocalTime.of(1430);  LocalDateTime appointment = LocalDateTime.of(christmas, meetingTime);  LocalDate nextWeek = today.plusWeeks(1);  LocalDate lastMonth = today.minusMonths(1);}
  • 优雅处理解析错误

理论:日期/时间解析因格式变化、区域设置差异、无效日期和模糊格式而固有易出错。请始终通过多次格式尝试实现稳健的错误处理。

// 处理多种格式的灵活解析器public static LocalDate parseFlexibleDate(String dateString) {  DateTimeFormatter[] formatters = { DateTimeFormatter.ISO_LOCAL_DATE, // e.g., 2025-10-08      DateTimeFormatter.ofPattern("dd-MM-yyyy"), // e.g., 08-10-2025      DateTimeFormatter.ofPattern("MM/dd/yyyy"), // e.g., 10/08/2025      DateTimeFormatter.ofPattern("dd/MM/yyyy") // e.g., 08/10/2025  };  for (DateTimeFormatter formatter : formatters) {    try {      // 尝试使用当前格式化程序进行解析      return LocalDate.parse(dateString, formatter);    } catch (DateTimeParseException e) {      System.out.println("Failed to parse with format: " + formatter + " → " + e.getMessage());    }  }  // 如果没有格式化程序工作,则抛出一个明确的异常  throw new IllegalArgumentException("Invalid date format: " + dateString);}public static void main(String[] args) {  String[] testDates = { "2025-10-08""08-10-2025""10/08/2025""08/10/2025""invalid-date" };  for (String dateStr : testDates) {    try {      LocalDate parsed = parseFlexibleDate(dateStr);      System.out.println("Parsed Date: " + parsed);    } catch (IllegalArgumentException e) {      System.out.println(" Error: " + e.getMessage());    }  }}

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!