LocalDate / LocalTime / LocalDateTime / Instant 源码精读
概述
java.time(JSR-310)是 Java 8 引入的全新日期时间 API:不可变、线程安全、语义清晰。LocalDate(年月日)、LocalTime(时分秒纳秒)、LocalDateTime(二者组合)都是无时区的时间,Instant 是 UTC 时间线上的瞬时点。理解它们的内部存储与计算方式,能避免常见的时间处理陷阱。本文基于 OpenJDK 21 源码拆解。
一、LocalDate.now() 与 Clock
java
// java.time.LocalDate
public static LocalDate now() {
return now(Clock.systemDefaultZone()); // ① 系统默认时区时钟
}
public static LocalDate now(Clock clock) {
// ② 取时钟的当前时刻 + 时区 → 转成日期
final Instant now = clock.instant(); // 当前瞬时
return ofInstant(now, clock.getZone()); // ③ 按时区转换
}
public static LocalDate ofInstant(Instant instant, ZoneId zone) {
// 从 Instant 按 UTC 偏移计算年月日
long localSecond = instant.getEpochSecond() + zone.getRules().getOffset(instant).getTotalSeconds();
...
return ofYearDay(localEpochDay, ...); // 用 epochDay 计算
}Clock 体系:
Clock.systemDefaultZone():系统时钟(跟随系统时间)
Clock.systemUTC():UTC 时钟
Clock.fixed():固定时刻(测试用)
Clock.offset():偏移时钟
可自定义 Clock → 应用层可注入时钟(测试可控)二、LocalDate 的内部存储
java
// java.time.LocalDate
public final class LocalDate implements Temporal, ... {
private final int year; // 年
private final short month; // 月(1-12)
private final short day; // 日(1-31)
}存储特点:
三个字段:year(int) + month(short) + day(short)
→ 12 字节对象(未算对象头),紧凑
不可变:所有修改操作返回新实例
范围:年份 -999,999,999 到 +999,999,9992.1 LocalDate.of(...) 的校验
java
public static LocalDate of(int year, int month, int dayOfMonth) {
YEAR.checkValidValue(year); // 年范围校验
MONTH_OF_YEAR.checkValidValue(month); // 月 1-12
// ③ 年月日组合校验(日是否合法:2 月 29、30/31 天等)
return create(year, month, dayOfMonth);
}
private static LocalDate create(int year, int month, int dayOfMonth) {
// 日范围校验(含闰年 2 月)
if (dayOfMonth > 28 && dayOfMonth > lengthOfMonth(year, month)) {
throw new DateTimeException("Invalid date ...");
}
return new LocalDate(year, month, dayOfMonth);
}校验链:
Range.checkValidIntValue → 检查字段值范围(年/月/日各自范围)
create → 检查"具体月份的日期合法性"(2 月 29 只在闰年)
所有非法组合(如 2 月 30 日)→ DateTimeException三、plusDays 的时间线计算
java
public LocalDate plusDays(long daysToAdd) {
if (daysToAdd == 0) return this;
// 时间线天数计算
long mjDay = Math.addExact(toEpochDay(), daysToAdd); // 转 epochDay 加
return LocalDate.ofEpochDay(mjDay); // 再转回日期
}
// epochDay:自 1970-01-01 起的天数
public long toEpochDay() {
long y = year;
long m = month;
long total = 365 * y;
// 400 年周期修正(儒略历→格里高利历转换)
...
return total - 719528; // 对齐 1970-01-01
}plusDays 跨月/跨年处理:
直接以"时间线天数"(epochDay)运算,天然处理月末溢出
1 月 31 日 + 1 天 → epochDay+1 → 2 月 1 日(无"无效日期"概念)
同年末 + 1 天 → 次年 1 月 1 日3.1 resolvePreviousValid() 月末溢出(plusMonths/plusYears)
java
// 加月的特殊处理:1 月 31 日 + 1 月 = ?
public LocalDate plusMonths(long monthsToAdd) {
...
int month0 = month - 1; // 0-based
int monthCount = year * 12 + month0; // 总月数
int calcMonths = (int) (monthCount + monthsToAdd);
int newYear = calcMonths / 12;
int newMonth = (calcMonths % 12) + 1;
// 关键:月末溢出修正
int dom = day;
if (dom > lengthOfMonth(newYear, newMonth)) // 新月份没有这一天
dom = lengthOfMonth(newYear, newMonth); // 回退到月末
return new LocalDate(newYear, newMonth, dom);
}resolvePreviousValid 语义:
1 月 31 日 + 1 月 → 2 月没有 31 日 → 取 2 月最后一天(28/29)
1 月 31 日 + 2 月 → 3 月 31 日(3 月有 31 日 → 保持)
规则:"向前解析到最近有效日"(PreviousValid)
注意与 MySQL/PostgreSQL 的"报错"策略不同四、LocalTime 的内部存储
java
// java.time.LocalTime
public final class LocalTime implements Temporal, ... {
private final byte hour; // 时(0-23)
private final byte minute; // 分(0-59)
private final byte second; // 秒(0-59)
private final int nano; // 纳秒(0-999,999,999)
}存储特点:
4 个字段:byte hour + byte minute + byte second + int nano
纳秒级精度(NANOS_PER_SECOND = 1_000_000_000)
范围 00:00:00.000000000 到 23:59:59.999999999java
// 校验
public static LocalTime of(int hour, int minute, int second, int nanoOfSecond) {
HOUR_OF_DAY.checkValidValue(hour); // 0-23
MINUTE_OF_HOUR.checkValidValue(minute); // 0-59
SECOND_OF_MINUTE.checkValidValue(second); // 0-59
NANO_OF_SECOND.checkValidValue(nanoOfSecond); // 0-999999999
return create(hour, minute, second, nanoOfSecond);
}五、plusHours 的时钟循环
java
public LocalTime plusHours(long hoursToAdd) {
if (hoursToAdd == 0) return this;
// 取 24 小时余数:加 500 小时与加 20 小时等价
int newHour = ((int) (hoursToAdd % 24) + hour + 24) % 24;
return create(newHour, minute, second, nano);
}时钟循环(mod 24):
23:00 + 2 小时 → (2 + 23) % 24 = 1 → 01:00
-2 小时 → (-2 + hour + 24) % 24 → 保证正余数
→ 时间"绕圈"而非溢出报错(与 plusDays 不同:时间加满 24 不跨日)
跨日由 LocalDateTime 的 date 部分承担六、LocalDateTime 的组合结构
java
// java.time.LocalDateTime
public final class LocalDateTime implements Temporal, ... {
private final LocalDate date; // 日期部分
private final LocalTime time; // 时间部分
}LocalDateTime 本质:
没有任何"独立状态"——就是 date + time 的组合
内部方法大多委托给 date 或 time:
plusDays → date.plusDays(days)(time 不变)
plusNanos → time.plusNanos(nanos)(date 不变,除非跨日)
加减操作的跨日结转由各字段的 modulo 处理
→ 修改 date 返回新 date,修改 time 返回新 time,再组合新实例java
// 组合示例
LocalDateTime dt = LocalDateTime.of(LocalDate.of(2026, 8, 7), LocalTime.of(12, 30));七、Instant 的时间线瞬时
7.1 Instant.now() 与内部存储
java
// java.time.Instant
public static Instant now() {
return Clock.systemUTC().instant(); // UTC 时钟当前瞬时
}
public final class Instant implements Temporal, ... {
private final long seconds; // 自 1970-01-01T00:00:00Z 起的秒数
private final int nanos; // 秒内的纳秒(0-999,999,999)
}Instant 存储:
seconds:long,从 epoch 起算(1970-01-01T00:00:00Z)
nanos:int,秒内纳秒
表示范围:±10 亿年(秒数在 long 内)
精度:纳秒(系统时钟通常只到毫秒/微秒)7.2 ofEpochSecond 的归一化
java
public static Instant ofEpochSecond(long epochSecond, long nanoAdjustment) {
// 把 nanoAdjustment 归一到秒 + 秒内纳秒
long secs = Math.addExact(epochSecond,
Math.floorDiv(nanoAdjustment, NANOS_PER_SECOND));
int nos = (int) Math.floorMod(nanoAdjustment, NANOS_PER_SECOND);
return create(secs, nos);
}归一化示例:
ofEpochSecond(100, 1_500_000_000) // 秒 + 15 亿纳秒 = +1.5 秒
→ secs = 100 + 1 = 101
→ nos = 500_000_000
ofEpochSecond(100, -1) // 负纳秒
→ secs = 100 + (-1/1e9 = -1) = 99
→ nos = 999_999_999
→ 任何 (second, nano) 组合都归一到合法区间7.3 until 计算时间差
java
public long until(Temporal endExclusive, TemporalUnit unit) {
// 委托给 unit.between(如 ChronoUnit.SECONDS.between)
return unit.between(this, endExclusive);
}
// ChronoUnit.between(秒差示例)
public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) {
...
// SECONDS → 秒差
// NANOS → 秒差*1e9 + 纳秒差(溢出检查)
// DAYS → epochDay 差
}until 时间差:
按指定单位计算两瞬时差(支持 SECONDS/NANOS/MINUTES/HOURS/DAYS...)
秒/纳秒运算基于 seconds 与 nanos 直接相减
大单位基于 epochDay 或秒差整除
溢出(超过 long 范围)→ ArithmeticException八、实现要点
LocalDate/Time/DateTime/Instant 核心:
LocalDate:year(int) + month(short) + day(short),epochDay 时间线运算
of 校验:Range.checkValidValue + create 的月份天数校验
plusDays:toEpochDay 加天数再转回(天然跨月跨年)
plusMonths:resolvePreviousValid 月末溢出回退到最近有效日
LocalTime:byte×3 + int nano,纳秒精度
plusHours:mod 24 时钟循环(绕圈不跨日)
LocalDateTime:date + time 组合,无独立状态
Instant:seconds(long) + nanos(int),UTC 时间线
ofEpochSecond:floorDiv/floorMod 归一化
until:unit.between 时间差(溢出抛异常)
常见陷阱:
1 月 31 日 plusMonths(1) → 2 月 28/29(不是报错)
LocalDateTime 无时区 → 不代表任何具体时刻
Instant.now() 精度取决于系统时钟(可能只有毫秒)
闰年 2 月 29 日 plusYears(1) → 2 月 28 日(resolvePreviousValid)
线程安全:不可变类天然线程安全(放心共享)