Skip to content

日期时间迁移

还在用 DateCalendar

是时候迁移到 JDK 8 的日期时间 API 了。

为什么要迁移

┌─────────────────────────────────────────────────────────────────┐
│                    旧 API vs 新 API                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Date / Calendar                                                │
│  ❌ 线程不安全                                                 │
│  ❌ 月份从 0 开始(容易出错)                                     │
│  ❌ 大量 mutable 方法                                           │
│  ❌ API 设计混乱                                                │
│                                                                  │
│  java.time                                                      │
│  ✅ 线程安全                                                   │
│  ✅ 月份从 1 开始(自然)                                       │
│  ✅ 不可变对象                                                 │
│  ✅ 清晰的设计                                                 │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

迁移对照表

旧 API新 API说明
DateInstant, LocalDateTime时间点
CalendarLocalDate, LocalTime, LocalDateTime日历
SimpleDateFormatDateTimeFormatter格式化
TimeZoneZoneId时区
GregorianCalendarZonedDateTime带时区

Date 迁移

创建 Date

java
// ❌ 旧方式
Date now = new Date();
Date date = new Date(System.currentTimeMillis());

// ✅ 新方式
Instant instant = Instant.now();
Date date = Date.from(instant);

Date 转其他类型

java
Date date = new Date();

// Date → Instant
Instant instant = date.toInstant();

// Instant → LocalDateTime(需要时区)
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());

// Instant → LocalDate
LocalDate ld = instant.atZone(ZoneId.systemDefault()).toLocalDate();

其他类型转 Date

java
LocalDateTime ldt = LocalDateTime.now();
Instant instant = ldt.toInstant(ZoneOffset.UTC);
Date date = Date.from(instant);

Calendar 迁移

创建 Calendar

java
// ❌ 旧方式
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.MARCH, 15, 14, 30, 45);  // ❌ 月份从 0 开始!

// ✅ 新方式
LocalDateTime dt = LocalDateTime.of(2024, 3, 15, 14, 30, 45);

Calendar 常用操作

java
// ❌ 旧方式:加一天
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 1);

// ✅ 新方式
LocalDateTime dt = LocalDateTime.now();
LocalDateTime tomorrow = dt.plusDays(1);

获取组件

java
// ❌ 旧方式
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1;  // ❌ 要 +1
int day = cal.get(Calendar.DAY_OF_MONTH);
int hour = cal.get(Calendar.HOUR_OF_DAY);

// ✅ 新方式
LocalDateTime dt = LocalDateTime.now();
int year = dt.getYear();
int month = dt.getMonthValue();  // ✅ 直接就是 1-12
int day = dt.getDayOfMonth();
int hour = dt.getHour();

SimpleDateFormat 迁移

格式化和解析

java
// ❌ 旧方式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formatted = sdf.format(new Date());
Date parsed = sdf.parse("2024-03-15 14:30:45");

// ✅ 新方式
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = LocalDateTime.now().format(fmt);
LocalDateTime parsed = LocalDateTime.parse("2024-03-15 14:30:45", fmt);

线程安全问题

java
// ❌ 旧方式:SimpleDateFormat 不是线程安全的
// 多个线程共用一个 SimpleDateFormat 会出问题

// ✅ 新方式:DateTimeFormatter 是线程安全的
// 可以定义为 static final
private static final DateTimeFormatter FMT = 
    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

TimeZone 迁移

java
// ❌ 旧方式
TimeZone tz = TimeZone.getTimeZone("America/New_York");
cal.setTimeZone(tz);

// ✅ 新方式
ZoneId zone = ZoneId.of("America/New_York");
ZonedDateTime zdt = LocalDateTime.now().atZone(zone);

完整迁移示例

场景:数据库日期字段处理

java
// ❌ 旧方式
public Date parseFromDb(String dbDate) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    try {
        return sdf.parse(dbDate);
    } catch (ParseException e) {
        throw new RuntimeException(e);
    }
}

public String toDb(Date date) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
}

// ✅ 新方式
private static final DateTimeFormatter FMT = 
    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

public Instant parseFromDb(String dbDate) {
    return LocalDateTime.parse(dbDate, FMT)
        .toInstant(ZoneOffset.UTC);
}

public String toDb(Instant instant) {
    return LocalDateTime.ofInstant(instant, ZoneOffset.UTC)
        .format(FMT);
}

场景:计算两个日期之间的天数

java
// ❌ 旧方式
public long daysBetween(Date start, Date end) {
    long diff = end.getTime() - start.getTime();
    return TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
}

// ✅ 新方式
public long daysBetween(LocalDate start, LocalDate end) {
    return ChronoUnit.DAYS.between(start, end);
}

注意事项

月份从 1 开始

java
// ❌ 旧 API:月份从 0 开始
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, 2);  // 设置为 3 月

// ✅ 新 API:月份从 1 开始
LocalDateTime dt = LocalDateTime.now();
LocalDateTime march = dt.withMonth(3);  // 设置为 3 月

时区转换

java
// 数据库存储 UTC,Java 显示本地时间
Instant utcInstant = Instant.now();  // 假设这是从 DB 读出的 UTC 时间

// DB → 本地显示
LocalDateTime local = LocalDateTime.ofInstant(utcInstant, ZoneId.systemDefault());

// 本地输入 → DB 存储
LocalDateTime localInput = LocalDateTime.now();
Instant utc = localInput.toInstant(ZoneOffset.UTC);
// 存入数据库

小结

迁移检查清单:

  • [ ] DateInstant / LocalDateTime
  • [ ] CalendarLocalDate / LocalTime / LocalDateTime
  • [ ] SimpleDateFormatDateTimeFormatter
  • [ ] TimeZoneZoneId
  • [ ] 月份处理是否正确(是否从 1 开始)
  • [ ] 时区转换是否正确

记住:新 API 线程安全、性能更好、功能更完整

基于 VitePress 构建