Skip to content
清晨的一缕阳光
返回

Java 日期时间 API 详解

Java 日期时间 API 详解

Java 8 引入了全新的日期时间 API,解决了传统 Date/Calendar 的诸多问题。

一、传统 API 的问题

1.1 Date 的问题

// ❌ 可变、线程不安全
Date date = new Date();

// ❌ 月份从 0 开始
Date date = new Date(2024 - 1900, 0, 15); // 2024 年 1 月 15 日

// ❌ 时区处理麻烦
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));

1.2 Calendar 的问题

// ❌ 代码冗长
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.JANUARY, 15);
int year = cal.get(Calendar.YEAR);

// ❌ 月份从 0 开始
// ❌ 可变、线程不安全

二、Java 8 新 API

2.1 核心类

Java 8 Date/Time API
├── LocalDate      - 日期(年月日)
├── LocalTime      - 时间(时分秒)
├── LocalDateTime  - 日期时间
├── ZonedDateTime  - 带时区的日期时间
├── Instant        - 时间戳
└── Duration/Period - 时间间隔

2.2 LocalDate

// 创建
LocalDate date = LocalDate.now();
LocalDate date = LocalDate.of(2024, 1, 15);
LocalDate date = LocalDate.parse("2024-01-15");

// 获取属性
int year = date.getYear();           // 2024
int month = date.getMonthValue();    // 1
Month monthEnum = date.getMonth();   // JANUARY
int day = date.getDayOfMonth();      // 15
DayOfWeek dow = date.getDayOfWeek(); // MONDAY

// 修改
LocalDate nextDay = date.plusDays(1);
LocalDate nextMonth = date.plusMonths(1);
LocalDate nextYear = date.plusYears(1);

LocalDate prevDay = date.minusDays(1);

// 不可变,返回新对象

2.3 LocalTime

// 创建
LocalTime time = LocalTime.now();
LocalTime time = LocalTime.of(10, 30, 45);
LocalTime time = LocalTime.parse("10:30:45");

// 获取属性
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();

// 修改
LocalTime later = time.plusHours(1);
LocalTime earlier = time.minusMinutes(30);

2.4 LocalDateTime

// 创建
LocalDateTime dateTime = LocalDateTime.now();
LocalDateTime dateTime = LocalDateTime.of(2024, 1, 15, 10, 30);
LocalDateTime dateTime = LocalDateTime.parse("2024-01-15T10:30:00");

// 组合
LocalDate date = LocalDate.of(2024, 1, 15);
LocalTime time = LocalTime.of(10, 30);
LocalDateTime dateTime = LocalDateTime.of(date, time);

// 获取
LocalDate datePart = dateTime.toLocalDate();
LocalTime timePart = dateTime.toLocalTime();

// 修改
LocalDateTime later = dateTime.plusHours(1);

三、时区处理

3.1 ZoneId

// 获取所有时区
Set<String> zoneIds = ZoneId.getAvailableZoneIds();

// 创建时区
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZoneId tokyo = ZoneId.of("Asia/Tokyo");
ZoneId utc = ZoneId.of("UTC");

// 系统默认时区
ZoneId systemDefault = ZoneId.systemDefault();

3.2 ZonedDateTime

// 创建
ZonedDateTime zdt = ZonedDateTime.now();
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime zdt = ZonedDateTime.of(
    LocalDateTime.of(2024, 1, 15, 10, 30),
    ZoneId.of("Asia/Shanghai")
);

// 转换
LocalDateTime ldt = zdt.toLocalDateTime();
LocalDate date = zdt.toLocalDate();
ZoneId zone = zdt.getZone();

// 时区转换
ZonedDateTime tokyoTime = zdt.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));

3.3 Instant

// 时间戳(UTC)
Instant now = Instant.now();

// 与 LocalDateTime 转换
Instant instant = Instant.now();
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());

// 与 Date 转换
Date date = Date.from(instant);
Instant instant = date.toInstant();

// 时间戳
long epochSecond = instant.getEpochSecond();
int nano = instant.getNano();

四、时间间隔

4.1 Duration(时间间隔)

// 创建
Duration d1 = Duration.ofSeconds(30);
Duration d2 = Duration.ofMinutes(10);
Duration d3 = Duration.ofHours(1);

Duration between = Duration.between(LocalTime.of(10, 0), LocalTime.of(11, 30));

// 获取
long seconds = between.getSeconds();
long minutes = between.toMinutes();
long hours = between.toHours();

// 运算
Duration sum = d1.plus(d2);
Duration diff = d2.minus(d1);

4.2 Period(日期间隔)

// 创建
Period p1 = Period.ofDays(7);
Period p2 = Period.ofMonths(1);
Period p3 = Period.ofYears(1);

Period between = Period.between(
    LocalDate.of(2024, 1, 1),
    LocalDate.of(2024, 12, 31)
);

// 获取
int days = between.getDays();
int months = between.getMonths();
int years = between.getYears();

// 运算
Period sum = p1.plus(p2);

五、格式化

5.1 DateTimeFormatter

// 预定义格式
DateTimeFormatter isoDate = DateTimeFormatter.ISO_LOCAL_DATE;
DateTimeFormatter isoDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

// 自定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

// 格式化
LocalDateTime dateTime = LocalDateTime.now();
String formatted = dateTime.format(formatter);
// "2024-01-15 10:30:00"

// 解析
LocalDateTime parsed = LocalDateTime.parse("2024-01-15 10:30:00", formatter);

5.2 常用格式

// 日期
DateTimeFormatter.ofPattern("yyyy-MM-dd");      // 2024-01-15
DateTimeFormatter.ofPattern("yyyy/MM/dd");      // 2024/01/15
DateTimeFormatter.ofPattern("dd/MM/yyyy");      // 15/01/2024

// 时间
DateTimeFormatter.ofPattern("HH:mm:ss");        // 10:30:00
DateTimeFormatter.ofPattern("HH:mm");           // 10:30

// 日期时间
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

// 带时区
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");    // +0800
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss ZZZZ"); // GMT+08:00

5.3 本地化

// 中文格式
DateTimeFormatter chineseFormatter = DateTimeFormatter
    .ofPattern("yyyy 年 MM 月 dd 日")
    .withLocale(Locale.CHINA);

// 英文格式
DateTimeFormatter englishFormatter = DateTimeFormatter
    .ofPattern("MMMM dd, yyyy")
    .withLocale(Locale.US);

六、计算与调整

6.1 TemporalAdjusters

// 第一个工作日
LocalDate firstDay = LocalDate.now()
    .with(TemporalAdjusters.firstDayOfMonth());

// 最后一天
LocalDate lastDay = LocalDate.now()
    .with(TemporalAdjusters.lastDayOfMonth());

// 下一个周一
LocalDate nextMonday = LocalDate.now()
    .with(TemporalAdjusters.next(DayOfWeek.MONDAY));

// 自定义调整器
TemporalAdjuster nextWorkingDay = temporal -> {
    LocalDate date = LocalDate.from(temporal);
    do {
        date = date.plusDays(1);
    } while (date.getDayOfWeek() == DayOfWeek.SATURDAY ||
             date.getDayOfWeek() == DayOfWeek.SUNDAY);
    return date;
};

LocalDate result = LocalDate.now().with(nextWorkingDay);

6.2 日期计算

LocalDate date = LocalDate.of(2024, 1, 15);

// 加减
LocalDate plus10Days = date.plusDays(10);
LocalDate minus5Days = date.minusDays(5);

// 周期
LocalDate plus1Month = date.plus(1, ChronoUnit.MONTHS);
LocalDate plus2Weeks = date.plus(2, ChronoUnit.WEEKS);

// 间隔
Period period = Period.between(
    LocalDate.of(2024, 1, 1),
    LocalDate.of(2024, 12, 31)
);
long days = ChronoUnit.DAYS.between(
    LocalDate.of(2024, 1, 1),
    LocalDate.of(2024, 12, 31)
);

七、实战场景

7.1 生日计算

public int calculateAge(LocalDate birthday) {
    return Period.between(birthday, LocalDate.now()).getYears();
}

// 使用
LocalDate birthday = LocalDate.of(1990, 5, 15);
int age = calculateAge(birthday);

7.2 过期判断

public boolean isExpired(LocalDate expireDate) {
    return expireDate.isBefore(LocalDate.now());
}

// 剩余天数
public long daysUntil(LocalDate targetDate) {
    return ChronoUnit.DAYS.between(LocalDate.now(), targetDate);
}

7.3 时区转换

public ZonedDateTime convertTime(ZonedDateTime sourceTime, ZoneId targetZone) {
    return sourceTime.withZoneSameInstant(targetZone);
}

// 使用
ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime tokyoTime = convertTime(shanghaiTime, ZoneId.of("Asia/Tokyo"));

7.4 工作时间计算

public Duration calculateWorkingHours(LocalTime start, LocalTime end) {
    LocalTime workStart = LocalTime.of(9, 0);
    LocalTime workEnd = LocalTime.of(18, 0);
    LocalTime lunchStart = LocalTime.of(12, 0);
    LocalTime lunchEnd = LocalTime.of(13, 0);
    
    Duration total = Duration.between(start, end);
    Duration lunch = Duration.between(lunchStart, lunchEnd);
    
    return total.minus(lunch);
}

八、与旧 API 互操作

8.1 Date 转换

// Date → LocalDateTime
Date date = new Date();
LocalDateTime ldt = date.toInstant()
    .atZone(ZoneId.systemDefault())
    .toLocalDateTime();

// LocalDateTime → Date
LocalDateTime ldt = LocalDateTime.now();
Date date = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

8.2 Calendar 转换

// Calendar → LocalDateTime
Calendar cal = Calendar.getInstance();
LocalDateTime ldt = cal.toInstant()
    .atZone(ZoneId.systemDefault())
    .toLocalDateTime();

// LocalDateTime → Calendar
LocalDateTime ldt = LocalDateTime.now();
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(ldt.atZone(ZoneId.systemDefault())
    .toInstant()
    .toEpochMilli());

九、最佳实践

9.1 使用建议

// ✅ 使用不可变类
LocalDate date = LocalDate.now(); // 线程安全

// ❌ 避免旧 API
Date date = new Date(); // 可变、线程不安全

// ✅ 明确时区
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));

// ✅ 使用 DateTimeFormatter(线程安全)
private static final DateTimeFormatter FORMATTER = 
    DateTimeFormatter.ofPattern("yyyy-MM-dd");

9.2 性能优化

// ✅ 缓存 formatter
private static final DateTimeFormatter FORMATTER = 
    DateTimeFormatter.ofPattern("yyyy-MM-dd");

// ❌ 每次创建
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

十、总结

Java 8 Date/Time API 核心要点:

用途示例
LocalDate日期2024-01-15
LocalTime时间10:30:00
LocalDateTime日期时间2024-01-15 10:30:00
ZonedDateTime带时区2024-01-15 10:30:00 +08:00
Instant时间戳1705291800
Period日期间隔1 年 2 个月 3 天
Duration时间间隔1 小时 30 分钟

Java 8 Date/Time API 不可变、线程安全、易用,是新项目的首选。


分享这篇文章到:

上一篇文章
事务隔离级别与 MVCC
下一篇文章
Go Context 上下文实战