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 不可变、线程安全、易用,是新项目的首选。