Skip to content

日期时间实战

常见场景

实际开发中日期时间的常见需求,这篇汇总最佳实践。

日期计算

计算年龄

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

// 使用
int age = calculateAge(LocalDate.of(2000, 3, 22));

计算两个日期之间的天数

java
// 方式一:Duration(精确到秒)
long days = Duration.between(startDate.atStartOfDay(), endDate.atStartOfDay()).toDays();

// 方式二:Period
Period period = Period.between(startDate, endDate);
long days2 = period.getDays(); // 不够准确,不推荐

// 方式三:直接计算
long days3 = startDate.until(endDate, ChronoUnit.DAYS);

计算工作日

java
public static long countWorkdays(LocalDate start, LocalDate end) {
    long count = 0;
    LocalDate current = start;
    while (!current.isAfter(end)) {
        DayOfWeek dow = current.getDayOfWeek();
        if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) {
            count++;
        }
        current = current.plusDays(1);
    }
    return count;
}

获取月初和月末

java
LocalDate today = LocalDate.now();

// 月初
LocalDate monthStart = today.withDayOfMonth(1);
// 或者
LocalDate monthStart2 = today.with(TemporalAdjusters.firstDayOfMonth());

// 月末
LocalDate monthEnd = today.with(TemporalAdjusters.lastDayOfMonth());

// 下个月初
LocalDate nextMonthStart = today.plusMonths(1).withDayOfMonth(1);

判断闰年

java
public static boolean isLeapYear(int year) {
    return Year.isLeap(year);
}

// 或者
LocalDate.of(year, 1, 1).lengthOfYear() == 366;

日期时间解析

解析常见格式

java
// yyyy-MM-dd
LocalDate date1 = LocalDate.parse("2026-03-22");

// yyyy-MM-dd HH:mm:ss
LocalDateTime dt1 = LocalDateTime.parse("2026-03-22 14:30:00",
    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

// 中文格式
LocalDate date2 = LocalDate.parse("2026年03月22日",
    DateTimeFormatter.ofPattern("yyyy年MM月dd日"));

// 带毫秒
LocalDateTime dt2 = LocalDateTime.parse("2026-03-22T14:30:45.123",
    DateTimeFormatter.ISO_LOCAL_DATE_TIME);

处理异常格式

java
// 宽松解析
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")
    .withResolverStyle(ResolverStyle.LENIENT);

LocalDate date = LocalDate.parse("2026/02/30", formatter);
// 自动修正为 2026-03-02(2 月 30 日 → 3 月 2 日)

日期时间格式化

常用格式

java
LocalDateTime dt = LocalDateTime.of(2026, 3, 22, 14, 30, 45);

// 标准格式
dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));           // 2026-03-22
dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));  // 2026-03-22 14:30:45
dt.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"));// 2026/03/22 14:30

// 中文格式
dt.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss")); // 2026年03月22日 14:30:45

// 带星期
dt.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 E HH:mm")); // 2026年03月22日 日 14:30

带时区格式化

java
ZonedDateTime zdt = ZonedDateTime.now();

// ISO 格式
String iso = zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
// 2026-03-22T14:30:45+08:00[Asia/Shanghai]

// 自定义格式
String custom = zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss XXX"));
// 2026-03-22 14:30:45 +08:00

时区转换

场景:API 统一用 UTC

java
// 存储:转为 UTC
ZonedDateTime utcTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"))
    .withZoneSameInstant(ZoneOffset.UTC);

// 存入数据库
Instant instant = utcTime.toInstant();
long timestamp = instant.toEpochMilli();

// 读取:从 UTC 转换
ZonedDateTime localTime = Instant.ofEpochMilli(timestamp)
    .atZone(ZoneId.of("Asia/Shanghai"));

场景:跨时区时间显示

java
// 统一用 UTC 存储
ZonedDateTime utcNow = ZonedDateTime.now(ZoneOffset.UTC);

// 美国用户看到的本地时间
ZonedDateTime usTime = utcNow.withZoneSameInstant(ZoneId.of("America/New_York"));

// 中国用户看到的本地时间
ZonedDateTime cnTime = utcNow.withZoneSameInstant(ZoneId.of("Asia/Shanghai"));

性能测量

java
// 用 Duration 精确测量
public static <T> T measure(String label, Supplier<T> task) {
    Instant start = Instant.now();
    T result = task.get();
    Instant end = Instant.now();
    Duration duration = Duration.between(start, end);
    System.out.println(label + " 耗时: " + duration.toMillis() + " ms");
    return result;
}

// 使用
List<String> sorted = measure("排序", () -> {
    List<String> list = new ArrayList<>();
    // ...
    Collections.sort(list);
    return list;
});

线程安全

JDK 8+ 的日期时间 API 都是线程安全的:

java
// 可以安全地作为静态常量
public class DateUtil {
    private static final DateTimeFormatter DATE_FORMATTER =
        DateTimeFormatter.ofPattern("yyyy-MM-dd");

    private static final DateTimeFormatter DATETIME_FORMATTER =
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static String formatDate(LocalDate date) {
        return date.format(DATE_FORMATTER);
    }

    public static String formatDateTime(LocalDateTime dt) {
        return dt.format(DATETIME_FORMATTER);
    }
}

多线程环境下直接调用,无需任何同步:

java
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    executor.submit(() -> {
        String today = DateUtil.formatDate(LocalDate.now()); // 安全!
    });
}

总结

场景推荐方案
只关心日期LocalDate
只关心时间LocalTime
日期+时间LocalDateTime
存储/传输Instant 或 UTC ZonedDateTime
显示给用户转换到用户时区
计算日期间隔Period
计算时间间隔Duration
格式化DateTimeFormatter(线程安全)

基于 VitePress 构建