Skip to content

新日期时间 API

旧 API 的问题

┌─────────────────────────────────────────────────────────────────┐
│                    旧 API 的问题                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ❌ java.util.Date 月份从 0 开始,容易出错                       │
│  ❌ java.util.Date 不是线程安全的                               │
│  ❌ SimpleDateFormat 不是线程安全的                              │
│  ❌ 日期时间格式化是线程共享,效率低下                           │
│                                                                  │
│  ✅ Java 8+ 新的日期时间 API:                                   │
│     • LocalDate、LocalTime、LocalDateTime                      │
│     • ZonedDateTime、OffsetDateTime                            │
│     • Duration、Period                                         │
│     • DateTimeFormatter(线程安全)                             │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

本地日期时间

LocalDate - 日期

java
// 获取当前日期
LocalDate today = LocalDate.now();

// 创建指定日期
LocalDate date1 = LocalDate.of(2024, 1, 15);
LocalDate date2 = LocalDate.of(2024, Month.JANUARY, 15);

// 解析字符串
LocalDate date3 = LocalDate.parse("2024-01-15");

// 获取年月日
int year = today.getYear();        // 2024
int month = today.getMonthValue(); // 1-12
int day = today.getDayOfMonth();   // 1-31

// 获取星期
DayOfWeek dayOfWeek = today.getDayOfWeek(); // MONDAY, TUESDAY...
String dayName = dayOfWeek.getDisplayName(TextStyle.FULL, Locale.CHINA);

// 日期加减
LocalDate tomorrow = today.plusDays(1);
LocalDate nextWeek = today.plusWeeks(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate nextYear = today.plusYears(1);

LocalDate yesterday = today.minusDays(1);

// 日期比较
boolean isAfter = date1.isAfter(date2);
boolean isBefore = date1.isBefore(date2);
boolean isEqual = date1.isEqual(date2);

// 计算天数差
long daysBetween = ChronoUnit.DAYS.between(date1, date2);

LocalTime - 时间

java
// 获取当前时间
LocalTime now = LocalTime.now();

// 创建指定时间
LocalTime time1 = LocalTime.of(14, 30);
LocalTime time2 = LocalTime.of(14, 30, 45);
LocalTime time3 = LocalTime.of(14, 30, 45, 123456789);

// 解析字符串
LocalTime time4 = LocalTime.parse("14:30:45");

// 获取时分秒
int hour = now.getHour();     // 0-23
int minute = now.getMinute(); // 0-59
int second = now.getSecond(); // 0-59
int nano = now.getNano();    // 0-999999999

// 时间加减
LocalTime later = now.plusHours(2);
LocalTime earlier = now.minusMinutes(30);

// 时间比较
boolean isAfter = time1.isAfter(time2);
boolean isBefore = time1.isBefore(time2);

// 计算时间差
long minutesBetween = ChronoUnit.MINUTES.between(time1, time2);

LocalDateTime - 日期时间

java
// 获取当前日期时间
LocalDateTime now = LocalDateTime.now();

// 创建指定日期时间
LocalDateTime dt1 = LocalDateTime.of(2024, 1, 15, 14, 30);
LocalDateTime dt2 = LocalDateTime.of(date1, time1);

// 转换
LocalDate date = now.toLocalDate();
LocalTime time = now.toLocalTime();

// 日期时间加减
LocalDateTime future = now.plusDays(7).minusHours(2);

// 格式化
String str = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

时区日期时间

ZonedDateTime - 带时区

java
// 获取当前时区日期时间
ZonedDateTime now = ZonedDateTime.now();

// 获取指定时区
ZonedDateTime tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
ZonedDateTime newYork = ZonedDateTime.now(ZoneId.of("America/New_York"));

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

// 获取时区
ZoneId zone = now.getZone();
ZoneOffset offset = now.getOffset(); // +08:00

OffsetDateTime - 带偏移量

java
// 带 UTC 偏移量的日期时间
OffsetDateTime odt = OffsetDateTime.now(ZoneOffset.of("+08:00"));

// 计算偏移量差异
int offsetSeconds = odt.getOffset().getTotalSeconds();

Instant - 瞬间

java
// UTC 瞬间
Instant now = Instant.now();

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

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

// 计算
Instant later = now.plus(Duration.ofHours(2));

Duration 与 Period

Duration - 时间段

java
// Duration 用于时间(秒、纳秒)
Duration duration = Duration.ofHours(2);
Duration duration2 = Duration.between(startTime, endTime);

// 转换为其他单位
long hours = duration.toHours();
long minutes = duration.toMinutes();
long seconds = duration.toSeconds();

// 加减
Duration sum = duration.plusMinutes(30);
Duration diff = duration.minusHours(1);

Period - 日期段

java
// Period 用于日期(年、月、日)
Period period = Period.ofYears(1);
Period period2 = Period.of(1, 6, 15);

// 转换为天数
int totalDays = period.getDays(); // 15

// 加减
Period future = period.plusMonths(3);

计算示例

java
LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2024, 12, 31);

// 日期差(Period)
Period period = start.until(end);
System.out.println(period.getYears()); // 0
System.out.println(period.getMonths()); // 11
System.out.println(period.getDays()); // 30

// 总天数(Duration)
long days = ChronoUnit.DAYS.between(start, end);

// 精确时间差
LocalDateTime start2 = LocalDateTime.of(2024, 1, 1, 10, 0);
LocalDateTime end2 = LocalDateTime.of(2024, 1, 1, 12, 30);
Duration duration = Duration.between(start2, end2);
System.out.println(duration.toMinutes()); // 150

日期时间格式化

DateTimeFormatter

java
// 预定义格式
String s1 = now.format(DateTimeFormatter.BASIC_ISO_DATE); // 20240115
String s2 = now.format(DateTimeFormatter.ISO_LOCAL_DATE);  // 2024-01-15
String s3 = now.format(DateTimeFormatter.ISO_LOCAL_TIME);  // 14:30:45

// 自定义格式
DateTimeFormatter f1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter f2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
DateTimeFormatter f3 = DateTimeFormatter.ofPattern("HH:mm:ss");
DateTimeFormatter f4 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

String str = now.format(f4); // "2024-01-15 14:30:45"

// 解析
LocalDate date = LocalDate.parse("2024-01-15", f1);
LocalDateTime dt = LocalDateTime.parse("2024-01-15 14:30:45", f4);

常用格式模式

模式含义示例
yyyy4位年份2024
MM2位月份01-12
dd2位日期01-31
HH24小时00-23
hh12小时01-12
mm分钟00-59
ss00-59
E星期几星期一/Monday
aAM/PM上午/下午

常用工具类

TemporalAdjusters - 日期调整

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

// 本月第一天
LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());

// 本月最后一天
LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());

// 下个月第一天
LocalDate firstDayOfNextMonth = date.with(TemporalAdjusters.firstDayOfNextMonth());

// 本年度第一天
LocalDate firstDayOfYear = date.with(TemporalAdjusters.firstDayOfYear());

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

// 上一个周五
LocalDate lastFriday = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));

// 自定义:月末前5天
LocalDate fiveDaysBeforeMonthEnd = date.with(temporal -> {
    LocalDate lastDay = temporal.toLocalDate().with(TemporalAdjusters.lastDayOfMonth());
    return lastDay.minusDays(5);
});

LocalDate 计算工具

java
// 判断是否闰年
boolean isLeap = LocalDate.now().isLeapYear();

// 判断月末
boolean isLastDayOfMonth = LocalDate.now().isEqual(
    LocalDate.now().with(TemporalAdjusters.lastDayOfMonth())
);

// 月初判断
boolean isFirstDayOfMonth = LocalDate.now().getDayOfMonth() == 1;

// 工作日判断
public static boolean isWorkday(LocalDate date) {
    DayOfWeek dow = date.getDayOfWeek();
    return dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY;
}

与旧 API 互转

java
// Date → LocalDateTime
Date date = new Date();
Instant instant = date.toInstant();
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());

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

// Date → LocalDate
LocalDate ld = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

// String → Date (使用 Instant)
Date date3 = Date.from(LocalDateTime.parse("2024-01-15 14:30:45",
    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
    .atZone(ZoneId.systemDefault()).toInstant());

总结

新日期时间 API 核心知识点

用途示例
LocalDate日期(年月日)2024-01-15
LocalTime时间(时分秒)14:30:45
LocalDateTime日期时间2024-01-15 14:30:45
ZonedDateTime带时区2024-01-15T14:30:45+08:00
InstantUTC 瞬间2024-01-15T06:30:45Z
Duration时间段2小时
Period日期段1年2个月
DateTimeFormatter格式化线程安全

⚠️ 易错点提醒

  1. 月份从 1 开始,不是 0(和旧 API 不同)
  2. DateTimeFormatter 是线程安全的,可以静态共享
  3. Duration 用于时间(秒),Period 用于日期(年月日)
  4. ZonedDateTime 和 OffsetDateTime 是不同的概念
  5. 旧 API 和新 API 可以通过 Instant 互转