mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
749 字
2 分钟
Java日期和时间处理详解
2026-02-08

Java的日期时间API进化史简直就是一部踩坑史。从Date到Calendar,再到java.time包,每次改版都在修复上一版的槽点。我最早用Calendar的时候,被那个月份从0开始坑过不止一次。这篇文章把各个版本的用法和坑都整理出来了。

Java日期和时间处理详解#

日期时间API进化史#

版本API槽点
Java 1.0Date大部分方法已废弃
Java 1.2Calendar月份从0开始,线程不安全
Java 8+java.time这才是正确的打开方式

一句话:新项目直接用java.time,别再用Date和Calendar了。

传统API(了解即可,不建议新项目使用)#

Date类#

Date now = new Date();
System.out.println("当前时间: " + now);
long timestamp = now.getTime(); // 时间戳,这个最常用

Date的大部分方法都标记为@Deprecated了,别琢磨怎么用了,就记住getTime()new Date(long)这两个就够了。

Calendar类 —— 坑最多的API#

Calendar的月份从0开始,这个设计真的让人无语。有次我写calendar.set(2023, 1, 1)以为设置的是1月1号,结果跑出来是2月1号,debug了半天才发现:

Calendar calendar = Calendar.getInstance();
calendar.set(2023, 1, 1); // 这是2月1日!不是1月1日!
calendar.set(2023, Calendar.JANUARY, 1); // 正确做法:用常量

SimpleDateFormat —— 线程不安全!#

SimpleDateFormat不是线程安全的,多个线程共用同一个实例会导致诡异的结果:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 错误用法:多个线程共用同一个sdf实例
// 会出现各种奇怪的结果,比如月份变成13、时间错乱
// 正确用法:每次new一个,或者用ThreadLocal包装

建议:新代码直接用DateTimeFormatter,它是线程安全的。

Java 8+ 新API(java.time包)#

几个核心类#

用途示例
LocalDate日期(年月日)2023-01-01
LocalTime时间(时分秒)12:00<00>
LocalDateTime日期+时间2023-01-01T12:00<00>
ZonedDateTime带时区的日期时间2023-01-01T12:00<00>+08<00>
Instant时间戳1970-01-01T00:00<00z>
Duration时间间隔(时分秒)2小时
Period日期间隔(年月日)1年6个月

LocalDate —— 最常用的日期类#

// 创建
LocalDate now = LocalDate.now();
LocalDate specific = LocalDate.of(2023, 1, 1); // 月份是1,不是0!舒服了
LocalDate parsed = LocalDate.parse("2023-01-01");
// 获取字段
int year = now.getYear();
int month = now.getMonthValue(); // 1-12,不用+1了
int day = now.getDayOfMonth();
// 日期计算
LocalDate nextWeek = now.plusDays(7);
LocalDate lastMonth = now.minusMonths(1);
// 日期比较
boolean isAfter = now.isAfter(specific);
boolean isBefore = now.isBefore(specific);
// 计算天数差
long daysBetween = ChronoUnit.DAYS.between(specific, now);
// 闰年判断
boolean isLeapYear = now.isLeapYear();

LocalTime —— 时间类#

LocalTime now = LocalTime.now();
LocalTime specific = LocalTime.of(12, 0, 0);
LocalTime parsed = LocalTime.parse("12:00:00");
int hour = now.getHour();
int minute = now.getMinute();
int second = now.getSecond();
LocalTime plusHours = now.plusHours(1);
LocalTime minusMinutes = now.minusMinutes(30);
long minutesBetween = ChronoUnit.MINUTES.between(specific, now);

LocalDateTime —— 日期+时间#

LocalDateTime now = LocalDateTime.now();
LocalDateTime specific = LocalDateTime.of(2023, 1, 1, 12, 0, 0);
// 拆分
LocalDate date = now.toLocalDate();
LocalTime time = now.toLocalTime();

ZonedDateTime —— 带时区#

// 当前时间(系统时区)
ZonedDateTime now = ZonedDateTime.now();
// 指定时区
ZonedDateTime newYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
// 时区转换
ZonedDateTime shanghai = now.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));

Instant —— 时间戳#

Instant now = Instant.now();
long epochMilli = now.toEpochMilli(); // 毫秒时间戳
long epochSecond = now.getEpochSecond(); // 秒时间戳

格式化与解析#

DateTimeFormatter是线程安全的,可以放心共用:

LocalDateTime now = LocalDateTime.now();
// 预定义格式
DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
// 自定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(formatter); // 2023-01-01 12:00:00
// 解析
LocalDateTime parsed = LocalDateTime.parse("2023-01-01 12:00:00", formatter);

Duration 和 Period#

// Duration:时间间隔(时分秒)
LocalTime start = LocalTime.of(10, 0);
LocalTime end = LocalTime.of(12, 30);
Duration duration = Duration.between(start, end);
long hours = duration.toHours(); // 2
long minutes = duration.toMinutes(); // 150
// Period:日期间隔(年月日)
LocalDate startDate = LocalDate.of(2020, 1, 1);
LocalDate endDate = LocalDate.of(2023, 6, 15);
Period period = Period.between(startDate, endDate);
int years = period.getYears(); // 3
int months = period.getMonths(); // 5
int days = period.getDays(); // 14

实战贴士#

  1. 新项目一律用java.time包:Date、Calendar、SimpleDateFormat都是历史遗留,别在新代码里用了
  2. SimpleDateFormat不是线程安全的:多线程共用会出诡异bug,用DateTimeFormatter替代
  3. 存储时间用UTC:数据库里存UTC时间,展示时再转用户时区
  4. DateTimeFormatter可以缓存:它是线程安全的,定义为static final复用
  5. 注意不可变性:java.time的类都是不可变的,now.plusDays(1)返回新对象,now本身不变

常见坑#

  • Calendar月份从0开始:1月是0,12月是11。这个坑我踩过无数次,用java.time包就没事了。
  • SimpleDateFormat线程不安全:高并发下会出现各种奇怪的结果,比如时间错乱、月份不对。用DateTimeFormatter替代。
  • 时区问题:不指定时区就默认用系统时区,部署到不同地区的服务器上,结果可能不一样。建议统一用UTC。
  • 格式化解析异常:输入的字符串格式不对会抛异常,记得try-catch。
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

部分信息可能已经过时

目录