在 Java 8 中 常用时间日期类
在 Java 8 及以后的版本中,引入了全新的日期和时间 API(JSR 310),这些类位于 java.time
包下,提供了更简洁、更安全、更易用的日期时间处理方式。除了 LocalDate
,以下是一些常用的日期时间类及其主要功能:
1. LocalTime
表示不带时区的时间(时、分、秒、纳秒),适用于只需要时间而不关心日期的场景。
import java.time.LocalTime;
LocalTime now = LocalTime.now(); // 当前时间(例如:14:30:45.123)
LocalTime specificTime = LocalTime.of(14, 30); // 指定时间:14:30:00
LocalTime parsedTime = LocalTime.parse("14:30:45"); // 从字符串解析
// 时间运算
LocalTime later = now.plusHours(2).plusMinutes(30); // 2小时30分钟后
boolean isBefore = now.isBefore(specificTime); // 比较时间先后
2. LocalDateTime
表示不带时区的日期和时间(年、月、日、时、分、秒、纳秒),是 LocalDate
和 LocalTime
的组合。
import java.time.LocalDateTime;
LocalDateTime now = LocalDateTime.now(); // 当前日期时间
LocalDateTime specificDateTime = LocalDateTime.of(2023, 10, 15, 14, 30); // 指定日期时间
LocalDateTime parsedDateTime = LocalDateTime.parse("2023-10-15T14:30:45"); // 从ISO格式解析
// 与LocalDate/LocalTime互转
LocalDate date = now.toLocalDate(); // 提取日期部分
LocalTime time = now.toLocalTime(); // 提取时间部分
3. ZonedDateTime
表示带时区的完整日期时间,包含时区信息(ZoneId
)和偏移量(ZoneOffset
),适用于需要处理不同时区的场景。
import java.time.ZonedDateTime;
import java.time.ZoneId;
ZonedDateTime nowInUTC = ZonedDateTime.now(ZoneId.of("UTC")); // UTC时间
ZonedDateTime nowInShanghai = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); // 上海时间
// 时区转换
ZonedDateTime timeInNewYork = nowInShanghai.withZoneSameInstant(ZoneId.of("America/New_York"));
// 获取时区信息
ZoneId zone = nowInShanghai.getZone(); // Asia/Shanghai
4. OffsetDateTime
表示带时区偏移量的日期时间(如 2023-10-15T14:30:45+08:00
),但不包含时区名称(如 Asia/Shanghai
)。
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
OffsetDateTime nowWithOffset = OffsetDateTime.now(); // 当前时间+系统默认偏移量
OffsetDateTime specificOffsetTime = OffsetDateTime.of(
2023, 10, 15, 14, 30, 0, 0, ZoneOffset.of("+08:00")); // 指定偏移量
5. Instant
表示时间线上的一个点(精确到纳秒),主要用于生成时间戳(如数据库记录的时间戳)或与 java.util.Date
互转。
import java.time.Instant;
Instant now = Instant.now(); // 当前时刻(UTC时间戳)
long epochSeconds = now.getEpochSecond(); // 自1970-01-01T00:00:00Z以来的秒数
long epochMilli = now.toEpochMilli(); // 毫秒数(适用于兼容旧API)
// 与java.util.Date互转
java.util.Date legacyDate = java.util.Date.from(now);
Instant instantFromDate = legacyDate.toInstant();
6. Duration
表示两个时间点之间的差值(如 “2小时30分钟”),适用于计算时间间隔。
import java.time.Duration;
import java.time.LocalTime;
LocalTime start = LocalTime.of(10, 0);
LocalTime end = LocalTime.of(14, 30);
Duration duration = Duration.between(start, end); // PT4H30M(4小时30分钟)
long hours = duration.toHours(); // 4
long minutes = duration.toMinutes(); // 270
7. Period
表示两个日期之间的差值(如 “3年2个月15天”),适用于计算日期间隔。
import java.time.Period;
import java.time.LocalDate;
LocalDate startDate = LocalDate.of(2020, 1, 1);
LocalDate endDate = LocalDate.of(2023, 3, 16);
Period period = Period.between(startDate, endDate); // P3Y2M15D(3年2个月15天)
int years = period.getYears(); // 3
int months = period.getMonths(); // 2
int days = period.getDays(); // 15
8. ZoneId 和 ZoneOffset
ZoneId
:表示时区标识符(如Asia/Shanghai
、America/New_York
)。ZoneOffset
:表示与 UTC 的固定偏移量(如+08:00
、-05:00
)。
import java.time.ZoneId;
import java.time.ZoneOffset;
// 获取系统默认时区
ZoneId systemZone = ZoneId.systemDefault(); // Asia/Shanghai(取决于系统设置)
// 获取所有可用时区
Set<String> allZones = ZoneId.getAvailableZoneIds();
// 创建固定偏移量
ZoneOffset offset = ZoneOffset.of("+08:00");
9. DateTimeFormatter
用于日期时间的解析和格式化,替代旧的 SimpleDateFormat
,线程安全。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
LocalDateTime now = LocalDateTime.now();
// 格式化为字符串
String formatted = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); // 2023-10-15T14:30:45
String customFormat = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); // 2023-10-15 14:30:45
// 从字符串解析
LocalDateTime parsed = LocalDateTime.parse("2023-10-15 14:30:45",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
10. TemporalAdjusters
用于获取特殊日期(如当月第一天、下一个周一等)。
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
LocalDate today = LocalDate.now();
LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 当月第一天
LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); // 下一个周一
LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear()); // 当年最后一天
总结
Java 8 的日期时间 API 设计更加合理,避免了旧 API 的线程安全问题和设计缺陷。常用场景建议:
- 日期:用
LocalDate
- 时间:用
LocalTime
- 日期+时间:用
LocalDateTime
- 带时区的完整时间:用
ZonedDateTime
- 时间戳:用
Instant
- 计算间隔:用
Duration
(时间)或Period
(日期) - 格式化/解析:用
DateTimeFormatter
以下是对 Java 8 日期时间 API 的补充内容,涵盖更多实用技巧、最佳实践及注意事项:
11.
Year
、YearMonth
和MonthDay
处理特定时间单位的类:
import java.time.Year; import java.time.YearMonth; import java.time.MonthDay; // Year:表示年份 Year currentYear = Year.now(); // 2025 Year specificYear = Year.of(2024); boolean isLeapYear = specificYear.isLeap(); // 是否闰年 // YearMonth:表示年月 YearMonth currentMonth = YearMonth.now(); // 2025-05 YearMonth futureMonth = currentMonth.plusMonths(3); // 2025-08 String formatted = currentMonth.format(DateTimeFormatter.ofPattern("yyyy-MM")); // MonthDay:表示月日(如生日) MonthDay birthday = MonthDay.of(5, 12); // 05-12 boolean isBirthday = birthday.equals(MonthDay.from(LocalDate.now()));
12.
DayOfWeek
和Month
枚举增强代码可读性:
import java.time.DayOfWeek; import java.time.Month; LocalDate date = LocalDate.of(2025, 5, 12); DayOfWeek dayOfWeek = date.getDayOfWeek(); // MONDAY Month month = date.getMonth(); // MAY // 枚举值可直接用于比较或循环 if (dayOfWeek == DayOfWeek.MONDAY) { System.out.println("It's Monday!"); } // 获取月份天数 int daysInMonth = month.length(Year.of(2024).isLeap()); // 2024年2月有29天
13. 时间调整器(TemporalAdjusters)进阶
获取复杂日期:
import java.time.temporal.TemporalAdjusters; LocalDate today = LocalDate.now(); // 当月最后一个工作日(周五) LocalDate lastFriday = today.with( TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); // 下一个工作日(跳过周六、周日) LocalDate nextWorkingDay = today.with(temporal -> { DayOfWeek dow = DayOfWeek.from(temporal); int daysToAdd = switch (dow) { case FRIDAY -> 3; case SATURDAY -> 2; default -> 1; }; return temporal.plus(daysToAdd, ChronoUnit.DAYS); });
14. 日期时间计算与比较
更灵活的计算方式:
import java.time.temporal.ChronoUnit; LocalDateTime start = LocalDateTime.of(2025, 5, 1, 10, 0); LocalDateTime end = LocalDateTime.of(2025, 5, 2, 12, 30); // 计算精确差值 long hoursBetween = ChronoUnit.HOURS.between(start, end); // 26 long minutesBetween = ChronoUnit.MINUTES.between(start, end); // 1590 // 检查是否在范围内 LocalDateTime now = LocalDateTime.now(); boolean isBetween = now.isAfter(start) && now.isBefore(end); // 日期时间截断 LocalDateTime truncated = now.truncatedTo(ChronoUnit.HOURS); // 截断到小时
15. 与旧 API 的互操作性
兼容
java.util.Date
和java.util.Calendar
:import java.util.Date; import java.util.Calendar; import java.time.ZoneId; import java.time.Instant; import java.time.LocalDateTime; // Date → Instant → LocalDateTime Date legacyDate = new Date(); Instant instant = legacyDate.toInstant(); LocalDateTime dateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime(); // Calendar → ZonedDateTime Calendar calendar = Calendar.getInstance(); ZonedDateTime zonedDateTime = calendar.toInstant() .atZone(ZoneId.systemDefault()); // LocalDateTime → Date(需指定时区) LocalDateTime localDateTime = LocalDateTime.now(); Instant instantFromLocal = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); Date legacyDateFromLocal = Date.from(instantFromLocal);
16. 时区转换最佳实践
避免夏令时陷阱:
// 上海时间 → 纽约时间(保持同一时刻) ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai")); ZonedDateTime newYorkTime = shanghaiTime.withZoneSameInstant(ZoneId.of("America/New_York")); // 上海时间 → 纽约时间(保持相同钟点,日期可能变化) ZonedDateTime newYorkSameTime = shanghaiTime.withZoneSameLocal(ZoneId.of("America/New_York")); // 处理夏令时转换(自动调整时间) ZonedDateTime beforeDst = ZonedDateTime.of( 2025, 3, 8, 1, 59, 0, 0, ZoneId.of("America/New_York")); ZonedDateTime afterDst = beforeDst.plusMinutes(1); // 结果为 3:00(跳过2:00)
17. 解析非标准格式的日期时间
自定义格式器:
// 解析带中文的日期 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日"); LocalDate date = LocalDate.parse("2025年05月12日", formatter); // 解析带时区缩写的时间(需自定义解析器) DateTimeFormatter customFormatter = new DateTimeFormatterBuilder() .appendPattern("yyyy-MM-dd HH:mm:ss") .optionalStart() .appendPattern(" z") .optionalEnd() .toFormatter(); ZonedDateTime zonedDateTime = ZonedDateTime.parse("2025-05-12 14:30:00 CST", customFormatter);
18. 处理不可变对象
所有
java.time
类都是不可变的,修改操作返回新对象:LocalDate date = LocalDate.of(2025, 5, 12); // 错误:不会修改原对象 date.plusDays(1); // 无效操作 // 正确:必须接收返回值 LocalDate newDate = date.plusDays(1); // 2025-05-13
19. 线程安全性
java.time
API 是线程安全的,可在多线程环境共享:// 线程安全的格式化器 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // 多线程共享使用 ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 100; i++) { executor.submit(() -> { String formatted = LocalDate.now().format(formatter); System.out.println(formatted); }); }
20. 常见陷阱与注意事项
时区缺失问题:
// 错误:LocalDateTime 没有时区信息 LocalDateTime localDateTime = LocalDateTime.now(); // 需显式关联时区 ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
夏令时转换:
// 美国2025年夏令时开始于3月9日 ZonedDateTime invalidTime = ZonedDateTime.of( 2025, 3, 9, 2, 30, 0, 0, ZoneId.of("America/New_York")); // 抛出 DateTimeException:2:30 在当天不存在(跳过2:00)
日期时间比较:
// 错误:直接比较 LocalDateTime 可能忽略时区差异 LocalDateTime ldtShanghai = LocalDateTime.of(2025, 5, 12, 12, 0); LocalDateTime ldtNewYork = LocalDateTime.of(2025, 5, 12, 12, 0); // ldtShanghai.equals(ldtNewYork) 返回 true,但实际时刻不同 // 正确:转换为 Instant 比较 Instant instantShanghai = ldtShanghai.atZone(ZoneId.of("Asia/Shanghai")).toInstant(); Instant instantNewYork = ldtNewYork.atZone(ZoneId.of("America/New_York")).toInstant(); // instantShanghai.equals(instantNewYork) 返回 false
总结
Java 8+ 的日期时间 API 设计更符合现代编程需求,建议优先使用:
- 不可变性:所有类都是不可变的,线程安全。
- 清晰的命名:类名直观(如
LocalDate
、ZonedDateTime
)。- 丰富的 API:内置大量工具方法,减少手动计算。
- 时区支持:彻底解决旧 API 的时区问题。
避免混用新旧 API,必要时通过
Instant
或ZonedDateTime
进行转换。
在 Java 8 中,除了
LocalDate
,还有以下常用的日期时间类,它们均位于java.time
包中,具有不可变性和线程安全的特性,设计更符合现代编程需求:
1. LocalTime
• 功能:仅表示时间(时、分、秒、纳秒),不含日期信息。• 示例:
14:30:45.123
。• 常用方法:
•
now()
获取当前时间。•
of(时, 分, 秒)
创建特定时间。•
plusHours(小时)
、minusMinutes(分钟)
进行时间运算。
2. LocalDateTime
• 功能:组合日期和时间,但不包含时区信息。• 示例:
2025-03-30T14:30:45
。• 特点:
• 适用于不需要时区操作的场景,如日志记录、本地事件。
• 支持通过
LocalDate
和LocalTime
组合创建。
3. ZonedDateTime
• 功能:包含时区的完整日期时间,如2025-03-30T14:30:45+08:00[Asia/Shanghai]
。• 用途:处理跨时区应用(如航班时间、全球会议)。
• 相关类:
•
ZoneId
:表示时区标识(如Asia/Shanghai
)。•
ZoneOffset
:表示与 UTC 的固定偏移量(如+08:00
)。
4. Instant
• 功能:表示时间戳(从 1970-01-01T00:00:00Z 开始的纳秒数),用于机器时间处理。• 示例:
2025-03-30T03:01:37Z
(UTC 时间)。• 转换:可与
Date
互转(Date.toInstant()
和Date.from(Instant)
)。
5. Period 与 Duration
• Period:表示以年、月、日为单位的日期差(如计算两个LocalDate
之间相差的天数)。Period period = Period.between(startDate, endDate);
• Duration:表示以秒、纳秒为单位的时间差(如计算两个
LocalTime
之间相差的分钟数)。Duration duration = Duration.between(startTime, endTime);
6. DateTimeFormatter
• 功能:用于日期时间的格式化和解析,替代旧版SimpleDateFormat
。• 特点:线程安全,支持自定义格式(如
yyyy-MM-dd HH:mm:ss
)。DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); LocalDate date = LocalDate.parse("2025-05-12", formatter);
如何选择合适类?
• 本地操作:优先使用LocalDate
、LocalTime
、LocalDateTime
。• 时区敏感:使用
ZonedDateTime
和ZoneId
。• 时间戳处理:用
Instant
与机器时间交互。• 差值计算:
Period
(日期差)和Duration
(时间差)。这些类共同构成了 Java 8 强大的日期时间处理体系,解决了旧版
Date
和Calendar
的缺陷(如可变性、线程不安全)。