跳到主要内容

Java 日期时间计算

介绍

在Java应用开发中,日期和时间的计算是一个非常常见的需求。无论是计算两个日期之间的差值,增加或减少时间单位,还是比较时间先后,都需要用到日期时间计算。Java 8引入的新日期时间API(java.time包)大大简化了这些操作,使日期时间计算变得更加直观和易用。

本文将介绍Java中进行日期时间计算的各种方法,帮助初学者掌握这一重要技能。

Java 8日期时间API核心类

在开始学习日期时间计算之前,我们先快速了解Java 8日期时间API的核心类:

  • LocalDate:表示日期(年月日),不包含时间和时区
  • LocalTime:表示时间(时分秒),不包含日期和时区
  • LocalDateTime:表示日期和时间,不包含时区
  • ZonedDateTime:表示带时区的日期和时间
  • Instant:表示时间戳(从1970-01-01T00:00:00Z开始的秒数)
  • Duration:表示两个时间之间的间隔
  • Period:表示两个日期之间的间隔

创建日期时间对象

在进行日期时间计算之前,我们需要先创建相应的对象:

java
// 获取当前日期
LocalDate today = LocalDate.now();
System.out.println("今天的日期: " + today);

// 获取当前时间
LocalTime now = LocalTime.now();
System.out.println("现在的时间: " + now);

// 获取当前日期和时间
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("当前日期和时间: " + dateTime);

// 创建特定的日期
LocalDate specificDate = LocalDate.of(2023, 10, 15);
System.out.println("指定的日期: " + specificDate);

// 创建特定的时间
LocalTime specificTime = LocalTime.of(13, 30, 45);
System.out.println("指定的时间: " + specificTime);

// 创建特定的日期和时间
LocalDateTime specificDateTime = LocalDateTime.of(2023, 10, 15, 13, 30, 45);
System.out.println("指定的日期和时间: " + specificDateTime);

输出:

今天的日期: 2023-10-20
现在的时间: 14:30:25.123456
当前日期和时间: 2023-10-20T14:30:25.123456
指定的日期: 2023-10-15
指定的时间: 13:30:45
指定的日期和时间: 2023-10-15T13:30:45

日期时间的加减运算

日期加减

LocalDate类提供了多种方法来增加或减少天数、月数或年数:

java
LocalDate today = LocalDate.now();
System.out.println("今天: " + today);

// 加法运算
LocalDate tomorrow = today.plusDays(1);
System.out.println("明天: " + tomorrow);

LocalDate nextWeek = today.plusWeeks(1);
System.out.println("下周今天: " + nextWeek);

LocalDate nextMonth = today.plusMonths(1);
System.out.println("下个月今天: " + nextMonth);

LocalDate nextYear = today.plusYears(1);
System.out.println("明年今天: " + nextYear);

// 减法运算
LocalDate yesterday = today.minusDays(1);
System.out.println("昨天: " + yesterday);

LocalDate lastWeek = today.minusWeeks(1);
System.out.println("上周今天: " + lastWeek);

输出:

今天: 2023-10-20
明天: 2023-10-21
下周今天: 2023-10-27
下个月今天: 2023-11-20
明年今天: 2024-10-20
昨天: 2023-10-19
上周今天: 2023-10-13

时间加减

类似地,LocalTime类提供了添加或减少小时、分钟、秒的方法:

java
LocalTime now = LocalTime.now();
System.out.println("现在: " + now);

// 加法运算
LocalTime oneHourLater = now.plusHours(1);
System.out.println("一小时后: " + oneHourLater);

LocalTime tenMinutesLater = now.plusMinutes(10);
System.out.println("十分钟后: " + tenMinutesLater);

// 减法运算
LocalTime twoHoursEarlier = now.minusHours(2);
System.out.println("两小时前: " + twoHoursEarlier);

输出:

现在: 14:30:25.123456
一小时后: 15:30:25.123456
十分钟后: 14:40:25.123456
两小时前: 12:30:25.123456

日期时间加减

LocalDateTime结合了LocalDateLocalTime的功能:

java
LocalDateTime dateTime = LocalDateTime.now();
System.out.println("当前日期时间: " + dateTime);

// 同时增加日期和时间
LocalDateTime futureDateTime = dateTime.plusDays(3).plusHours(2);
System.out.println("三天后的两小时后: " + futureDateTime);

输出:

当前日期时间: 2023-10-20T14:30:25.123456
三天后的两小时后: 2023-10-23T16:30:25.123456
不可变性

Java 8日期时间API中的类都是不可变的,这意味着每次修改操作(如加减)都会返回一个新的对象,而不是修改原对象。这种设计避免了并发问题,使代码更加安全。

时间段计算

使用Duration计算时间差

Duration类用于表示两个时间点之间的时间量:

java
LocalTime start = LocalTime.of(8, 0);
LocalTime end = LocalTime.of(17, 30);

Duration duration = Duration.between(start, end);
System.out.println("工作时长(小时): " + duration.toHours());
System.out.println("工作时长(分钟): " + duration.toMinutes());

LocalDateTime startDateTime = LocalDateTime.of(2023, 10, 20, 9, 0);
LocalDateTime endDateTime = LocalDateTime.of(2023, 10, 20, 18, 0);

Duration workDuration = Duration.between(startDateTime, endDateTime);
System.out.println("工作日时长(小时): " + workDuration.toHours());

输出:

工作时长(小时): 9
工作时长(分钟): 570
工作日时长(小时): 9

使用Period计算日期差

Period类用于表示两个日期之间的时间段:

java
LocalDate startDate = LocalDate.of(2023, 1, 1);
LocalDate endDate = LocalDate.of(2023, 12, 31);

Period period = Period.between(startDate, endDate);
System.out.println("时间段: " + period.getYears() + " 年 "
+ period.getMonths() + " 月 "
+ period.getDays() + " 天");

// 计算总天数
long totalDays = ChronoUnit.DAYS.between(startDate, endDate);
System.out.println("总天数: " + totalDays);

输出:

时间段: 0 年 11 月 30 天
总天数: 364

日期时间的比较

使用compareTo方法

日期时间类都实现了Comparable接口,可以使用compareTo方法进行比较:

java
LocalDate date1 = LocalDate.of(2023, 10, 15);
LocalDate date2 = LocalDate.of(2023, 10, 20);

int comparison = date1.compareTo(date2);
if (comparison < 0) {
System.out.println(date1 + " 早于 " + date2);
} else if (comparison > 0) {
System.out.println(date1 + " 晚于 " + date2);
} else {
System.out.println(date1 + " 等于 " + date2);
}

输出:

2023-10-15 早于 2023-10-20

使用比较方法

日期时间类还提供了更直观的比较方法:

java
LocalDate date1 = LocalDate.of(2023, 10, 15);
LocalDate date2 = LocalDate.of(2023, 10, 20);

System.out.println("date1 是否在date2之前: " + date1.isBefore(date2));
System.out.println("date1 是否在date2之后: " + date1.isAfter(date2));
System.out.println("date1 是否等于date2: " + date1.isEqual(date2));

输出:

date1 是否在date2之前: true
date1 是否在date2之后: false
date1 是否等于date2: false

日期时间的调整和操作

日期调整器

Java 8提供了TemporalAdjusters类,包含许多预定义的调整器,可以执行常见的日期操作:

java
LocalDate date = LocalDate.of(2023, 10, 15);
System.out.println("当前日期: " + date);

// 获取当月第一天
LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());
System.out.println("当月第一天: " + firstDayOfMonth);

// 获取当月最后一天
LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("当月最后一天: " + lastDayOfMonth);

// 获取下一个周一
LocalDate nextMonday = date.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
System.out.println("下一个周一: " + nextMonday);

// 获取本月第二个周三
LocalDate secondWednesday = date.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.WEDNESDAY));
System.out.println("本月第二个周三: " + secondWednesday);

输出:

当前日期: 2023-10-15
当月第一天: 2023-10-01
当月最后一天: 2023-10-31
下一个周一: 2023-10-16
本月第二个周三: 2023-10-11

自定义调整器

你还可以创建自定义的日期调整器:

java
// 创建一个调整器,获取下一个工作日(跳过周六和周日)
TemporalAdjuster nextWorkingDay = temporal -> {
LocalDate date = LocalDate.from(temporal);
DayOfWeek dayOfWeek = date.getDayOfWeek();

int daysToAdd = 1;
if (dayOfWeek == DayOfWeek.FRIDAY) {
daysToAdd = 3; // 周五加3天到周一
} else if (dayOfWeek == DayOfWeek.SATURDAY) {
daysToAdd = 2; // 周六加2天到周一
}

return date.plusDays(daysToAdd);
};

LocalDate today = LocalDate.of(2023, 10, 20); // 假设今天是周五
System.out.println("今天: " + today + " (" + today.getDayOfWeek() + ")");

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

输出:

今天: 2023-10-20 (FRIDAY)
下一个工作日: 2023-10-23 (MONDAY)

实际应用案例

案例1:计算项目截止日期

假设我们有一个项目,从今天开始,需要30个工作日完成。我们需要计算出截止日期(不包括周末):

java
public LocalDate calculateDeadline(LocalDate startDate, int workDays) {
LocalDate deadline = startDate;
int addedDays = 0;

while (addedDays < workDays) {
deadline = deadline.plusDays(1);
if (!(deadline.getDayOfWeek() == DayOfWeek.SATURDAY ||
deadline.getDayOfWeek() == DayOfWeek.SUNDAY)) {
addedDays++;
}
}

return deadline;
}

// 使用示例
LocalDate startDate = LocalDate.now();
LocalDate deadline = calculateDeadline(startDate, 30);
System.out.println("项目开始日期: " + startDate);
System.out.println("预计完成日期: " + deadline);
System.out.println("总天数: " + ChronoUnit.DAYS.between(startDate, deadline));

案例2:年龄计算器

计算一个人的确切年龄:

java
public void calculateAge(LocalDate birthDate) {
LocalDate currentDate = LocalDate.now();
Period age = Period.between(birthDate, currentDate);

System.out.println("您已经 " + age.getYears() + " 岁 " +
age.getMonths() + " 个月 " +
age.getDays() + " 天了");

long totalDays = ChronoUnit.DAYS.between(birthDate, currentDate);
System.out.println("总共生活了 " + totalDays + " 天");
}

// 使用示例
LocalDate birthDate = LocalDate.of(1990, 5, 15);
calculateAge(birthDate);

输出:

您已经 33 岁 5 个月 5 天了
总共生活了 12192 天

案例3:会议调度系统

判断两个会议是否有时间冲突:

java
public boolean hasConflict(LocalDateTime meeting1Start, LocalDateTime meeting1End,
LocalDateTime meeting2Start, LocalDateTime meeting2End) {
// 如果会议1结束时间早于会议2开始时间,或会议1开始时间晚于会议2结束时间,则没有冲突
return !(meeting1End.isBefore(meeting2Start) || meeting1Start.isAfter(meeting2End));
}

// 使用示例
LocalDateTime meeting1Start = LocalDateTime.of(2023, 10, 20, 10, 0);
LocalDateTime meeting1End = LocalDateTime.of(2023, 10, 20, 11, 30);
LocalDateTime meeting2Start = LocalDateTime.of(2023, 10, 20, 11, 0);
LocalDateTime meeting2End = LocalDateTime.of(2023, 10, 20, 12, 0);

boolean conflict = hasConflict(meeting1Start, meeting1End, meeting2Start, meeting2End);
System.out.println("会议时间有冲突: " + conflict);

输出:

会议时间有冲突: true

总结

Java 8的日期时间API为我们提供了丰富的日期和时间计算功能。本文介绍了:

  1. 日期时间对象的创建
  2. 日期时间的加减运算
  3. 使用Duration和Period计算时间差
  4. 日期时间的比较方法
  5. 使用调整器进行高级操作
  6. 实际应用案例

掌握这些日期时间计算技能,可以帮助你在日常开发中轻松处理各种与时间相关的业务逻辑。

注意时区

本文主要介绍了不带时区的日期时间计算。在处理跨时区问题时,应使用ZonedDateTimeInstant类,并特别注意夏令时等特殊情况。

练习题

为了巩固所学知识,尝试完成以下练习:

  1. 编写一个方法,计算两个日期之间的工作日数量(不包括周末)。
  2. 创建一个生日提醒程序,当用户的生日在30天内时发出提醒。
  3. 实现一个旅行天数计算器,考虑不同时区的情况。
  4. 编写一个方法,找出一年中的所有法定节假日(可以使用你所在国家的节假日)。
  5. 创建一个简单的日程表,允许添加事件并检测时间冲突。

附加资源