顾乔芝士网

持续更新的前后端开发技术栈

疯传!Java 日期时间底层逻辑大揭秘,看完直接拿捏面试官挖的坑!

作为Java开发者,处理日期和时间是日常工作中的常见任务。本文将带你全面掌握Java日期时间处理的方方面面,从基础概念到高级应用,从常见陷阱到最佳实践。

一、Java日期时间API演进史

让我们先通过一个对比表格了解Java日期时间API的发展历程:

版本

API

优点

缺点

现状

Java 1.0

java.util.Date

简单易用

设计缺陷多,月份从0开始

已废弃

Java 1.1

java.util.Calendar

解决了Date的部分问题

API设计复杂,可变对象

不推荐

Java 8

java.time (JSR-310)

设计优良,线程安全

学习曲线稍陡

推荐使用

通俗解释:就像手机发展史,Date像早期的功能机,Calendar像复杂的智能机,而java.time就像现代流畅的智能手机。

1.1 总结对比表

特性

java.util.Date

java.util.Calendar

java.time

设计

糟糕

复杂

优秀

线程安全

可读性

一般

方法链

不支持

不支持

支持

时区支持

有限

完善

扩展性

一般

推荐使用

不推荐

不推荐

推荐

1.2 UML核心类图

┌───────────────────┐       ┌───────────────────┐       ┌───────────────────┐
│    Temporal       │<|-----│    TemporalAccessor│       │   DateTimeFormatter│
└───────────────────┘       └───────────────────┘       └───────────────────┘
         ^                                                    ^
         |                                                    |
┌───────────────────┐       ┌───────────────────┐       ┌───────────────────┐
│  ChronoLocalDate  │       │    LocalTime      │       │      ZoneId        │
└───────────────────┘       └───────────────────┘       └───────────────────┘
         ^                          ^                              ^
         |                          |                              |
┌───────────────────┐       ┌───────────────────┐       ┌───────────────────┐
│    LocalDate      │       │  LocalDateTime    │------>│    ZonedDateTime   │
└───────────────────┘       └───────────────────┘       └───────────────────┘
         ^                                                    ^
         |                                                    |
┌───────────────────┐                                ┌───────────────────┐
│    Instant        │                                │    OffsetDateTime  │
└───────────────────┘                                └───────────────────┘

1.3 思维导图概览

Java日期时间处理
├── 传统API
│   ├── Date (不推荐)
│   └── Calendar (不推荐)
└── java.time (JSR-310)
    ├── 基本类
    │   ├── Instant (时间点)
    │   ├── LocalDate (日期)
    │   ├── LocalTime (时间)
    │   └── LocalDateTime (日期时间)
    ├── 时区类
    │   ├── ZoneId (时区)
    │   ├── ZonedDateTime (带时区时间)
    │   └── OffsetDateTime (带偏移时间)
    ├── 时间段
    │   ├── Duration (基于时间)
    │   └── Period (基于日期)
    ├── 工具类
    │   ├── DateTimeFormatter (格式化)
    │   └── TemporalAdjusters (调整器)
    └── 其他
        ├── ChronoUnit (时间单位)
        └── DayOfWeek (星期枚举)

二、核心类详解

2.1 基础类介绍

// 1. LocalDate - 只包含日期,不包含时间和时区
LocalDate birthday = LocalDate.of(1990, 8, 20);
System.out.println("我的生日: " + birthday);

// 2. LocalTime - 只包含时间,不包含日期和时区
LocalTime lunchTime = LocalTime.of(12, 30);
System.out.println("午餐时间: " + lunchTime);

// 3. LocalDateTime - 包含日期和时间,但不包含时区
LocalDateTime meetingTime = LocalDateTime.of(2023, 8, 20, 14, 30);
System.out.println("会议时间: " + meetingTime);

// 4. ZonedDateTime - 完整的日期时间,包含时区
ZonedDateTime flightTime = ZonedDateTime.of(
    LocalDateTime.of(2023, 8, 20, 15, 30),
    ZoneId.of("Asia/Shanghai")
);
System.out.println("航班时间: " + flightTime);

// 5. Instant - 时间线上的瞬时点(UTC时间)
Instant now = Instant.now();
System.out.println("当前时刻(UTC): " + now);

// 6、时间段:Duration (基于时间的量,如"25秒")
Duration duration = Duration.ofSeconds(25);

2.2 核心类对比表

包含日期

包含时间

包含时区

典型用途

LocalDate

不含时区的日期,生日、纪念日

LocalTime

不含时区的时间,营业时间、会议时间

LocalDateTime

不含时区的日期时间,本地活动时间

ZonedDateTime

带时区的日期时间,国际航班时间

Instant

隐含UTC

日志时间戳

Period

日期区间(年月日)

Duration

时间区间(时分秒纳秒),PT8H6M12.345S (8小时6分12.345秒)

三、日期时间操作大全

3.1 创建与获取

// 获取当前日期时间
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now();
LocalDateTime current = LocalDateTime.now();

// 指定日期创建
LocalDate projectStart = LocalDate.of(2023, Month.SEPTEMBER, 1);
LocalTime standupTime = LocalTime.of(9, 30); // 每日站会时间

// 解析字符串
LocalDate parsedDate = LocalDate.parse("2023-08-20");
LocalTime parsedTime = LocalTime.parse("14:30:45");
LocalDateTime parsedDT = LocalDateTime.parse("2023-08-20T14:30:45");

3.2 日期时间运算

// 基本加减操作
LocalDate tomorrow = today.plusDays(1);
LocalDate nextWeek = today.plusWeeks(1);
LocalTime inAnHour = now.plusHours(1);

// 使用Period处理日期间隔
Period oneMonth = Period.ofMonths(1);
LocalDate nextMonth = today.plus(oneMonth);

// 使用Duration处理时间间隔
Duration twoHours = Duration.ofHours(2);
LocalTime later = now.plus(twoHours);

// 计算两个日期间隔
long daysBetween = ChronoUnit.DAYS.between(today, nextMonth);
System.out.println("间隔天数: " + daysBetween);

3.3 日期时间比较

// 比较方法
boolean isAfter = tomorrow.isAfter(today);
boolean isBefore = today.isBefore(tomorrow);
boolean isEqual = today.equals(LocalDate.now());

// 比较时间先后
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 0);
if (now.isAfter(start) && now.isBefore(end)) {
    System.out.println("现在是工作时间");
}

四、格式化与解析

4.1 SimpleDateFormat使用

java.util.Date

import java.util.Date;
import java.text.SimpleDateFormat;

// 创建当前时间的Date对象
Date now = new Date();
System.out.println(now); // 默认格式输出

// 格式化Date
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formattedDate = sdf.format(now);
System.out.println(formattedDate);

// 解析字符串为Date
String dateString = "2023-05-15 14:30:00";
Date parsedDate = sdf.parse(dateString);
System.out.println(parsedDate);

java.util.Calendar

import java.util.Calendar;

// 获取Calendar实例
Calendar calendar = Calendar.getInstance();

// 设置特定日期时间
calendar.set(2023, Calendar.MAY, 15, 14, 30, 0);

// 获取各个字段
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1; // 月份从0开始
int day = calendar.get(Calendar.DAY_OF_MONTH);

// 转换为Date
Date date = calendar.getTime();

// 使用SimpleDateFormat格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
System.out.println(sdf.format(date));

4.2 DateTimeFormatter使用

基础格式化

// 使用预定义格式
String isoDate = today.format(DateTimeFormatter.ISO_LOCAL_DATE);
String isoTime = now.format(DateTimeFormatter.ISO_LOCAL_TIME);

// 自定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String formatted = current.format(formatter);
System.out.println("格式化结果: " + formatted);

// 解析
LocalDateTime parsed = LocalDateTime.parse("2023年08月20日 14:30:45", formatter);

本地化格式化

// 本地化格式
DateTimeFormatter germanFormatter = DateTimeFormatter
    .ofLocalizedDateTime(FormatStyle.MEDIUM)
    .withLocale(Locale.GERMAN);
String germanDateTime = current.format(germanFormatter);
System.out.println("德国格式: " + germanDateTime);

// 中文格式
DateTimeFormatter chineseFormatter = DateTimeFormatter
    .ofLocalizedDateTime(FormatStyle.LONG)
    .withLocale(Locale.CHINA);
System.out.println("中文格式: " + current.format(chineseFormatter));

常见模式符号

符号

含义

示例

y

2023, 23

M

8, 08, Aug, August

d

5, 05

H

小时(0-23)

0, 05, 14, 23

h

小时(1-12, AM/PM)

1, 12

m

分钟

0, 05, 30, 59

s

0, 05, 30, 59

S

毫秒

0, 500, 999

a

AM/PM

AM, PM

E

星期

Mon, Monday

z

时区

CST, GMT+08:00

Z

时区偏移

+0800

4.3 其他第三方库

4.3.1 Joda-Time (Java 8前推荐)

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

DateTime dt = new DateTime();
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");

// 格式化
String str = fmt.print(dt);
System.out.println(str);

// 解析
DateTime parsedDt = fmt.parseDateTime("2023-05-15 14:30:00");
System.out.println(parsedDt);

4.3.2 Apache Commons Lang的FastDateFormat

import org.apache.commons.lang3.time.FastDateFormat;
import java.util.Date;

FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");
String formatted = fdf.format(new Date());
System.out.println(formatted);

五、时区处理实战

5.1 时区基础

// 获取可用时区
Set<String> zoneIds = ZoneId.getAvailableZoneIds();
zoneIds.stream().filter(z -> z.contains("Asia")).forEach(System.out::println);

// 创建时区对象
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");
ZoneId newYorkZone = ZoneId.of("America/New_York");

// 时区转换
ZonedDateTime shanghaiTime = ZonedDateTime.now(shanghaiZone);
ZonedDateTime newYorkTime = shanghaiTime.withZoneSameInstant(newYorkZone);
System.out.println("上海时间: " + shanghaiTime);
System.out.println("纽约时间: " + newYorkTime);

5.2 夏令时处理

// 检查夏令时
ZoneId londonZone = ZoneId.of("Europe/London");
ZoneRules rules = londonZone.getRules();
boolean isDst = rules.isDaylightSavings(Instant.now());
System.out.println("伦敦是否在夏令时: " + isDst);

// 夏令时转换示例
ZonedDateTime beforeDst = ZonedDateTime.of(
    LocalDateTime.of(2023, 3, 12, 1, 59),
    ZoneId.of("America/New_York"));
ZonedDateTime afterDst = beforeDst.plusMinutes(2);
System.out.println("夏令时转换: " + beforeDst + " → " + afterDst);

5.3 日期加减计算

LocalDate today = LocalDate.now();

// 加1天
LocalDate tomorrow = today.plusDays(1);

// 加1周
LocalDate nextWeek = today.plusWeeks(1);

// 加2个月
LocalDate inTwoMonths = today.plusMonths(2);

// 减10年
LocalDate tenYearsAgo = today.minusYears(10);

5.4 日期比较

LocalDate date1 = LocalDate.of(2023, 5, 1);
LocalDate date2 = LocalDate.of(2023, 5, 15);

// 比较
boolean isBefore = date1.isBefore(date2); // true
boolean isAfter = date1.isAfter(date2);  // false
boolean isEqual = date1.isEqual(date2);  // false

// 计算两个日期之间的天数
long daysBetween = ChronoUnit.DAYS.between(date1, date2); // 14

六、高级技巧

6.1 时间调整器

// 内置调整器
LocalDate nextFriday = today.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());

// 自定义调整器:下个工作日
TemporalAdjuster nextWorkingDay = temporal -> {
    DayOfWeek day = DayOfWeek.from(temporal);
    int daysToAdd = 1;
    if (day == DayOfWeek.FRIDAY) daysToAdd = 3;
    else if (day == DayOfWeek.SATURDAY) daysToAdd = 2;
    return temporal.plus(daysToAdd, ChronoUnit.DAYS);
};

LocalDate nextWorkDate = today.with(nextWorkingDay);
System.out.println("下个工作日: " + nextWorkDate);

6.2 时间段计算

// 计算两个日期之间的工作日
public static long countWorkingDays(LocalDate start, LocalDate end) {
    return start.datesUntil(end)
        .filter(date -> date.getDayOfWeek() != DayOfWeek.SATURDAY 
                     && date.getDayOfWeek() != DayOfWeek.SUNDAY)
        .count();
}

// 计算年龄
public static int calculateAge(LocalDate birthDate, LocalDate currentDate) {
    return Period.between(birthDate, currentDate).getYears();
}

七、与传统API互操作

7.1 与Date/Calendar转换

// Date → java.time
Date oldDate = new Date();
Instant instant = oldDate.toInstant();
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());

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

// Calendar → java.time
Calendar calendar = Calendar.getInstance();
ZonedDateTime zdtFromCal = ZonedDateTime.ofInstant(
    calendar.toInstant(), calendar.getTimeZone().toZoneId());

7.2 与数据库交互

// java.time → SQL
LocalDate localDate = LocalDate.now();
java.sql.Date sqlDate = java.sql.Date.valueOf(localDate);

LocalDateTime localDateTime = LocalDateTime.now();
java.sql.Timestamp timestamp = java.sql.Timestamp.valueOf(localDateTime);

// SQL → java.time
LocalDate fromSqlDate = sqlDate.toLocalDate();
LocalDateTime fromTimestamp = timestamp.toLocalDateTime();

八、最佳实践

  1. 始终明确时区:避免隐式使用系统默认时区
  2. // 不推荐
    ZonedDateTime zdt = LocalDateTime.now().atZone(ZoneId.systemDefault());
    // 推荐
    ZonedDateTime zdt = LocalDateTime.now().atZone(ZoneId.of("Asia/Shanghai"));
  3. 使用不可变对象:所有修改操作返回新对象
  4. LocalDate date = LocalDate.now();
    // 错误 - 结果被丢弃
    date.plusDays(1);
    // 正确
    LocalDate tomorrow = date.plusDays(1);
  5. 选择合适的时间类型
  6. 生日、纪念日 → LocalDate
  7. 营业时间 → LocalTime
  8. 本地活动 → LocalDateTime
  9. 国际事件 → ZonedDateTime
  10. 时间戳 → Instant

九、常见问题与解决方案

问题1:时区转换错误

场景:跨国会议时间显示不正确

解决方案

LocalDateTime localMeetingTime = LocalDateTime.of(2023, 8, 20, 14, 0);
ZonedDateTime londonMeeting = localMeetingTime.atZone(ZoneId.of("Europe/London"));
ZonedDateTime newYorkMeeting = londonMeeting.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("伦敦时间: " + londonMeeting);
System.out.println("纽约时间: " + newYorkMeeting);

问题2:日期格式解析失败

场景:用户输入的日期字符串格式不固定

解决方案

String[] possiblePatterns = {"yyyy-MM-dd", "yyyy/MM/dd", "MM/dd/yyyy"};

LocalDate parseDate(String dateStr) {
    for (String pattern : possiblePatterns) {
        try {
            return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
        } catch (DateTimeParseException e) {
            // 尝试下一种格式
        }
    }
    throw new IllegalArgumentException("无法解析日期: " + dateStr);
}

十、性能优化技巧

  1. 重用DateTimeFormatter:避免重复创建格式化对象
  2. // 类级别定义
    private static final DateTimeFormatter FORMATTER =
    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public String formatDateTime(LocalDateTime dateTime) {
    return dateTime.format(FORMATTER);
    }
  3. 使用Instant处理高频时间操作
  4. Instant start = Instant.now();
    // 执行操作...
    Instant end = Instant.now();
    Duration elapsed = Duration.between(start, end);
    System.out.println("耗时: " + elapsed.toMillis() + "ms");

附录:完整方法参考表

LocalDate常用方法

方法

描述

示例

now()

获取当前日期

LocalDate.now()

of()

指定日期创建

LocalDate.of(2023, 8, 20)

parse()

解析字符串

LocalDate.parse("2023-08-20")

plusDays()

加天数

today.plusDays(1)

minusMonths()

减月数

today.minusMonths(1)

getDayOfWeek()

获取星期

today.getDayOfWeek()

isLeapYear()

是否闰年

today.isLeapYear()

datesUntil()

日期范围流

start.datesUntil(end)

通过本指南,你应该已经掌握了Java日期时间处理的完整知识体系。记住,对于新项目,始终优先使用java.time API,它提供了更现代、更安全的日期时间操作方式。

日期指南已解锁,别嘚瑟!时区偏移量没处理好,约会时间都能给你整成跨国异地恋!

笑过之后请记住:生活就像我的排版,乱中有序,序中带梗。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言