引言:为什么需要新的日期时间API?
如果你曾经使用过Java的java.util.Date和java.util.Calendar,一定体会过那种混乱和痛苦:月份从0开始、Date既包含日期又包含时间、SimpleDateFormat线程不安全、时区处理复杂... Java 8引入的全新日期时间API彻底解决了这些问题,带来了现代化、易于使用且线程安全的时间处理方式。
新旧API对比:一场革命
传统API的问题
java
import java.util.*;
import java.text.SimpleDateFormat;
public class LegacyDateTimeProblems {
public static void main(String[] args) throws Exception {
// 问题1: Date的月份从0开始(0=一月,11=十二月)
Date date = new Date(2023, 10, 15); // 这实际上是2023年11月15日!
System.out.println("令人困惑的月份: " + date);
// 问题2: SimpleDateFormat线程不安全
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date parsedDate = sdf.parse("2023-11-15");
System.out.println("解析后的日期: " + parsedDate);
// 问题3: Calendar操作繁琐且容易出错
Calendar calendar = Calendar.getInstance();
calendar.set(2023, Calendar.NOVEMBER, 15); // 注意:这里月份是实际的11月
calendar.add(Calendar.DAY_OF_MONTH, 7);
System.out.println("7天后: " + calendar.getTime());
// 问题4: Date同时包含日期和时间,但经常被误用
Date now = new Date();
System.out.println("Date包含时间: " + now);
// 问题5: 时区处理复杂
TimeZone tz = TimeZone.getTimeZone("America/New_York");
calendar.setTimeZone(tz);
System.out.println("纽约时间: " + calendar.getTime());
}
}
Java 8日期时间API的核心优势
- 不可变性:所有日期时间对象都是不可变的,线程安全
- 清晰的设计:分离了日期、时间、日期时间等概念
- 流畅的API:链式调用,代码更易读
- 强大的时区支持:内置完善的时区处理
- 更好的扩展性:支持自定义日历系统
核心类概览
java
Java 8日期时间API主要类:
├── 基本日期时间类
│ ├── LocalDate (日期:年-月-日)
│ ├── LocalTime (时间:时-分-秒-纳秒)
│ ├── LocalDateTime (日期时间)
│ ├── ZonedDateTime (带时区的日期时间)
│ └── OffsetDateTime (带偏移量的日期时间)
│
├── 时间点与持续时间
│ ├── Instant (时间戳,基于Unix时间)
│ ├── Duration (时间段,基于时间)
│ └── Period (时间段,基于日期)
│
├── 辅助类
│ ├── DateTimeFormatter (日期时间格式化)
│ ├── ZoneId (时区ID)
│ ├── ZoneOffset (时区偏移量)
│ └── Chronology (年表,支持不同日历系统)
│
└── 时间调整器
├── TemporalAdjuster (时间调整器)
└── TemporalAdjusters (预定义调整器)
LocalDate:处理日期
LocalDate基础操作
java
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;
public class LocalDateExample {
public static void main(String[] args) {
System.out.println("=== LocalDate 基础操作 ===");
// 1. 创建LocalDate的多种方式
LocalDate today = LocalDate.now();
LocalDate specificDate = LocalDate.of(2023, 11, 15);
LocalDate parsedDate = LocalDate.parse("2023-11-15");
System.out.println("今天: " + today);
System.out.println("指定日期: " + specificDate);
System.out.println("解析日期: " + parsedDate);
// 2. 获取日期各部分
System.out.println("\n日期分解:");
System.out.println("年: " + today.getYear());
System.out.println("月: " + today.getMonth() + " (数值: " + today.getMonthValue() + ")");
System.out.println("日: " + today.getDayOfMonth());
System.out.println("星期: " + today.getDayOfWeek());
System.out.println("一年中的第几天: " + today.getDayOfYear());
// 3. 日期运算
System.out.println("\n日期运算:");
System.out.println("明天: " + today.plusDays(1));
System.out.println("一周后: " + today.plusWeeks(1));
System.out.println("一月后: " + today.plusMonths(1));
System.out.println("一年后: " + today.plusYears(1));
System.out.println("昨天: " + today.minusDays(1));
System.out.println("一周前: " + today.minusWeeks(1));
System.out.println("一月前: " + today.minusMonths(1));
System.out.println("一年前: " + today.minusYears(1));
// 4. 日期比较
LocalDate otherDate = LocalDate.of(2023, 12, 25);
System.out.println("\n日期比较:");
System.out.println(today + " 在 " + otherDate + " 之前? " + today.isBefore(otherDate));
System.out.println(today + " 在 " + otherDate + " 之后? " + today.isAfter(otherDate));
System.out.println(today + " 等于 " + otherDate + "? " + today.isEqual(otherDate));
// 5. 特殊日期
System.out.println("\n特殊日期:");
System.out.println("是否是闰年? " + today.isLeapYear());
System.out.println("当月天数: " + today.lengthOfMonth());
System.out.println("当年天数: " + today.lengthOfYear());
// 6. 日期调整
System.out.println("\n日期调整:");
System.out.println("当月第一天: " + today.with(TemporalAdjusters.firstDayOfMonth()));
System.out.println("当月最后一天: " + today.with(TemporalAdjusters.lastDayOfMonth()));
System.out.println("下个月第一天: " + today.with(TemporalAdjusters.firstDayOfNextMonth()));
System.out.println("下个周一: " + today.with(TemporalAdjusters.next(DayOfWeek.MONDAY)));
System.out.println("当月最后一个周五: " + today.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)));
// 7. 自定义调整器
TemporalAdjuster nextWorkingDay = TemporalAdjusters.ofDateAdjuster(date -> {
DayOfWeek dayOfWeek = date.getDayOfWeek();
if (dayOfWeek == DayOfWeek.FRIDAY) {
return date.plusDays(3); // 周五到下周一
} else if (dayOfWeek == DayOfWeek.SATURDAY) {
return date.plusDays(2); // 周六到下周一
} else {
return date.plusDays(1); // 其他日子到明天
}
});
System.out.println("下一个工作日: " + today.with(nextWorkingDay));
}
// 实际应用:计算年龄
public static int calculateAge(LocalDate birthDate) {
return Period.between(birthDate, LocalDate.now()).getYears();
}
// 实际应用:计算工作日
public static long calculateWorkingDays(LocalDate start, LocalDate end) {
return start.datesUntil(end.plusDays(1))
.filter(date -> date.getDayOfWeek() != DayOfWeek.SATURDAY &&
date.getDayOfWeek() != DayOfWeek.SUNDAY)
.count();
}
// 实际应用:生成日期范围
public static List<LocalDate> generateDateRange(LocalDate start, LocalDate end, int stepDays) {
List<LocalDate> dates = new ArrayList<>();
LocalDate current = start;
while (!current.isAfter(end)) {
dates.add(current);
current = current.plusDays(stepDays);
}
return dates;
}
}
LocalTime:处理时间
LocalTime基础操作
java
import java.time.*;
import java.time.temporal.ChronoUnit;
public class LocalTimeExample {
public static void main(String[] args) {
System.out.println("=== LocalTime 基础操作 ===");
// 1. 创建LocalTime的多种方式
LocalTime now = LocalTime.now();
LocalTime specificTime = LocalTime.of(14, 30, 45); // 14:30:45
LocalTime parsedTime = LocalTime.parse("09:15:30");
System.out.println("现在时间: " + now);
System.out.println("指定时间: " + specificTime);
System.out.println("解析时间: " + parsedTime);
// 2. 获取时间各部分
System.out.println("\n时间分解:");
System.out.println("小时: " + now.getHour());
System.out.println("分钟: " + now.getMinute());
System.out.println("秒: " + now.getSecond());
System.out.println("纳秒: " + now.getNano());
// 3. 时间运算
System.out.println("\n时间运算:");
System.out.println("1小时后: " + now.plusHours(1));
System.out.println("30分钟后: " + now.plusMinutes(30));
System.out.println("15秒后: " + now.plusSeconds(15));
System.out.println("100纳秒后: " + now.plusNanos(100));
System.out.println("2小时前: " + now.minusHours(2));
System.out.println("45分钟前: " + now.minusMinutes(45));
System.out.println("30秒前: " + now.minusSeconds(30));
// 使用ChronoUnit进行更灵活的时间运算
System.out.println("半小时后: " + now.plus(30, ChronoUnit.MINUTES));
System.out.println("三刻钟后: " + now.plus(3, ChronoUnit.HALF_HOURS));
// 4. 时间比较
LocalTime otherTime = LocalTime.of(18, 0, 0);
System.out.println("\n时间比较:");
System.out.println(now + " 在 " + otherTime + " 之前? " + now.isBefore(otherTime));
System.out.println(now + " 在 " + otherTime + " 之后? " + now.isAfter(otherTime));
// 5. 时间调整
System.out.println("\n时间调整:");
System.out.println("调整到整点: " + now.withMinute(0).withSecond(0).withNano(0));
System.out.println("调整到下一小时: " + now.withHour((now.getHour() + 1) % 24));
// 6. 获取最大/最小时间
System.out.println("\n特殊时间:");
System.out.println("最小时间: " + LocalTime.MIN);
System.out.println("最大时间: " + LocalTime.MAX);
System.out.println("午夜: " + LocalTime.MIDNIGHT);
System.out.println("中午: " + LocalTime.NOON);
// 7. 时间范围检查
System.out.println("\n时间范围检查:");
LocalTime startWork = LocalTime.of(9, 0);
LocalTime endWork = LocalTime.of(18, 0);
System.out.println(now + " 是否在工作时间内? " +
(now.isAfter(startWork) && now.isBefore(endWork)));
// 8. 时间差计算
LocalTime start = LocalTime.of(9, 0);
LocalTime end = LocalTime.of(17, 30);
System.out.println("\n时间差计算:");
long hoursBetween = ChronoUnit.HOURS.between(start, end);
long minutesBetween = ChronoUnit.MINUTES.between(start, end);
System.out.println(start + " 到 " + end + " 相差 " + hoursBetween + " 小时");
System.out.println(start + " 到 " + end + " 相差 " + minutesBetween + " 分钟");
}
// 实际应用:计算会议持续时间
public static Duration calculateMeetingDuration(LocalTime startTime, LocalTime endTime) {
return Duration.between(startTime, endTime);
}
// 实际应用:检查是否在营业时间
public static boolean isBusinessHour(LocalTime time, LocalTime open, LocalTime close) {
return !time.isBefore(open) && !time.isAfter(close);
}
// 实际应用:生成时间表
public static List<LocalTime> generateTimeSchedule(LocalTime start, int intervalMinutes, int count) {
List<LocalTime> schedule = new ArrayList<>();
LocalTime current = start;
for (int i = 0; i < count; i++) {
schedule.add(current);
current = current.plusMinutes(intervalMinutes);
}
return schedule;
}
}
LocalDateTime:日期时间组合
LocalDateTime基础操作
java
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;
public class LocalDateTimeExample {
public static void main(String[] args) {
System.out.println("=== LocalDateTime 基础操作 ===");
// 1. 创建LocalDateTime的多种方式
LocalDateTime now = LocalDateTime.now();
LocalDateTime specificDateTime = LocalDateTime.of(2023, 11, 15, 14, 30, 45);
LocalDateTime parsedDateTime = LocalDateTime.parse("2023-11-15T14:30:45");
LocalDateTime fromDateAndTime = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println("现在: " + now);
System.out.println("指定日期时间: " + specificDateTime);
System.out.println("解析日期时间: " + parsedDateTime);
System.out.println("从日期和时间组合: " + fromDateAndTime);
// 2. 转换操作
System.out.println("\n转换操作:");
LocalDate datePart = now.toLocalDate();
LocalTime timePart = now.toLocalTime();
System.out.println("日期部分: " + datePart);
System.out.println("时间部分: " + timePart);
// 3. 日期时间运算
System.out.println("\n日期时间运算:");
System.out.println("3天后: " + now.plusDays(3));
System.out.println("2周后: " + now.plusWeeks(2));
System.out.println("6个月后: " + now.plusMonths(6));
System.out.println("1年2个月3天后: " + now.plusYears(1).plusMonths(2).plusDays(3));
System.out.println("5小时前: " + now.minusHours(5));
System.out.println("30分钟前: " + now.minusMinutes(30));
// 4. 日期时间比较
LocalDateTime futureDateTime = LocalDateTime.of(2024, 1, 1, 0, 0, 0);
System.out.println("\n日期时间比较:");
System.out.println(now + " 在 " + futureDateTime + " 之前? " + now.isBefore(futureDateTime));
System.out.println(now + " 在 " + futureDateTime + " 之后? " + now.isAfter(futureDateTime));
// 5. 日期时间调整
System.out.println("\n日期时间调整:");
System.out.println("调整到当月第一天: " + now.with(TemporalAdjusters.firstDayOfMonth()));
System.out.println("调整到下个周一上午9点: " +
now.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).withHour(9).withMinute(0));
// 6. 格式化与解析
System.out.println("\n格式化与解析:");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String formatted = now.format(formatter);
System.out.println("格式化后: " + formatted);
LocalDateTime parsed = LocalDateTime.parse("2023年11月15日 14:30:00", formatter);
System.out.println("解析后: " + parsed);
// 7. 自定义格式化器
System.out.println("\n自定义格式化:");
DateTimeFormatter customFormatter = new DateTimeFormatterBuilder()
.appendPattern("yyyy/MM/dd")
.appendLiteral(" ")
.appendPattern("HH:mm")
.toFormatter();
System.out.println("自定义格式: " + now.format(customFormatter));
// 8. 与时间戳的转换
System.out.println("\n与时间戳转换:");
Instant instant = now.atZone(ZoneId.systemDefault()).toInstant();
long epochMilli = instant.toEpochMilli();
System.out.println("时间戳: " + epochMilli);
LocalDateTime fromTimestamp = LocalDateTime.ofInstant(
Instant.ofEpochMilli(epochMilli),
ZoneId.systemDefault()
);
System.out.println("从时间戳恢复: " + fromTimestamp);
}
// 实际应用:计算项目截止日期
public static LocalDateTime calculateDeadline(LocalDateTime startDate, int workingDays) {
LocalDateTime deadline = startDate;
int daysAdded = 0;
while (daysAdded < workingDays) {
deadline = deadline.plusDays(1);
DayOfWeek dayOfWeek = deadline.getDayOfWeek();
if (dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY) {
daysAdded++;
}
}
return deadline;
}
// 实际应用:检查是否在维护窗口
public static boolean isInMaintenanceWindow(LocalDateTime dateTime) {
LocalTime start = LocalTime.of(2, 0); // 凌晨2点
LocalTime end = LocalTime.of(4, 0); // 凌晨4点
DayOfWeek day = dateTime.getDayOfWeek();
// 假设每周日2-4点维护
return day == DayOfWeek.SUNDAY &&
!dateTime.toLocalTime().isBefore(start) &&
dateTime.toLocalTime().isBefore(end);
}
// 实际应用:生成时间轴
public static Map<LocalDateTime, String> generateTimeline(LocalDateTime start, int intervalHours, int count) {
Map<LocalDateTime, String> timeline = new LinkedHashMap<>();
LocalDateTime current = start;
for (int i = 0; i < count; i++) {
timeline.put(current, "事件 " + (i + 1));
current = current.plusHours(intervalHours);
}
return timeline;
}
}
时区处理:ZonedDateTime和OffsetDateTime
时区处理实战
java
import java.time.*;
import java.time.format.*;
import java.util.*;
public class TimeZoneExample {
public static void main(String[] args) {
System.out.println("=== 时区处理实战 ===");
// 1. 时区基础
System.out.println("\n1. 时区基础:");
Set<String> allZoneIds = ZoneId.getAvailableZoneIds();
System.out.println("可用时区数量: " + allZoneIds.size());
System.out.println("前10个时区:");
allZoneIds.stream().sorted().limit(10).forEach(System.out::println);
// 2. 创建带时区的时间
System.out.println("\n2. 创建带时区的时间:");
ZonedDateTime nowInTokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
ZonedDateTime nowInNewYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime nowInLondon = ZonedDateTime.now(ZoneId.of("Europe/London"));
System.out.println("东京时间: " + nowInTokyo);
System.out.println("纽约时间: " + nowInNewYork);
System.out.println("伦敦时间: " + nowInLondon);
// 3. 时区转换
System.out.println("\n3. 时区转换:");
ZonedDateTime tokyoTime = ZonedDateTime.of(
LocalDateTime.of(2023, 11, 15, 14, 30),
ZoneId.of("Asia/Tokyo")
);
ZonedDateTime newYorkTime = tokyoTime.withZoneSameInstant(ZoneId.of("America/New_York"));
ZonedDateTime londonTime = tokyoTime.withZoneSameInstant(ZoneId.of("Europe/London"));
System.out.println("东京 14:30 相当于:");
System.out.println(" 纽约: " + newYorkTime.toLocalDateTime() + " (" + newYorkTime.getZone() + ")");
System.out.println(" 伦敦: " + londonTime.toLocalDateTime() + " (" + londonTime.getZone() + ")");
// 4. 夏令时处理
System.out.println("\n4. 夏令时测试:");
testDaylightSavingTime();
// 5. OffsetDateTime(固定时区偏移)
System.out.println("\n5. OffsetDateTime:");
OffsetDateTime offsetDateTime = OffsetDateTime.now(ZoneOffset.ofHours(8));
System.out.println("UTC+8时间: " + offsetDateTime);
// 6. 时区偏移计算
System.out.println("\n6. 时区偏移计算:");
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
ZoneId newYorkZone = ZoneId.of("America/New_York");
Instant instant = Instant.now();
ZoneOffset tokyoOffset = tokyoZone.getRules().getOffset(instant);
ZoneOffset newYorkOffset = newYorkZone.getRules().getOffset(instant);
System.out.println("东京时区偏移: " + tokyoOffset);
System.out.println("纽约时区偏移: " + newYorkOffset);
System.out.println("时差: " +
Duration.between(
instant.atOffset(tokyoOffset).toLocalTime(),
instant.atOffset(newYorkOffset).toLocalTime()
).toHours() + " 小时"
);
// 7. 实际应用:全球会议时间
System.out.println("\n7. 实际应用:全球会议时间安排:");
scheduleGlobalMeeting();
}
// 测试夏令时
public static void testDaylightSavingTime() {
ZoneId nyZone = ZoneId.of("America/New_York");
// 纽约2023年夏令时开始于3月12日
LocalDateTime beforeDST = LocalDateTime.of(2023, 3, 12, 1, 30);
LocalDateTime afterDST = LocalDateTime.of(2023, 3, 12, 3, 30);
ZonedDateTime zonedBefore = ZonedDateTime.of(beforeDST, nyZone);
ZonedDateTime zonedAfter = ZonedDateTime.of(afterDST, nyZone);
System.out.println("夏令时变化前: " + zonedBefore);
System.out.println("夏令时变化后: " + zonedAfter);
System.out.println("是否在夏令时中: " + zonedAfter.getZone().getRules().isDaylightSavings(zonedAfter.toInstant()));
// 检查转换
System.out.println("\n验证转换:");
System.out.println("转换到伦敦时间:");
System.out.println(" 前: " + zonedBefore.withZoneSameInstant(ZoneId.of("Europe/London")));
System.out.println(" 后: " + zonedAfter.withZoneSameInstant(ZoneId.of("Europe/London")));
}
// 安排全球会议
public static void scheduleGlobalMeeting() {
System.out.println("=== 全球会议安排 ===");
// 假设会议在旧金山时间上午9点
LocalDateTime meetingTime = LocalDateTime.of(2023, 11, 15, 9, 0);
ZonedDateTime meetingSF = ZonedDateTime.of(meetingTime, ZoneId.of("America/Los_Angeles"));
System.out.println("会议时间(旧金山): " + meetingSF);
// 转换到其他城市
String[] cities = {
"Asia/Shanghai", // 上海
"Asia/Tokyo", // 东京
"Europe/London", // 伦敦
"Europe/Paris", // 巴黎
"America/New_York", // 纽约
"Australia/Sydney" // 悉尼
};
System.out.println("\n各城市会议时间:");
for (String city : cities) {
ZonedDateTime cityTime = meetingSF.withZoneSameInstant(ZoneId.of(city));
System.out.printf("%-20s: %s%n",
city.split("/")[1],
cityTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm (zzz)"))
);
}
}
// 计算飞行时间(考虑时区)
public static Duration calculateFlightDuration(ZonedDateTime departure, ZonedDateTime arrival) {
return Duration.between(departure, arrival);
}
// 检查是否在营业时间内(考虑时区)
public static boolean isBusinessHourGlobal(ZonedDateTime time, ZoneId businessZone,
LocalTime open, LocalTime close) {
ZonedDateTime businessZoneTime = time.withZoneSameInstant(businessZone);
LocalTime localTime = businessZoneTime.toLocalTime();
return !localTime.isBefore(open) && !localTime.isAfter(close);
}
}
Instant和Duration:时间点与时间段
Instant和Duration实战
java
import java.time.*;
import java.time.temporal.*;
import java.util.concurrent.TimeUnit;
public class InstantDurationExample {
public static void main(String[] args) throws Exception {
System.out.println("=== Instant 和 Duration 实战 ===");
// 1. Instant:时间点(时间戳)
System.out.println("\n1. Instant - 时间点:");
Instant now = Instant.now();
Instant specificInstant = Instant.ofEpochMilli(1699999999999L);
Instant parsedInstant = Instant.parse("2023-11-15T14:30:45.123Z");
System.out.println("当前时刻: " + now);
System.out.println("指定时间戳: " + specificInstant);
System.out.println("解析的ISO格式: " + parsedInstant);
// 获取时间戳
System.out.println("\n时间戳获取:");
System.out.println("毫秒时间戳: " + now.toEpochMilli());
System.out.println("秒时间戳: " + now.getEpochSecond());
System.out.println("纳秒部分: " + now.getNano());
// 2. Instant运算
System.out.println("\n2. Instant运算:");
System.out.println("1小时后: " + now.plus(1, ChronoUnit.HOURS));
System.out.println("30分钟后: " + now.plus(30, ChronoUnit.MINUTES));
System.out.println("1天前: " + now.minus(1, ChronoUnit.DAYS));
System.out.println("500毫秒前: " + now.minus(500, ChronoUnit.MILLIS));
// 3. Duration:时间段(基于时间)
System.out.println("\n3. Duration - 时间段:");
Duration oneHour = Duration.ofHours(1);
Duration thirtyMinutes = Duration.ofMinutes(30);
Duration customDuration = Duration.of(90, ChronoUnit.SECONDS);
Duration parsedDuration = Duration.parse("PT1H30M"); // ISO-8601格式
System.out.println("1小时: " + oneHour);
System.out.println("30分钟: " + thirtyMinutes);
System.out.println("90秒: " + customDuration);
System.out.println("解析1小时30分钟: " + parsedDuration);
// 4. Duration运算
System.out.println("\n4. Duration运算:");
System.out.println("1小时 + 30分钟 = " + oneHour.plus(thirtyMinutes));
System.out.println("2小时 - 30分钟 = " + Duration.ofHours(2).minus(thirtyMinutes));
System.out.println("30分钟 × 2 = " + thirtyMinutes.multipliedBy(2));
System.out.println("1小时 ÷ 2 = " + oneHour.dividedBy(2));
// 5. 获取Duration各部分
System.out.println("\n5. Duration分解:");
Duration complexDuration = Duration.ofHours(25).plusMinutes(90);
System.out.println("25小时90分钟分解:");
System.out.println(" 总天数: " + complexDuration.toDays());
System.out.println(" 总小时数: " + complexDuration.toHours());
System.out.println(" 总分钟数: " + complexDuration.toMinutes());
System.out.println(" 总秒数: " + complexDuration.toSeconds());
System.out.println(" 总毫秒数: " + complexDuration.toMillis());
System.out.println(" 总纳秒数: " + complexDuration.toNanos());
System.out.println("\n各部分:");
System.out.println(" 小时部分: " + complexDuration.toHoursPart());
System.out.println(" 分钟部分: " + complexDuration.toMinutesPart());
System.out.println(" 秒部分: " + complexDuration.toSecondsPart());
// 6. Period:时间段(基于日期)
System.out.println("\n6. Period - 基于日期的时间段:");
Period oneYear = Period.ofYears(1);
Period sixMonths = Period.ofMonths(6);
Period complexPeriod = Period.of(1, 6, 15); // 1年6个月15天
Period parsedPeriod = Period.parse("P1Y6M15D"); // ISO-8601格式
System.out.println("1年: " + oneYear);
System.out.println("6个月: " + sixMonths);
System.out.println("1年6个月15天: " + complexPeriod);
System.out.println("解析的Period: " + parsedPeriod);
// 7. 实际应用:计算两个日期之间的时间段
System.out.println("\n7. 实际应用:计算时间段:");
LocalDate birthDate = LocalDate.of(1990, 5, 15);
LocalDate today = LocalDate.now();
Period age = Period.between(birthDate, today);
System.out.println("年龄: " + age.getYears() + "年 " +
age.getMonths() + "个月 " +
age.getDays() + "天");
// 8. 实际应用:性能测量
System.out.println("\n8. 性能测量示例:");
measurePerformance();
// 9. 实际应用:超时控制
System.out.println("\n9. 超时控制示例:");
timeoutControl();
}
// 性能测量
public static void measurePerformance() {
Instant start = Instant.now();
// 模拟耗时操作
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Instant end = Instant.now();
Duration elapsed = Duration.between(start, end);
System.out.println("操作耗时: " + elapsed.toMillis() + " 毫秒");
System.out.println("操作耗时: " + elapsed.getSeconds() + " 秒 " +
elapsed.toMillisPart() + " 毫秒");
// 格式化输出
if (elapsed.toMinutes() > 0) {
System.out.printf("格式化: %d分 %d秒 %d毫秒%n",
elapsed.toMinutes(),
elapsed.toSecondsPart(),
elapsed.toMillisPart());
} else {
System.out.printf("格式化: %d秒 %d毫秒%n",
elapsed.toSeconds(),
elapsed.toMillisPart());
}
}
// 超时控制
public static void timeoutControl() {
Duration timeout = Duration.ofSeconds(3);
Instant startTime = Instant.now();
System.out.println("开始执行,超时时间: " + timeout.getSeconds() + "秒");
try {
// 模拟长时间操作
for (int i = 1; i <= 10; i++) {
// 检查是否超时
Duration elapsed = Duration.between(startTime, Instant.now());
if (elapsed.compareTo(timeout) > 0) {
System.out.println("操作超时!");
break;
}
System.out.println("第 " + i + " 步");
TimeUnit.MILLISECONDS.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
Duration totalTime = Duration.between(startTime, Instant.now());
System.out.println("总耗时: " + totalTime.toMillis() + "毫秒");
}
// 计算两个时刻之间的精确时间段
public static String formatDurationBetween(Instant start, Instant end) {
Duration duration = Duration.between(start, end);
long days = duration.toDays();
long hours = duration.toHoursPart();
long minutes = duration.toMinutesPart();
long seconds = duration.toSecondsPart();
long millis = duration.toMillisPart();
StringBuilder sb = new StringBuilder();
if (days > 0) sb.append(days).append("天 ");
if (hours > 0 || days > 0) sb.append(hours).append("小时 ");
if (minutes > 0 || hours > 0 || days > 0) sb.append(minutes).append("分 ");
sb.append(seconds).append("秒 ");
sb.append(millis).append("毫秒");
return sb.toString();
}
// 计算工作日的持续时间
public static Duration calculateWorkingDuration(LocalDateTime start, LocalDateTime end) {
Duration totalDuration = Duration.between(start, end);
// 计算包含的周末天数
long weekendDays = 0;
LocalDate currentDate = start.toLocalDate();
LocalDate endDate = end.toLocalDate();
while (!currentDate.isAfter(endDate)) {
DayOfWeek dayOfWeek = currentDate.getDayOfWeek();
if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) {
weekendDays++;
}
currentDate = currentDate.plusDays(1);
}
// 减去周末的时间
Duration weekendDuration = Duration.ofDays(weekendDays);
return totalDuration.minus(weekendDuration);
}
}
格式化与解析
高级格式化与解析
java
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;
import java.util.Locale;
public class DateTimeFormatting {
public static void main(String[] args) {
System.out.println("=== 日期时间格式化与解析 ===");
LocalDateTime now = LocalDateTime.now();
// 1. 预定义的格式化器
System.out.println("\n1. 预定义的格式化器:");
System.out.println("ISO格式: " + now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
System.out.println("ISO日期: " + now.format(DateTimeFormatter.ISO_LOCAL_DATE));
System.out.println("ISO时间: " + now.format(DateTimeFormatter.ISO_LOCAL_TIME));
// 2. 自定义模式
System.out.println("\n2. 自定义模式:");
DateTimeFormatter[] formatters = {
DateTimeFormatter.ofPattern("yyyy-MM-dd"),
DateTimeFormatter.ofPattern("dd/MM/yyyy"),
DateTimeFormatter.ofPattern("yyyy年MM月dd日"),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"),
DateTimeFormatter.ofPattern("EEEE, MMMM dd, yyyy"), // 星期,月份,日,年
DateTimeFormatter.ofPattern("hh:mm a"), // 12小时制
DateTimeFormatter.ofPattern("yyyy年 第Q季度"), // 季度
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX") // 带时区偏移
};
for (DateTimeFormatter formatter : formatters) {
System.out.println(formatter.format(now) + " <- " +
formatter.toString().replaceAll(".*\[", "["));
}
// 3. 本地化格式化
System.out.println("\n3. 本地化格式化:");
Locale[] locales = {Locale.US, Locale.UK, Locale.GERMANY, Locale.JAPAN, Locale.CHINA};
DateTimeFormatter localizedFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
for (Locale locale : locales) {
DateTimeFormatter localeSpecific = localizedFormatter.withLocale(locale);
System.out.println(locale.getDisplayName() + ": " + localeSpecific.format(now));
}
// 4. 格式化器构建器(复杂格式化)
System.out.println("\n4. 格式化器构建器:");
DateTimeFormatter complexFormatter = new DateTimeFormatterBuilder()
.appendLiteral("日期: ")
.appendValue(ChronoField.YEAR, 4)
.appendLiteral("年")
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
.appendLiteral("月")
.appendValue(ChronoField.DAY_OF_MONTH, 2)
.appendLiteral("日")
.appendLiteral(" 时间: ")
.appendValue(ChronoField.HOUR_OF_DAY, 2)
.appendLiteral(":")
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
.appendLiteral(":")
.appendValue(ChronoField.SECOND_OF_MINUTE, 2)
.optionalStart() // 可选部分
.appendLiteral(".")
.appendValue(ChronoField.MILLI_OF_SECOND, 3)
.optionalEnd()
.toFormatter();
System.out.println("复杂格式化: " + complexFormatter.format(now));
// 5. 解析日期时间
System.out.println("\n5. 解析日期时间:");
String[] dateStrings = {
"2023-11-15",
"15/11/2023",
"2023年11月15日",
"2023-11-15 14:30:45",
"November 15, 2023"
};
DateTimeFormatter[] parsers = {
DateTimeFormatter.ofPattern("yyyy-MM-dd"),
DateTimeFormatter.ofPattern("dd/MM/yyyy"),
DateTimeFormatter.ofPattern("yyyy年MM月dd日"),
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
DateTimeFormatter.ofPattern("MMMM dd, yyyy", Locale.US)
};
for (int i = 0; i < dateStrings.length; i++) {
try {
TemporalAccessor parsed = parsers[i].parse(dateStrings[i]);
System.out.println(dateStrings[i] + " -> 解析成功");
} catch (DateTimeParseException e) {
System.out.println(dateStrings[i] + " -> 解析失败: " + e.getMessage());
}
}
// 6. 宽松解析与严格解析
System.out.println("\n6. 宽松解析与严格解析:");
String looseDate = "2023-2-31"; // 2月31日不存在
DateTimeFormatter lenientFormatter = DateTimeFormatter.ofPattern("yyyy-M-d")
.withResolverStyle(ResolverStyle.LENIENT);
DateTimeFormatter strictFormatter = DateTimeFormatter.ofPattern("yyyy-M-d")
.withResolverStyle(ResolverStyle.STRICT);
try {
LocalDate parsedLenient = LocalDate.parse(looseDate, lenientFormatter);
System.out.println("宽松解析成功: " + parsedLenient); // 会自动调整到3月3日
} catch (DateTimeParseException e) {
System.out.println("宽松解析失败: " + e.getMessage());
}
try {
LocalDate parsedStrict = LocalDate.parse(looseDate, strictFormatter);
System.out.println("严格解析成功: " + parsedStrict);
} catch (DateTimeParseException e) {
System.out.println("严格解析失败: " + e.getMessage());
}
// 7. 实际应用:多格式解析器
System.out.println("\n7. 多格式解析器:");
String[] possibleFormats = {
"yyyy-MM-dd",
"yyyy/MM/dd",
"dd-MM-yyyy",
"dd/MM/yyyy",
"yyyy.MM.dd",
"MMM dd, yyyy",
"dd MMM yyyy"
};
String testDate = "15/11/2023";
LocalDate result = parseWithMultipleFormats(testDate, possibleFormats);
System.out.println("多格式解析结果: " + result);
// 8. 自定义格式化扩展
System.out.println("\n8. 自定义格式化扩展:");
DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
.withZone(ZoneId.of("Asia/Shanghai"))
.withDecimalStyle(DecimalStyle.STANDARD)
.withChronology(IsoChronology.INSTANCE);
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println("带时区格式化: " + customFormatter.format(zonedDateTime));
}
// 多格式解析器实现
public static LocalDate parseWithMultipleFormats(String dateString, String[] formats) {
for (String format : formats) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
return LocalDate.parse(dateString, formatter);
} catch (DateTimeParseException e) {
// 继续尝试下一个格式
continue;
}
}
throw new IllegalArgumentException("无法解析日期: " + dateString);
}
// 智能日期解析
public static LocalDate smartDateParse(String input) {
// 移除常见的分隔符,统一处理
String normalized = input.replaceAll("[/.-]", "-");
// 尝试常见格式
String[] patterns = {
"yyyy-MM-dd",
"yyyy-M-d",
"yy-MM-dd",
"yy-M-d",
"MM-dd-yyyy",
"M-d-yyyy",
"dd-MM-yyyy",
"d-M-yyyy"
};
for (String pattern : patterns) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return LocalDate.parse(normalized, formatter);
} catch (DateTimeParseException e) {
continue;
}
}
// 尝试文本月份
patterns = new String[]{
"MMM dd, yyyy",
"MMM d, yyyy",
"MMMM dd, yyyy",
"MMMM d, yyyy",
"dd MMM yyyy",
"d MMM yyyy"
};
for (String pattern : patterns) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern, Locale.ENGLISH);
return LocalDate.parse(input, formatter);
} catch (DateTimeParseException e) {
continue;
}
}
throw new IllegalArgumentException("无法识别的日期格式: " + input);
}
// 生成可读的时间间隔描述
public static String formatTimeAgo(LocalDateTime pastTime) {
Duration duration = Duration.between(pastTime, LocalDateTime.now());
if (duration.toDays() > 365) {
long years = duration.toDays() / 365;
return years + "年前";
} else if (duration.toDays() > 30) {
long months = duration.toDays() / 30;
return months + "个月前";
} else if (duration.toDays() > 0) {
return duration.toDays() + "天前";
} else if (duration.toHours() > 0) {
return duration.toHours() + "小时前";
} else if (duration.toMinutes() > 0) {
return duration.toMinutes() + "分钟前";
} else {
return "刚刚";
}
}
// 生成日历视图
public static void printCalendar(int year, int month) {
LocalDate firstDay = LocalDate.of(year, month, 1);
DayOfWeek firstDayOfWeek = firstDay.getDayOfWeek();
int daysInMonth = firstDay.lengthOfMonth();
System.out.printf("\n %d年 %d月\n", year, month);
System.out.println("日 一 二 三 四 五 六");
// 打印前面的空白
for (int i = 0; i < firstDayOfWeek.getValue() % 7; i++) {
System.out.print(" ");
}
// 打印日期
for (int day = 1; day <= daysInMonth; day++) {
System.out.printf("%2d ", day);
// 换行
if ((firstDayOfWeek.getValue() % 7 + day) % 7 == 0) {
System.out.println();
}
}
System.out.println();
}
}
实战:完整的企业级应用
java
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;
import java.util.*;
import java.util.concurrent.*;
public class EnterpriseDateTimeApplication {
// 1. 航班预订系统
static class FlightBookingSystem {
static class Flight {
String flightNumber;
String airline;
String departureAirport;
String arrivalAirport;
ZonedDateTime departureTime;
ZonedDateTime arrivalTime;
Duration flightDuration;
public Flight(String flightNumber, String airline, String departureAirport,
String arrivalAirport, ZonedDateTime departureTime,
ZonedDateTime arrivalTime) {
this.flightNumber = flightNumber;
this.airline = airline;
this.departureAirport = departureAirport;
this.arrivalAirport = arrivalAirport;
this.departureTime = departureTime;
this.arrivalTime = arrivalTime;
this.flightDuration = Duration.between(departureTime, arrivalTime);
}
public boolean isOverlapping(Flight other) {
return !(this.arrivalTime.isBefore(other.departureTime) ||
this.departureTime.isAfter(other.arrivalTime));
}
public Duration getLayoverTime(Flight connectingFlight) {
if (!this.arrivalAirport.equals(connectingFlight.departureAirport)) {
throw new IllegalArgumentException("不是连续的航班");
}
return Duration.between(this.arrivalTime, connectingFlight.departureTime);
}
@Override
public String toString() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm (zzz)");
return String.format("%s %s: %s [%s] -> %s [%s] 时长: %s",
airline, flightNumber,
departureTime.format(formatter),
departureAirport,
arrivalTime.format(formatter),
arrivalAirport,
formatDuration(flightDuration));
}
private String formatDuration(Duration duration) {
long hours = duration.toHours();
long minutes = duration.toMinutesPart();
return String.format("%d小时%d分钟", hours, minutes);
}
}
public static void bookFlight() {
System.out.println("=== 航班预订系统 ===");
// 定义航班
Flight flight1 = new Flight(
"CA123",
"中国航空",
"北京首都国际机场 (PEK)",
"上海浦东国际机场 (PVG)",
ZonedDateTime.of(2023, 12, 1, 8, 0, 0, 0, ZoneId.of("Asia/Shanghai")),
ZonedDateTime.of(2023, 12, 1, 10, 30, 0, 0, ZoneId.of("Asia/Shanghai"))
);
Flight flight2 = new Flight(
"AA789",
"美国航空",
"上海浦东国际机场 (PVG)",
"纽约肯尼迪机场 (JFK)",
ZonedDateTime.of(2023, 12, 1, 13, 30, 0, 0, ZoneId.of("Asia/Shanghai")),
ZonedDateTime.of(2023, 12, 1, 16, 0, 0, 0, ZoneId.of("America/New_York"))
);
System.out.println("航班1: " + flight1);
System.out.println("航班2: " + flight2);
// 检查转机时间
Duration layover = flight1.getLayoverTime(flight2);
System.out.println("\n转机时间: " +
layover.toHours() + "小时" + layover.toMinutesPart() + "分钟");
if (layover.toHours() < 1) {
System.out.println("⚠️ 警告:转机时间太短!");
} else if (layover.toHours() > 4) {
System.out.println("⚠️ 警告:转机时间太长!");
} else {
System.out.println("✓ 转机时间合适");
}
// 显示到达目的地的时间(按目的地时区)
ZonedDateTime arrivalInLocalTime = flight2.arrivalTime
.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("\n到达纽约时间: " +
arrivalInLocalTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm (zzz)")));
}
}
// 2. 会议调度系统
static class MeetingScheduler {
static class Meeting {
String title;
String organizer;
ZonedDateTime startTime;
Duration duration;
Set<String> participants;
String location;
public Meeting(String title, String organizer, ZonedDateTime startTime,
Duration duration, Set<String> participants, String location) {
this.title = title;
this.organizer = organizer;
this.startTime = startTime;
this.duration = duration;
this.participants = participants;
this.location = location;
}
public ZonedDateTime getEndTime() {
return startTime.plus(duration);
}
public boolean conflictsWith(Meeting other) {
return !(this.getEndTime().isBefore(other.startTime) ||
this.startTime.isAfter(other.getEndTime()));
}
public Map<String, ZonedDateTime> getLocalTimesForParticipants() {
Map<String, ZonedDateTime> localTimes = new HashMap<>();
// 模拟从数据库获取参与者时区
Map<String, String> participantTimezones = Map.of(
"张三", "Asia/Shanghai",
"李四", "America/New_York",
"王五", "Europe/London",
"山田", "Asia/Tokyo"
);
for (String participant : participants) {
String timezone = participantTimezones.getOrDefault(participant, "UTC");
localTimes.put(participant,
startTime.withZoneSameInstant(ZoneId.of(timezone)));
}
return localTimes;
}
@Override
public String toString() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm (zzz)");
return String.format("会议: %s\n组织者: %s\n时间: %s - %s\n地点: %s\n参与者: %s",
title, organizer,
startTime.format(formatter),
getEndTime().format(formatter),
location,
String.join(", ", participants));
}
}
public static void scheduleMeeting() {
System.out.println("\n=== 会议调度系统 ===");
Set<String> participants = new HashSet<>(Arrays.asList(
"张三", "李四", "王五", "山田"
));
Meeting meeting = new Meeting(
"季度项目评审",
"张三",
ZonedDateTime.of(2023, 12, 15, 14, 0, 0, 0, ZoneId.of("Asia/Shanghai")),
Duration.ofHours(2),
participants,
"会议室A"
);
System.out.println(meeting);
// 显示各参与者的本地时间
System.out.println("\n各参与者的本地时间:");
Map<String, ZonedDateTime> localTimes = meeting.getLocalTimesForParticipants();
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM月dd日");
for (Map.Entry<String, ZonedDateTime> entry : localTimes.entrySet()) {
ZonedDateTime localTime = entry.getValue();
System.out.printf("%-8s: %s %s%n",
entry.getKey(),
localTime.format(dateFormatter),
localTime.format(timeFormatter));
}
// 检查时间是否合适
System.out.println("\n时间合适性检查:");
for (Map.Entry<String, ZonedDateTime> entry : localTimes.entrySet()) {
ZonedDateTime localTime = entry.getValue();
LocalTime meetingLocalTime = localTime.toLocalTime();
boolean isWorkingHour = meetingLocalTime.isAfter(LocalTime.of(9, 0)) &&
meetingLocalTime.isBefore(LocalTime.of(18, 0));
System.out.printf("%-8s: %s (%s)%n",
entry.getKey(),
isWorkingHour ? "✓ 工作时间" : "⚠️ 非工作时间",
meetingLocalTime.format(DateTimeFormatter.ofPattern("HH:mm")));
}
}
}
// 3. 任务管理系统
static class TaskManagementSystem {
static class Task {
String id;
String title;
String description;
TaskPriority priority;
LocalDateTime dueDate;
Duration estimatedEffort;
LocalDateTime actualStart;
LocalDateTime actualEnd;
TaskStatus status;
enum TaskPriority { LOW, MEDIUM, HIGH, CRITICAL }
enum TaskStatus { PENDING, IN_PROGRESS, BLOCKED, COMPLETED, CANCELLED }
public Task(String id, String title, String description, TaskPriority priority,
LocalDateTime dueDate, Duration estimatedEffort) {
this.id = id;
this.title = title;
this.description = description;
this.priority = priority;
this.dueDate = dueDate;
this.estimatedEffort = estimatedEffort;
this.status = TaskStatus.PENDING;
}
public void startTask() {
this.actualStart = LocalDateTime.now();
this.status = TaskStatus.IN_PROGRESS;
}
public void completeTask() {
this.actualEnd = LocalDateTime.now();
this.status = TaskStatus.COMPLETED;
}
public Duration getTimeSpent() {
if (actualStart == null) return Duration.ZERO;
if (actualEnd == null) return Duration.between(actualStart, LocalDateTime.now());
return Duration.between(actualStart, actualEnd);
}
public Duration getTimeRemaining() {
if (status == TaskStatus.COMPLETED) return Duration.ZERO;
Duration timeSpent = getTimeSpent();
if (timeSpent.compareTo(estimatedEffort) >= 0) {
return Duration.ZERO;
}
return estimatedEffort.minus(timeSpent);
}
public boolean isOverdue() {
return status != TaskStatus.COMPLETED &&
LocalDateTime.now().isAfter(dueDate);
}
public Duration getOverdueDuration() {
if (!isOverdue()) return Duration.ZERO;
return Duration.between(dueDate, LocalDateTime.now());
}
public String getProgress() {
if (status == TaskStatus.PENDING) return "0%";
if (status == TaskStatus.COMPLETED) return "100%";
if (estimatedEffort.isZero()) return "N/A";
long spentMillis = getTimeSpent().toMillis();
long totalMillis = estimatedEffort.toMillis();
double progress = (double) spentMillis / totalMillis * 100;
return String.format("%.1f%%", Math.min(100, progress));
}
@Override
public String toString() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
return String.format("任务: %s [%s]\n优先级: %s\n截止时间: %s\n预估工作量: %s\n状态: %s\n进度: %s\n%s",
title, id,
priority,
dueDate.format(formatter),
formatDuration(estimatedEffort),
status,
getProgress(),
isOverdue() ? "⚠️ 已超期 " + formatDuration(getOverdueDuration()) : "✓ 按时"
);
}
private String formatDuration(Duration duration) {
if (duration.toDays() > 0) {
return String.format("%d天%d小时", duration.toDays(), duration.toHoursPart());
} else if (duration.toHours() > 0) {
return String.format("%d小时%d分钟", duration.toHours(), duration.toMinutesPart());
} else {
return String.format("%d分钟", duration.toMinutes());
}
}
}
public static void manageTasks() {
System.out.println("\n=== 任务管理系统 ===");
Task task1 = new Task(
"TASK-001",
"实现用户登录功能",
"实现用户登录、注册、忘记密码功能",
Task.TaskPriority.HIGH,
LocalDateTime.of(2023, 11, 30, 18, 0),
Duration.ofHours(16)
);
Task task2 = new Task(
"TASK-002",
"编写单元测试",
"为核心模块编写单元测试",
Task.TaskPriority.MEDIUM,
LocalDateTime.of(2023, 12, 5, 18, 0),
Duration.ofHours(8)
);
task1.startTask();
task1.completeTask();
task2.startTask();
// task2 正在进行中
System.out.println(task1);
System.out.println("\n---\n");
System.out.println(task2);
// 生成任务报告
System.out.println("\n=== 任务报告 ===");
List<Task> tasks = Arrays.asList(task1, task2);
long totalEstimated = tasks.stream()
.mapToLong(t -> t.estimatedEffort.toHours())
.sum();
long totalActual = tasks.stream()
.mapToLong(t -> t.getTimeSpent().toHours())
.sum();
long overdueCount = tasks.stream()
.filter(Task::isOverdue)
.count();
System.out.printf("总任务数: %d\n", tasks.size());
System.out.printf("预估总工时: %d 小时\n", totalEstimated);
System.out.printf("实际总工时: %d 小时\n", totalActual);
System.out.printf("超期任务数: %d\n", overdueCount);
System.out.printf("完成率: %.1f%%\n",
tasks.stream().filter(t -> t.status == Task.TaskStatus.COMPLETED).count() * 100.0 / tasks.size());
}
}
// 4. 缓存过期系统
static class CacheExpirySystem<K, V> {
private final ConcurrentHashMap<K, CacheEntry<V>> cache = new ConcurrentHashMap<>();
private final ScheduledExecutorService cleaner = Executors.newScheduledThreadPool(1);
static class CacheEntry<V> {
V value;
Instant expiryTime;
CacheEntry(V value, Duration ttl) {
this.value = value;
this.expiryTime = Instant.now().plus(ttl);
}
boolean isExpired() {
return Instant.now().isAfter(expiryTime);
}
Duration getTimeUntilExpiry() {
return Duration.between(Instant.now(), expiryTime);
}
}
public CacheExpirySystem() {
// 每5秒清理一次过期缓存
cleaner.scheduleAtFixedRate(this::cleanExpiredEntries, 5, 5, TimeUnit.SECONDS);
}
public void put(K key, V value, Duration ttl) {
cache.put(key, new CacheEntry<>(value, ttl));
}
public V get(K key) {
CacheEntry<V> entry = cache.get(key);
if (entry == null || entry.isExpired()) {
cache.remove(key);
return null;
}
return entry.value;
}
public void remove(K key) {
cache.remove(key);
}
public Set<K> getKeysAboutToExpire(Duration threshold) {
Instant warningTime = Instant.now().plus(threshold);
Set<K> result = new HashSet<>();
for (Map.Entry<K, CacheEntry<V>> entry : cache.entrySet()) {
if (entry.getValue().expiryTime.isBefore(warningTime)) {
result.add(entry.getKey());
}
}
return result;
}
public Map<K, Duration> getExpiryTimes() {
Map<K, Duration> result = new HashMap<>();
for (Map.Entry<K, CacheEntry<V>> entry : cache.entrySet()) {
result.put(entry.getKey(), entry.getValue().getTimeUntilExpiry());
}
return result;
}
private void cleanExpiredEntries() {
cache.entrySet().removeIf(entry -> entry.getValue().isExpired());
}
public void shutdown() {
cleaner.shutdown();
}
public void demonstrate() {
System.out.println("\n=== 缓存过期系统 ===");
put("user:1001", "用户数据A", Duration.ofSeconds(30));
put("product:2001", "产品数据B", Duration.ofMinutes(1));
put("config:3001", "配置数据C", Duration.ofHours(1));
System.out.println("初始缓存状态:");
getExpiryTimes().forEach((key, duration) ->
System.out.printf(" %s: %d秒后过期%n", key, duration.getSeconds())
);
System.out.println("\n30秒内将过期的缓存:");
getKeysAboutToExpire(Duration.ofSeconds(30)).forEach(key ->
System.out.println(" " + key)
);
System.out.println("\n等待35秒后...");
try {
Thread.sleep(35000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("\n当前缓存状态:");
getExpiryTimes().forEach((key, duration) ->
System.out.printf(" %s: %d秒后过期%n", key, duration.getSeconds())
);
System.out.println("\n获取user:1001: " + get("user:1001"));
}
}
public static void main(String[] args) {
// 运行各个系统
FlightBookingSystem.bookFlight();
MeetingScheduler.scheduleMeeting();
TaskManagementSystem.manageTasks();
CacheExpirySystem<String, String> cacheSystem = new CacheExpirySystem<>();
cacheSystem.demonstrate();
cacheSystem.shutdown();
}
}
最佳实践与性能优化
java
import java.time.*;
import java.time.format.*;
import java.time.temporal.*;
import java.util.concurrent.*;
public class DateTimeBestPractices {
// 1. 线程安全的日期时间格式化
static class ThreadSafeFormatter {
// ❌ 错误做法:SimpleDateFormat不是线程安全的
// private static final SimpleDateFormat unsafeFormatter = new SimpleDateFormat("yyyy-MM-dd");
// ✅ 正确做法:DateTimeFormatter是线程安全的
private static final DateTimeFormatter safeFormatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final Map<String, DateTimeFormatter> formatterCache =
new ConcurrentHashMap<>();
public static DateTimeFormatter getFormatter(String pattern) {
return formatterCache.computeIfAbsent(pattern,
p -> DateTimeFormatter.ofPattern(p));
}
public static String format(LocalDateTime dateTime, String pattern) {
return dateTime.format(getFormatter(pattern));
}
}
// 2. 日期时间对象池(针对高频率创建场景)
static class DateTimePool {
private final ConcurrentLinkedQueue<LocalDateTime> pool =
new ConcurrentLinkedQueue<>();
private final int maxSize;
public DateTimePool(int maxSize) {
this.maxSize = maxSize;
}
public LocalDateTime borrow() {
LocalDateTime dt = pool.poll();
return dt != null ? dt : LocalDateTime.now();
}
public void release(LocalDateTime dateTime) {
if (pool.size() < maxSize) {
pool.offer(dateTime);
}
}
}
// 3. 高效的时间比较
static class EfficientDateTimeComparison {
// 比较两个时间段是否重叠
public static boolean isOverlap(LocalDateTime start1, LocalDateTime end1,
LocalDateTime start2, LocalDateTime end2) {
// 使用时间戳比较,避免多次方法调用
long s1 = start1.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
long e1 = end1.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
long s2 = start2.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
long e2 = end2.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
return !(e1 <= s2 || s1 >= e2);
}
// 批量检查日期是否在范围内
public static List<LocalDate> filterDatesInRange(List<LocalDate> dates,
LocalDate start,
LocalDate end) {
long startEpochDay = start.toEpochDay();
long endEpochDay = end.toEpochDay();
return dates.stream()
.filter(date -> {
long epochDay = date.toEpochDay();
return epochDay >= startEpochDay && epochDay <= endEpochDay;
})
.collect(Collectors.toList());
}
}
// 4. 避免常见的性能陷阱
static class PerformancePitfalls {
// ❌ 错误做法:在循环中重复创建格式化器
public static void badPractice(List<LocalDateTime> dateTimes) {
for (LocalDateTime dt : dateTimes) {
// 每次循环都创建新的格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = dt.format(formatter);
// 使用 formatted...
}
}
// ✅ 正确做法:重用格式化器
public static void goodPractice(List<LocalDateTime> dateTimes) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
for (LocalDateTime dt : dateTimes) {
String formatted = dt.format(formatter);
// 使用 formatted...
}
}
// ❌ 错误做法:不必要的时区转换
public static void badTimeZoneConversion() {
LocalDateTime localDateTime = LocalDateTime.now();
// 不必要的双重转换
ZonedDateTime zoned = localDateTime.atZone(ZoneId.systemDefault());
Instant instant = zoned.toInstant();
// ...
}
// ✅ 正确做法:直接转换
public static void goodTimeZoneConversion() {
LocalDateTime localDateTime = LocalDateTime.now();
// 直接转换为Instant
Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
// ...
}
}
// 5. 内存优化:使用原始类型处理时间戳
static class MemoryOptimizedTimeSeries {
private final long[] timestamps;
private final double[] values;
private int size;
public MemoryOptimizedTimeSeries(int capacity) {
this.timestamps = new long[capacity];
this.values = new double[capacity];
this.size = 0;
}
public void addDataPoint(LocalDateTime timestamp, double value) {
if (size >= timestamps.length) {
throw new IllegalStateException("容量已满");
}
// 存储时间戳(毫秒)
timestamps[size] = timestamp.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
values[size] = value;
size++;
}
public LocalDateTime getTimestamp(int index) {
return LocalDateTime.ofInstant(
Instant.ofEpochMilli(timestamps[index]),
ZoneId.systemDefault()
);
}
public double getValue(int index) {
return values[index];
}
// 高效的时间范围查询
public List<Integer> queryByTimeRange(LocalDateTime start, LocalDateTime end) {
long startMillis = start.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
long endMillis = end.atZone(ZoneId.systemDefault())
.toInstant()
.toEpochMilli();
List<Integer> result = new ArrayList<>();
for (int i = 0; i < size; i++) {
if (timestamps[i] >= startMillis && timestamps[i] <= endMillis) {
result.add(i);
}
}
return result;
}
}
// 6. 基准测试:对比不同实现方式的性能
static class DateTimeBenchmark {
public static void benchmarkFormatting() {
int iterations = 100000;
LocalDateTime now = LocalDateTime.now();
// 测试1:重复创建格式化器
long start1 = System.nanoTime();
for (int i = 0; i < iterations; i++) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(formatter);
}
long time1 = System.nanoTime() - start1;
// 测试2:重用格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
long start2 = System.nanoTime();
for (int i = 0; i < iterations; i++) {
String formatted = now.format(formatter);
}
long time2 = System.nanoTime() - start2;
System.out.println("=== 格式化性能测试 ===");
System.out.printf("重复创建格式化器: %,d ns%n", time1);
System.out.printf("重用格式化器: %,d ns%n", time2);
System.out.printf("性能提升: %.1f%%%n", (time1 - time2) * 100.0 / time1);
}
public static void benchmarkComparison() {
int iterations = 1000000;
List<LocalDate> dates = new ArrayList<>();
Random random = new Random();
// 生成测试数据
for (int i = 0; i < 1000; i++) {
dates.add(LocalDate.now().plusDays(random.nextInt(365)));
}
LocalDate start = LocalDate.now().minusDays(180);
LocalDate end = LocalDate.now().plusDays(180);
// 测试不同的比较方法
long startTime = System.nanoTime();
long count1 = dates.stream()
.filter(date -> date.isAfter(start) && date.isBefore(end))
.count();
long time1 = System.nanoTime() - startTime;
startTime = System.nanoTime();
long startEpochDay = start.toEpochDay();
long endEpochDay = end.toEpochDay();
long count2 = dates.stream()
.filter(date -> {
long epochDay = date.toEpochDay();
return epochDay > startEpochDay && epochDay < endEpochDay;
})
.count();
long time2 = System.nanoTime() - startTime;
System.out.println("\n=== 日期比较性能测试 ===");
System.out.printf("使用isAfter/isBefore: %,d ns%n", time1);
System.out.printf("使用epochDay直接比较: %,d ns%n", time2);
System.out.printf("性能提升: %.1f%%%n", (time1 - time2) * 100.0 / time1);
}
}
// 7. 实用的工具方法
static class DateTimeUtils {
// 安全解析日期,支持多种格式
public static Optional<LocalDate> safeParseDate(String dateStr) {
String[] patterns = {
"yyyy-MM-dd",
"yyyy/MM/dd",
"dd/MM/yyyy",
"dd-MM-yyyy",
"yyyy.MM.dd",
"MM/dd/yyyy"
};
for (String pattern : patterns) {
try {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
return Optional.of(LocalDate.parse(dateStr, formatter));
} catch (DateTimeParseException e) {
continue;
}
}
return Optional.empty();
}
// 计算两个日期之间的工作日
public static long calculateWorkingDays(LocalDate start, LocalDate end) {
return start.datesUntil(end.plusDays(1))
.filter(date -> date.getDayOfWeek() != DayOfWeek.SATURDAY &&
date.getDayOfWeek() != DayOfWeek.SUNDAY)
.count();
}
// 获取下一个工作日
public static LocalDate getNextWorkingDay(LocalDate date) {
LocalDate nextDay = date.plusDays(1);
while (nextDay.getDayOfWeek() == DayOfWeek.SATURDAY ||
nextDay.getDayOfWeek() == DayOfWeek.SUNDAY) {
nextDay = nextDay.plusDays(1);
}
return nextDay;
}
// 格式化时间差为可读字符串
public static String formatDurationReadable(Duration duration) {
if (duration.isNegative()) {
return "已过期";
}
if (duration.toDays() > 0) {
return String.format("%d天%d小时", duration.toDays(), duration.toHoursPart());
} else if (duration.toHours() > 0) {
return String.format("%d小时%d分钟", duration.toHours(), duration.toMinutesPart());
} else if (duration.toMinutes() > 0) {
return String.format("%d分钟", duration.toMinutes());
} else {
return String.format("%d秒", duration.toSeconds());
}
}
// 检查是否是营业时间(考虑周末和节假日)
public static boolean isBusinessTime(LocalDateTime dateTime,
Set<LocalDate> holidays) {
// 检查是否是周末
DayOfWeek dayOfWeek = dateTime.getDayOfWeek();
if (dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY) {
return false;
}
// 检查是否是节假日
if (holidays.contains(dateTime.toLocalDate())) {
return false;
}
// 检查时间是否在营业时间内(假设9:00-18:00)
LocalTime time = dateTime.toLocalTime();
return !time.isBefore(LocalTime.of(9, 0)) &&
!time.isAfter(LocalTime.of(18, 0));
}
}
public static void main(String[] args) {
System.out.println("=== 日期时间API最佳实践 ===");
// 运行性能测试
DateTimeBenchmark.benchmarkFormatting();
DateTimeBenchmark.benchmarkComparison();
// 演示实用工具方法
System.out.println("\n=== 实用工具方法演示 ===");
LocalDate today = LocalDate.now();
System.out.println("今天: " + today);
System.out.println("下一个工作日: " + DateTimeUtils.getNextWorkingDay(today));
LocalDate start = today.minusDays(30);
LocalDate end = today.plusDays(30);
System.out.printf("%s 到 %s 之间的工作日: %d天%n",
start, end, DateTimeUtils.calculateWorkingDays(start, end));
// 测试安全解析
String[] testDates = {"2023-11-15", "15/11/2023", "2023.11.15", "无效日期"};
for (String testDate : testDates) {
Optional<LocalDate> parsed = DateTimeUtils.safeParseDate(testDate);
System.out.printf("解析 '%s': %s%n",
testDate,
parsed.map(LocalDate::toString).orElse("解析失败"));
}
// 演示缓存系统
System.out.println("\n=== 线程安全格式化演示 ===");
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 100; i++) {
final int index = i;
futures.add(executor.submit(() ->
ThreadSafeFormatter.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss") +
" - 线程" + index
));
}
for (Future<String> future : futures) {
try {
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
executor.shutdown();
}
}
总结:Java 8日期时间API的核心要点
主要优势
- 不可变性:所有核心类都是不可变的,线程安全
- 清晰的API设计:分离了日期、时间、时区等概念
- 强大的时区支持:内置完善的时区处理机制
- 流畅的链式调用:代码更易读、更易写
- ISO标准兼容:默认使用ISO-8601标准
常见使用场景
| 场景 | 推荐使用的类 | 说明 |
|---|---|---|
| 只处理日期 | LocalDate | 生日、纪念日、截止日期 |
| 只处理时间 | LocalTime | 营业时间、会议时间 |
| 日期+时间 | LocalDateTime | 日志时间、创建时间 |
| 跨时区应用 | ZonedDateTime | 国际会议、航班时间 |
| 时间点/时间戳 | Instant | 性能测量、缓存过期 |
| 时间段(时间) | Duration | 电影时长、任务耗时 |
| 时间段(日期) | Period | 年龄、订阅期限 |
迁移指南(从传统API迁移)
java
public class MigrationGuide {
// 传统API -> Java 8 API的迁移
public static void migrateFromLegacy() {
System.out.println("=== 从传统API迁移到Java 8 API ===");
// 1. Date -> Instant/LocalDateTime
java.util.Date oldDate = new java.util.Date();
Instant instant = oldDate.toInstant();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
System.out.println("Date -> LocalDateTime: " + localDateTime);
// 2. Calendar -> ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(
calendar.toInstant(),
calendar.getTimeZone().toZoneId()
);
System.out.println("Calendar -> ZonedDateTime: " + zonedDateTime);
// 3. SimpleDateFormat -> DateTimeFormatter
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
System.out.println("格式化: " + formatter.format(LocalDate.now()));
// 4. 时间运算的迁移
calendar.add(Calendar.DAY_OF_MONTH, 7);
ZonedDateTime sevenDaysLater = zonedDateTime.plusDays(7);
System.out.println("7天后: " + sevenDaysLater);
// 5. 时间比较的迁移
boolean isBefore = calendar.before(Calendar.getInstance());
boolean isBefore8 = zonedDateTime.isBefore(ZonedDateTime.now());
System.out.println("时间比较: " + isBefore8);
}
// 常见陷阱和解决方案
public static void commonPitfalls() {
System.out.println("\n=== 常见陷阱和解决方案 ===");
// 陷阱1:时区处理不当
System.out.println("\n陷阱1:时区处理");
System.out.println("❌ LocalDateTime.now() 不包含时区信息");
System.out.println("✅ 使用 ZonedDateTime.now() 或指定时区");
// 陷阱2:忽略不可变性
System.out.println("\n陷阱2:忽略不可变性");
LocalDate date = LocalDate.of(2023, 11, 15);
date.plusDays(1); // 这个方法返回新对象,不修改原对象
System.out.println("date.plusDays(1) 后,原date未改变: " + date);
LocalDate newDate = date.plusDays(1);
System.out.println("需要接收返回值: " + newDate);
// 陷阱3:格式化模式错误
System.out.println("\n陷阱3:格式化模式");
System.out.println("❌ yyyy-MM-dd HH:mm:ss - 24小时制");
System.out.println("❌ yyyy-MM-dd hh:mm:ss - 12小时制(需要a表示上午/下午)");
System.out.println("✅ yyyy-MM-dd HH:mm:ss - 24小时制");
System.out.println("✅ yyyy-MM-dd hh:mm:ss a - 12小时制");
// 陷阱4:解析严格性
System.out.println("\n陷阱4:解析严格性");
String dateStr = "2023-02-31"; // 2月31日不存在
try {
LocalDate date2 = LocalDate.parse(dateStr);
System.out.println("宽松解析: " + date2);
} catch (Exception e) {
System.out.println("默认严格解析失败: " + e.getMessage());
}
}
// 性能最佳实践总结
public static void performanceBestPractices() {
System.out.println("\n=== 性能最佳实践 ===");
System.out.println("1. 重用DateTimeFormatter");
System.out.println(" ✅ 将格式化器声明为静态常量");
System.out.println(" ❌ 避免在循环中创建新的格式化器");
System.out.println("\n2. 使用正确的数据结构");
System.out.println(" ✅ 对于时间序列数据,考虑使用long数组存储时间戳");
System.out.println(" ❌ 避免存储大量LocalDateTime对象");
System.out.println("\n3. 高效的时间比较");
System.out.println(" ✅ 对于批量比较,转换为epochDay或时间戳进行比较");
System.out.println(" ❌ 避免在循环中多次调用isBefore/isAfter");
System.out.println("\n4. 时区处理优化");
System.out.println(" ✅ 如果不需要时区信息,使用LocalDateTime而不是ZonedDateTime");
System.out.println(" ✅ 避免不必要的时区转换");
System.out.println(" ❌ 不要在性能关键路径中频繁转换时区");
}
public static void main(String[] args) {
migrateFromLegacy();
commonPitfalls();
performanceBestPractices();
System.out.println("\n=== 学习资源推荐 ===");
System.out.println("1. Oracle官方教程: Java Date Time");
System.out.println("2. 书籍: 《Java 8 in Action》");
System.out.println("3. 在线练习: codingbat.com/java/Date-Time");
System.out.println("4. 开源项目: Joda-Time (Java 8日期时间API的前身)");
System.out.println("\n记住:熟练掌握Java 8日期时间API是现代Java开发的必备技能!");
}
}