Java8:新日期时间
目录
1、旧API问题
2、核心设计理念
3、核心类和使用方法
3.1 不含时区的日期/时间
3.2 带时区的日期/时间
3.3 时间点和时间间隔
3.4 格式化与解析
4、使用技巧
5、案例
1、旧API问题
在 Java 8 之前,主要使用 java.util.Date 和 java.util.Calendar 来处理日期和时间。这些旧的 API 存在诸多问题:
-  设计缺陷: Date类既包含日期,又包含时间,但它的名字却只体现了“日期”。Calendar的设计也非常繁琐和反直觉。
-  可变性: Date和Calendar对象都是可变的,这意味着它们不是线程安全的。在多线程环境下需要额外小心。
-  时区处理困难:时区逻辑分散在 Date、Calendar和TimeZone类中,使用起来非常混乱和容易出错。
-  API 难用:简单的日期计算(如加一周、减一天)都需要通过 Calendar进行复杂操作,代码冗长且不易读。
-  格式化问题: SimpleDateFormat也是非线程安全的,同样需要小心使用。
为了解决这些问题,Java 8 借鉴了非常成功的 Joda-Time 库,并由其核心贡献者 Stephen Colebourne 主导,创建了全新的 java.time API。
2、核心设计理念
-  不可变:所有核心类都是不可变的。任何修改操作都会返回一个新的对象。这保证了线程安全,无需额外的同步。 
-  清晰的分工:将日期、时间、日期时间、时区、时刻等概念用不同的类清晰地表示。 
-  流式 API:方法调用可以像链条一样连接,使代码更流畅、更易读。 
-  基于 ISO-8601 标准:默认遵循国际标准日历系统。 
-  可扩展性:支持不同的日历系统(如日本历、泰国佛历等)。 
3、核心类和使用方法
3.1 不含时区的日期/时间
| 类 | 描述 | 示例 (格式:ISO-8601) | 
|---|---|---|
| LocalDate | 只表示日期,不包含时间和时区。 | 2023-10-27 | 
| LocalTime | 只表示时间,不包含日期和时区。 | 15:30:00.123 | 
| LocalDateTime | 表示日期和时间的组合,但不带时区信息。 | 2023-10-27T15:30:00.123 | 
// 获取当前日期时间
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now();
LocalDateTime currentDateTime = LocalDateTime.now();// 指定日期时间创建对象
LocalDate birthday = LocalDate.of(1990, Month.AUGUST, 15);
LocalTime meetingTime = LocalTime.of(14, 30); // 14:30
LocalDateTime projectDeadline = LocalDateTime.of(2023, 12, 31, 23, 59, 59);// 从字符串解析 (必须严格遵守ISO格式)
LocalDate date = LocalDate.parse("2023-10-27");
LocalDateTime dateTime = LocalDateTime.parse("2023-10-27T15:30:00");// 获取特定字段的值
int year = today.getYear();
Month month = today.getMonth(); // 返回 Month 枚举
int dayOfMonth = today.getDayOfMonth();
int hour = now.getHour();// 日期时间的加减 (返回新对象,原对象不变)
LocalDate nextWeek = today.plusWeeks(1);
LocalDate previousMonth = date.minusMonths(1);
LocalDateTime yesterdaySameTime = currentDateTime.minusDays(1);
LocalTime inAnHour = now.plusHours(1).plusMinutes(10);// 比较
boolean isAfter = birthday.isAfter(today);
boolean isBefore = meetingTime.isBefore(now);// 调整器(TemporalAdjuster)
LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());
LocalDate nextMonday = date.with(TemporalAdjusters.next(DayOfWeek.MONDAY));// 自定义调整
LocalDate nextWorkingDay = date.with(temporal -> {DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));int daysToAdd = 1;if (dow == DayOfWeek.FRIDAY) daysToAdd = 3;if (dow == DayOfWeek.SATURDAY) daysToAdd = 2;return temporal.plus(daysToAdd, ChronoUnit.DAYS);
});3.2 带时区的日期/时间
| 类 | 描述 | 
|---|---|
| ZonedDateTime | 一个带有时区的完整的日期和时间。它可以对应到时间线上的一个确定的时刻。 | 
| OffsetDateTime | 一个带有与 UTC 偏移量的日期和时间,但不包含时区规则(如夏令时)。 | 
ZonedDateTime 示例:
// 获取系统默认时区的当前时间
ZonedDateTime zonedNow = ZonedDateTime.now();// 时区ID
Set<String> availableZones = ZoneId.getAvailableZoneIds();// 获取指定时区的当前时间
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
ZonedDateTime tokyoTime = ZonedDateTime.now(tokyoZone);// 从 LocalDateTime 添加时区
LocalDateTime localDateTime = LocalDateTime.of(2023, 10, 27, 15, 30);
ZonedDateTime beijingTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));// 时区转换
ZonedDateTime newYorkTime = beijingTime.withZoneSameInstant(ZoneId.of("America/New_York"));
// 此时 newYorkTime 的时间会比 beijingTime 晚 12 小时(夏令时期间)System.out.println(beijingTime); // 2023-10-27T15:30+08:00[Asia/Shanghai]
System.out.println(newYorkTime); // 2023-10-27T03:30-04:00[America/New_York]// 偏移量时间
OffsetDateTime offsetDateTime = OffsetDateTime.now(ZoneOffset.of("+08:00"));3.3 时间点和时间间隔
| 类 | 描述 | 
|---|---|
| Instant | 时间线上的一个瞬时点(时间戳)。通常用于机器时间,表示自 1970-01-01T00:00:00Z (UTC) 开始的秒和纳秒。 | 
| Duration | 表示一个以秒和纳秒为单位的时间量。用于测量基于时间的间隔(如“2小时30分钟”)。 | 
| Period | 表示一个以年、月、日为单位的日期量。用于测量基于日期的间隔(如“3年2个月1天”)。 | 
// Instant - 机器时间
Instant instant = Instant.now(); // 获取当前UTC时刻
Instant fromEpochMilli = Instant.ofEpochMilli(System.currentTimeMillis());
Instant later = instant.plusSeconds(3600);// Duration - 时间间隔
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 30);
Duration duration = Duration.between(start, end);
System.out.println(duration.toHours()); // 8
System.out.println(duration.toMinutes()); // 510// 也可以直接创建
Duration twoHours = Duration.ofHours(2);// Period - 日期段
LocalDate startDate = LocalDate.of(2020, 1, 1);
LocalDate endDate = LocalDate.of(2023, 10, 27);
Period period = Period.between(startDate, endDate);
System.out.println(period.getYears() + "年" + period.getMonths() + "月" + period.getDays() + "天");
// 输出:3年9月26天// 也可以直接创建
Period oneWeek = Period.ofWeeks(1);3.4 格式化与解析
DateTimeFormatter 类替代了旧的 SimpleDateFormat,并且是线程安全的。
// 使用预定义的格式化器
LocalDateTime now = LocalDateTime.now();
String isoFormat = now.format(DateTimeFormatter.ISO_DATE_TIME);
String basicIsoDate = now.format(DateTimeFormatter.BASIC_ISO_DATE);// 使用自定义模式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
String customFormat = now.format(formatter); // 输出:2023年10月27日 15:30:00 周五// 从字符串解析回对象
LocalDateTime parsedDateTime = LocalDateTime.parse("2023/10/27 15:30", DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"));
 4、使用技巧
 
-  何时使用哪个类? -  如果只需要日期,用 LocalDate。
-  如果只需要时间,用 LocalTime。
-  如果需要日期和时间,但不涉及时区,用 LocalDateTime(例如,会议时间、生日)。
-  如果需要明确表示一个时间点(比如在数据库中存储时间戳),或者需要跨时区计算,用 Instant或ZonedDateTime。
-  如果需要和数据库交互,大多数 JDBC 驱动都支持 java.time类型(LocalDate,LocalTime,LocalDateTime)。
 
-  
-  与旧 API 的转换 -  新的 API 提供了 from()、to()等方法与旧的Date和Calendar进行互转。
-  例如, Date.from(instant)和Date.toInstant()。
 
-  
-  时区处理 -  始终使用 ZoneId的 region ID(如"Asia/Shanghai"),而不是固定的偏移量(如"+08:00"),因为 region ID 会自动处理夏令时等规则。
 
-  
5、案例
public class DateUtils {// 计算年龄public static int calculateAge(LocalDate birthDate) {return Period.between(birthDate, LocalDate.now()).getYears();}// 检查是否在工作时间public static boolean isWorkingHours(LocalDateTime dateTime) {LocalTime time = dateTime.toLocalTime();DayOfWeek day = dateTime.getDayOfWeek();return !day.equals(DayOfWeek.SATURDAY) && !day.equals(DayOfWeek.SUNDAY)&& time.isAfter(LocalTime.of(9, 0))&& time.isBefore(LocalTime.of(18, 0));}// 获取下个工作日public static LocalDate getNextWorkingDay(LocalDate date) {return date.with(temporal -> {// 获取星期几DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));// 默认下个工作日是明天int daysToAdd = 1;if (dow == DayOfWeek.FRIDAY) daysToAdd = 3;else if (dow == DayOfWeek.SATURDAY) daysToAdd = 2;return temporal.plus(daysToAdd, ChronoUnit.DAYS);});}
}