Java 根据现有实例创建日期和时间,用户希望修改 Date-Time API 中某个类的现有实例,如果需要进行简单的增减操作,使用 plus
或 minus
方法;对于其他操作,使用 with
方法。Date-Time API 中的所有实例都是不可变的。一旦创建 LocalDate
、LocalTime
、LocalDateTime
或 ZonedDateTime
,就无法修改它们。
Java 根据现有实例创建日期和时间 问题描述
用户希望修改 Date-Time API 中某个类的现有实例。
Java 根据现有实例创建日期和时间 解决方案
如果需要进行简单的增减操作,使用 plus
或 minus
方法;对于其他操作,使用 with
方法。
Java 根据现有实例创建日期和时间 具体实例
Date-Time API 中的所有实例都是不可变的。一旦创建 LocalDate
、LocalTime
、LocalDateTime
或 ZonedDateTime
,就无法修改它们。这对保持线程安全而言十分有利,不过如何根据现有实例创建新的实例呢?
以 LocalDate
类为例,它定义了多种对日期进行增减操作的方法,包括:
LocalDate plusDays(long daysToAdd)
-
LocalDate plusWeeks(long weeksToAdd)
-
LocalDate plusMonths(long monthsToAdd)
-
LocalDate plusYears(long yearsToAdd)
上述方法均返回一个新的 LocalDate
,它是当前日期的副本,并添加了指定的值。
LocalTime
类也定义了类似的方法:
LocalTime plusNanos(long nanosToAdd)
-
LocalTime plusSeconds(long secondsToAdd)
-
LocalTime plusMinutes(long minutesToAdd)
-
LocalTime plusHours(long hoursToAdd)
类似地,每种方法均返回一个新的 LocalTime
,它是当前时间的副本,并添加了指定的值。此外,LocalDateTime
类囊括了 LocalDate
和 LocalTime
类中用于处理日期和时间增减的所有方法。例 8-6 显示了各种 plus
方法在 LocalDate
和 LocalTime
类中的应用。
例 8-6
plus
方法在LocalDate
和LocalTime
类中的应用
@Test
public void localDatePlus() throws Exception {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate start = LocalDate.of(2017, Month.FEBRUARY, 2);
LocalDate end = start.plusDays(3);
assertEquals("2017-02-05", end.format(formatter));
end = start.plusWeeks(5);
assertEquals("2017-03-09", end.format(formatter));
end = start.plusMonths(7);
assertEquals("2017-09-02", end.format(formatter));
end = start.plusYears(2);
assertEquals("2019-02-02", end.format(formatter));
}
@Test
public void localTimePlus() throws Exception {
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_TIME;
LocalTime start = LocalTime.of(11, 30, 0, 0);
LocalTime end = start.plusNanos(1_000_000);
assertEquals("11:30:00.001", end.format(formatter));
end = start.plusSeconds(20);
assertEquals("11:30:20", end.format(formatter));
end = start.plusMinutes(45);
assertEquals("12:15:00", end.format(formatter));
end = start.plusHours(5);
assertEquals("16:30:00", end.format(formatter));
}
不少类还包括其他两种形式的 plus
和 minus
方法。以 LocalDateTime
类为例,plus
和 minus
方法的签名如下:
LocalDateTime plus(long amountToAdd, TemporalUnit unit)
LocalDateTime plus(TemporalAmount amountToAdd)
LocalDateTime minus(long amountToSubtract, TemporalUnit unit)
LocalDateTime minus(TemporalAmount amountToSubtract)
对于 LocalDate
和 LocalDate
类,plus
和 minus
方法的格式与 LocalDateTime
类相同,具有相应的返回类型。有趣的是,不妨将 minus
方法视为具有否定形式的 plus
方法。
对传入 TemporalAmount
的方法而言,参数通常为 Period
或 Duration
,但也可以是任何实现 TemporalAmount
接口的类型。该接口定义了 addTo
和 subtractFrom
两种方法:
Temporal addTo(Temporal temporal)
Temporal subtractFrom(Temporal temporal)
跟踪调用栈(call stack)可以看到,调用 minus
委托给带有否定参数的 plus
,而 plus
委托给 TemporalAmount.addTo(Temporal)
,TemporalAmount.addTo(Temporal)
再回调 plus(long, TemporalUnit)
,它将执行实际的操作。
例 8-7 显示了 plus
和 minus
方法的相关应用。
例 8-7
plus
和minus
方法的应用
@Test
public void plus_minus() throws Exception {
Period period = Period.of(2, 3, 4); // 两年3个月零4天
LocalDateTime start = LocalDateTime.of(2017, Month.FEBRUARY, 2, 11, 30);
LocalDateTime end = start.plus(period);
assertEquals("2019-05-06T11:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
end = start.plus(3, ChronoUnit.HALF_DAYS);
assertEquals("2017-02-03T23:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
end = start.minus(period);
assertEquals("2014-10-29T11:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
end = start.minus(2, ChronoUnit.CENTURIES);
assertEquals("1817-02-02T11:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
end = start.plus(3, ChronoUnit.MILLENNIA);
assertEquals("5017-02-02T11:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}
当 API 调用
TemporalUnit
时,提供的实现类为ChronoUnit
,它定义了许多方便的枚举常量(enum constant)可供使用。
此外,每种类都定义了一系列 with
方法,可以一次修改一个字段。
with
方法用于处理常用的日期和时间,某些方法颇为有趣。以 LocalDateTime
类为例:
LocalDateTime withNano(int nanoOfSecond)
LocalDateTime withSecond(int second)
LocalDateTime withMinute(int minute)
LocalDateTime withHour(int hour)
LocalDateTime withDayOfMonth(int dayOfMonth)
LocalDateTime withDayOfYear(int dayOfYear)
LocalDateTime withMonth(int month)
LocalDateTime withYear(int year)
例 8-8 显示了各种 with
方法的应用。
例 8-8
with
方法在LocalDateTime
类中的应用
@Test
public void with() throws Exception {
LocalDateTime start = LocalDateTime.of(2017, Month.FEBRUARY, 2, 11, 30);
LocalDateTime end = start.withMinute(45);
assertEquals("2017-02-02T11:45:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
end = start.withHour(16);
assertEquals("2017-02-02T16:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
end = start.withDayOfMonth(28);
assertEquals("2017-02-28T11:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
end = start.withDayOfYear(300);
assertEquals("2017-10-27T11:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
end = start.withYear(2020);
assertEquals("2020-02-02T11:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}
@Test(expected = DateTimeException.class)
public void withInvalidDate() throws Exception {
LocalDateTime start = LocalDateTime.of(2017, Month.FEBRUARY, 2, 11, 30);
start.withDayOfMonth(29);
}
由于 2017 年并非闰年,无法将日期设置为 2 月 29 日,第二个测试将抛出 DateTimeException
。
with
方法也可以传入 TemporalAdjuster
或 TemporalField
:
LocalDateTime with(TemporalAdjuster adjuster)
LocalDateTime with(TemporalField field, long newValue)
传入 TemporalField
的 with
方法允许字段解析日期以使其有效。如例 8-9 所示,程序传入 1 月的最后一天,并尝试将月份改为 2 月(“2 月 31 日”)。此时,根据 Javadoc 的描述,系统将选择前一个有效日期,即 2 月的最后一天(2 月 28 日)。
例 8-9 月份调整(无效)
@Test
public void temporalField() throws Exception {
LocalDateTime start = LocalDateTime.of(2017, Month.JANUARY, 31, 11, 30);
LocalDateTime end = start.with(ChronoField.MONTH_OF_YEAR, 2);
assertEquals("2017-02-28T11:30:00",
end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}
可想而知,日期和时间的处理涉及一些相当复杂的规则,不过 Javadoc 对此做了系统而详尽的描述。
范例调节器与查询将讨论传入 TemporalAdjuster
的 with
方法。