61 LocalDate
和LocalTime
中的LocalDateTime
LocalDateTime
类公开了一系列of()
方法,这些方法可用于获取LocalDateTime
的不同类型的实例。例如,从年、月、日、时、分、秒或纳秒获得的LocalDateTime
类如下所示:
LocalDateTime ldt = LocalDateTime.of(2020, 4, 1, 12, 33, 21, 675);
因此,前面的代码将日期和时间组合为of()
方法的参数。为了将日期和时间组合为对象,解决方案可以利用以下of()
方法:
public static LocalDateTime of(LocalDate date, LocalTime time)
这导致LocalDate
和LocalTime
,如下所示:
LocalDate localDate = LocalDate.now(); // 2019-Feb-24 LocalTime localTime = LocalTime.now(); // 02:08:10 PM
它们可以组合在一个对象LocalDateTime
中,如下所示:
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
格式化LocalDateTime显示日期和时间如下:
// 2019-Feb-24 02:08:10 PM String localDateTimeAsString = localDateTime .format(DateTimeFormatter.ofPattern("yyyy-MMM-dd hh:mm:ss a"));
62 通过Instant
类的机器时间
JDK8 附带了一个新类,名为java.time.Instant
。主要地,Instant
类表示时间线上的一个瞬时点,从 1970 年 1 月 1 日(纪元)的第一秒开始,在 UTC 时区,分辨率为纳秒。
Java8Instant类在概念上类似于java.util.Date。两者都代表 UTC 时间线上的一个时刻。当Instant的分辨率高达纳秒时,java.util.Date的分辨率为毫秒。
这个类对于生成机器时间的时间戳非常方便。为了获得这样的时间戳,只需调用如下的now()方法:
// 2019-02-24T15:05:21.781049600Z Instant timestamp = Instant.now();
使用以下代码段可以获得类似的输出:
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
或者,使用以下代码段:
Clock clock = Clock.systemUTC();
调用Instant.toString()
产生一个输出,该输出遵循 ISO-8601 标准来表示日期和时间。
将字符串转换为Instant
遵循 ISO-8601 标准表示日期和时间的字符串可以通过Instant.parse()方法轻松转换为Instant,如下例所示:
// 2019-02-24T14:31:33.197021300Z Instant timestampFromString = Instant.parse("2019-02-24T14:31:33.197021300Z");
向Instant
添加/减去时间
对于添加时间,Instant
有一套方法。例如,向当前时间戳添加 2 小时可以如下完成:
Instant twoHourLater = Instant.now().plus(2, ChronoUnit.HOURS);
在减去时间方面,例如 10 分钟,请使用以下代码段:
Instant tenMinutesEarlier = Instant.now() .minus(10, ChronoUnit.MINUTES);
除plus()方法外,Instant还包含plusNanos()、plusMillis()、plusSeconds()。此外,除了minus()方法外,Instant还包含minusNanos()、minusMillis()、minusSeconds()。
比较Instant对象
比较两个Instant对象可以通过Instant.isAfter()和Instant.isBefore()方法来完成。例如,让我们看看以下两个Instant对象:
Instant timestamp1 = Instant.now(); Instant timestamp2 = timestamp1.plusSeconds(10);
检查timestamp1
是否在timestamp2
之后:
boolean isAfter = timestamp1.isAfter(timestamp2); // false
检查timestamp1
是否在timestamp2
之前:
boolean isBefore = timestamp1.isBefore(timestamp2); // true
两个Instant
对象之间的时差可以通过Instant.until()
方法计算:
// 10 seconds long difference = timestamp1.until(timestamp2, ChronoUnit.SECONDS);
在Instant
和LocalDateTime
、ZonedDateTime
和OffsetDateTime
之间转换
这些常见的转换可以在以下示例中完成:
在Instant
和LocalDateTime
之间转换-因为LocalDateTime
不知道时区,所以使用零偏移 UTC+0:
// 2019-02-24T15:27:13.990103700 LocalDateTime ldt = LocalDateTime.ofInstant( Instant.now(), ZoneOffset.UTC); // 2019-02-24T17:27:14.013105Z Instant instantLDT = LocalDateTime.now().toInstant(ZoneOffset.UTC);
在Instant
和ZonedDateTime
之间转换—将Instant
UTC+0 转换为巴黎ZonedDateTime
UTC+1:
// 2019-02-24T16:34:36.138393100+01:00[Europe/Paris] ZonedDateTime zdt = Instant.now().atZone(ZoneId.of("Europe/Paris")); // 2019-02-24T16:34:36.150393800Z Instant instantZDT = LocalDateTime.now() .atZone(ZoneId.of("Europe/Paris")).toInstant();
在Instant
和OffsetDateTime
之间转换-指定 2 小时的偏移量:
// 2019-02-24T17:34:36.151393900+02:00 OffsetDateTime odt = Instant.now().atOffset(ZoneOffset.of("+02:00")); // 2019-02-24T15:34:36.153394Z Instant instantODT = LocalDateTime.now() .atOffset(ZoneOffset.of("+02:00")).toInstant();
63 使用基于日期的值定义时段,使用基于时间的值定义持续时间
JDK8 附带了两个新类,分别命名为java.time.Period
和java.time.Duration
。让我们在下一节中详细了解它们。
使用基于日期的值的时间段
Period
类意味着使用基于日期的值(年、月、周和天)来表示时间量。这段时间可以用不同的方法获得。例如,120 天的周期可以如下获得:
Period fromDays = Period.ofDays(120); // P120D
在ofDays()方法旁边,Period类还有ofMonths()、ofWeeks()和ofYears()。
或者,通过of()方法可以得到 2000 年 11 个月 24 天的期限,如下所示:
Period periodFromUnits = Period.of(2000, 11, 24); // P2000Y11M24D
Period
也可以从LocalDate
中得到:
LocalDate localDate = LocalDate.now(); Period periodFromLocalDate = Period.of(localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth());
最后,可以从遵循 ISO-8601 周期格式PnYnMnD和PnW的String对象获得Period。例如,P2019Y2M25D字符串表示 2019 年、2 个月和 25 天:
Period periodFromString = Period.parse("P2019Y2M25D");
调用Period.toString()将返回时间段,同时也遵循 ISO-8601 时间段格式,PnYnMnD和PnW(例如P120D、P2000Y11M24D)。
但是,当Period被用来表示两个日期之间的一段时间(例如LocalDate时,Period的真实力量就显现出来了。2018 年 3 月 12 日至 2019 年 7 月 20 日期间可表示为:
LocalDate startLocalDate = LocalDate.of(2018, 3, 12); LocalDate endLocalDate = LocalDate.of(2019, 7, 20); Period periodBetween = Period.between(startLocalDate, endLocalDate);
年、月、日的时间量可以通过Period.getYears()
、Period.getMonths()
、Period.getDays()
获得。例如,以下辅助方法使用这些方法将时间量输出为字符串:
public static String periodToYMD(Period period) { StringBuilder sb = new StringBuilder(); sb.append(period.getYears()) .append("y:") .append(period.getMonths()) .append("m:") .append(period.getDays()) .append("d"); return sb.toString(); }
我们将此方法称为periodBetween
(差值为 1 年 4 个月 8 天):
periodToYMD(periodBetween); // 1y:4m:8d
当确定某个日期是否早于另一个日期时,Period类也很有用。有一个标志方法,名为isNegative()。有一个A周期和一个B周期,如果B在A之前,应用Period.between(A, B)的结果可以是负的,如果A在B之前,应用isNegative()的结果可以是正的,如果B在A之前,false在A之前,则isNegative()返回true B,如我们的例子所示(基本上,如果年、月或日为负数,此方法返回false):
// returns false, since 12 March 2018 is earlier than 20 July 2019 periodBetween.isNegative();
最后,Period可以通过加上或减去一段时间来修改。方法有plusYears()、plusMonths()、plusDays()、minusYears()、minusMonths()、minusDays()等。例如,在periodBetween上加 1 年可以如下操作:
Period periodBetweenPlus1Year = periodBetween.plusYears(1L);
添加两个Period
类可以通过Period.plus()
方法完成,如下所示:
Period p1 = Period.ofDays(5); Period p2 = Period.ofDays(20); Period p1p2 = p1.plus(p2); // P25D
使用基于时间的值的持续时间
Duration
类意味着使用基于时间的值(小时、分钟、秒或纳秒)来表示时间量。这种持续时间可以通过不同的方式获得。例如,可以如下获得 10 小时的持续时间:
Duration fromHours = Duration.ofHours(10); // PT10H
在ofHours()
方法旁边,Duration
类还有ofDays()
、ofMillis()
、ofMinutes()
、ofSeconds()
和ofNanos()
。
或者,可以通过of()
方法获得 3 分钟的持续时间,如下所示:
Duration fromMinutes = Duration.of(3, ChronoUnit.MINUTES); // PT3M
Duration
也可以从LocalDateTime
中得到:
LocalDateTime localDateTime = LocalDateTime.of(2018, 3, 12, 4, 14, 20, 670); // PT14M Duration fromLocalDateTime = Duration.ofMinutes(localDateTime.getMinute());
也可从LocalTime
中获得:
LocalTime localTime = LocalTime.of(4, 14, 20, 670); // PT0.00000067S Duration fromLocalTime = Duration.ofNanos(localTime.getNano());
最后,可以从遵循 ISO-8601 持续时间格式PnDTnHnMn.nS的String对象获得Duration,其中天被认为正好是 24 小时。例如,P2DT3H4M字符串有 2 天 3 小时 4 分钟:
Duration durationFromString = Duration.parse("P2DT3H4M");
调用Duration.toString()将返回符合 ISO-8601 持续时间格式的持续时间PnDTnHnMn.nS(例如,PT10H、PT3M或PT51H4M)。
但是,与Period的情况一样,当Duration用于表示两次之间的时间段(例如,Instant时,揭示了它的真实功率。从 2015 年 11 月 3 日 12:11:30 到 2016 年 12 月 6 日 15:17:10 之间的持续时间可以表示为两个Instant类之间的差异,如下所示:
Instant startInstant = Instant.parse("2015-11-03T12:11:30.00Z"); Instant endInstant = Instant.parse("2016-12-06T15:17:10.00Z"); // PT10059H5M40S Duration durationBetweenInstant = Duration.between(startInstant, endInstant);
以秒为单位,可通过Duration.getSeconds()
方法获得该差值:
durationBetweenInstant.getSeconds(); // 36212740 seconds
或者,从 2018 年 3 月 12 日 04:14:20.000000670 到 2019 年 7 月 20 日 06:10:10.000000720 之间的持续时间可以表示为两个LocalDateTime
对象之间的差异,如下所示:
LocalDateTime startLocalDateTime = LocalDateTime.of(2018, 3, 12, 4, 14, 20, 670); LocalDateTime endLocalDateTime = LocalDateTime.of(2019, 7, 20, 6, 10, 10, 720); // PT11881H55M50.00000005S, or 42774950 seconds Duration durationBetweenLDT = Duration.between(startLocalDateTime, endLocalDateTime);
最后,04:14:20.000000670 和 06:10:10.000000720 之间的持续时间可以表示为两个LocalTime
对象之间的差异,如下所示:
LocalTime startLocalTime = LocalTime.of(4, 14, 20, 670); LocalTime endLocalTime = LocalTime.of(6, 10, 10, 720); // PT1H55M50.00000005S, or 6950 seconds Duration durationBetweenLT = Duration.between(startLocalTime, endLocalTime);
在前面的例子中,Duration通过Duration.getSeconds()方法以秒表示,这是Duration类中的秒数。然而,Duration类包含一组方法,这些方法专用于通过toDays()以天为单位、通过toHours()以小时为单位、通过toMinutes()以分钟为单位、通过toMillis()以毫秒为单位、通过toNanos()以纳秒为单位来表达Duration。
从一个时间单位转换到另一个时间单位可能会产生残余。例如,从秒转换为分钟可能导致秒的剩余(例如,65 秒是 1 分钟,5 秒是剩余)。残差可以通过以下一组方法获得:天残差通过toDaysPart(),小时残差通过toHoursPart(),分钟残差通过toMinutesPart()等等。
假设差异应该显示为天:小时:分:秒:纳秒(例如,9d:2h:15m:20s:230n)。将toFoo()和toFooPart()方法的力结合在一个辅助方法中将产生以下代码:
public static String durationToDHMSN(Duration duration) { StringBuilder sb = new StringBuilder(); sb.append(duration.toDays()) .append("d:") .append(duration.toHoursPart()) .append("h:") .append(duration.toMinutesPart()) .append("m:") .append(duration.toSecondsPart()) .append("s:") .append(duration.toNanosPart()) .append("n"); return sb.toString(); }
让我们调用这个方法durationBetweenLDT
(差别是 495 天 1 小时 55 分 50 秒 50 纳秒):
// 495d:1h:55m:50s:50n durationToDHMSN(durationBetweenLDT);
与Period类相同,Duration类有一个名为isNegative()的标志方法。当确定某个特定时间是否早于另一个时间时,此方法很有用。有持续时间A和持续时间B,如果B在A之前,应用Duration.between(A, B)的结果可以是负的,如果A在B之前,应用Duration.between(A, B)的结果可以是正的,进一步逻辑,isNegative()如果B在A之前,则返回true,如果A在B之前,则返回false,如以下情况:
durationBetweenLT.isNegative(); // false
最后,Duration可以通过增加或减少持续时间来修改。有plusDays()、plusHours()、plusMinutes()、plusMillis()、plusNanos()、minusDays()、minusHours()、minusMinutes()、minusMillis()和minusNanos()等方法来执行此操作。例如,向durationBetweenLT添加 5 小时可以如下所示:
Duration durationBetweenPlus5Hours = durationBetweenLT.plusHours(5);
添加两个Duration
类可以通过Duration.plus()
方法完成,如下所示:
Duration d1 = Duration.ofMinutes(20); Duration d2 = Duration.ofHours(2); Duration d1d2 = d1.plus(d2); System.out.println(d1 + "+" + d2 + "=" + d1d2); // PT2H20M
64 获取日期和时间单位
对于Date
对象,解决方案可能依赖于Calendar
实例。绑定到本书的代码包含此解决方案。
对于 JDK8 类,Java 提供了专用的getFoo()
方法和get(TemporalField field)
方法。例如,假设下面的LocalDateTime
对象:
LocalDateTime ldt = LocalDateTime.now();
依靠getFoo()
方法,我们得到如下代码:
int year = ldt.getYear(); int month = ldt.getMonthValue(); int day = ldt.getDayOfMonth(); int hour = ldt.getHour(); int minute = ldt.getMinute(); int second = ldt.getSecond(); int nano = ldt.getNano();
或者,依赖于get(TemporalField field)
结果如下:
int yearLDT = ldt.get(ChronoField.YEAR); int monthLDT = ldt.get(ChronoField.MONTH_OF_YEAR); int dayLDT = ldt.get(ChronoField.DAY_OF_MONTH); int hourLDT = ldt.get(ChronoField.HOUR_OF_DAY); int minuteLDT = ldt.get(ChronoField.MINUTE_OF_HOUR); int secondLDT = ldt.get(ChronoField.SECOND_OF_MINUTE); int nanoLDT = ldt.get(ChronoField.NANO_OF_SECOND);
请注意,月份是从 1 开始计算的,即 1 月。
例如,2019-02-25T12:58:13.109389100的LocalDateTime对象可以被切割成日期时间单位,结果如下:
Year: 2019 Month: 2 Day: 25 Hour: 12 Minute: 58 Second: 13 Nano: 109389100
通过一点直觉和文档,很容易将此示例改编为LocalDate
、LocalTime
、ZonedDateTime
和其他示例。
65 日期时间的加减
这个问题的解决方案依赖于专用于处理日期和时间的 Java API。让我们在下一节中看看它们。
使用Date
对于Date
对象,解决方案可能依赖于Calendar
实例。绑定到本书的代码包含此解决方案。
使用LocalDateTime
跳转到 JDK8,重点是LocalDate、LocalTime、LocalDateTime、Instant等等。新的 Java 日期时间 API 提供了专门用于加减时间量的方法。LocalDate、LocalTime、LocalDateTime、ZonedDateTime、OffsetDateTime、Instant、Period、Duration以及许多其他方法,如plusFoo()和minusFoo(),其中Foo可以用单位替换时间(例如,plusYears()、plusMinutes()、minusHours()、minusSeconds()等等)。
假设如下LocalDateTime
:
// 2019-02-25T14:55:06.651155500 LocalDateTime ldt = LocalDateTime.now();
加 10 分钟和调用LocalDateTime.plusMinutes(long minutes)一样简单,减 10 分钟和调用LocalDateTime.minusMinutes(long minutes)一样简单:
LocalDateTime ldtAfterAddingMinutes = ldt.plusMinutes(10); LocalDateTime ldtAfterSubtractingMinutes = ldt.minusMinutes(10);
输出将显示以下日期:
After adding 10 minutes: 2019-02-25T15:05:06.651155500 After subtracting 10 minutes: 2019-02-25T14:45:06.651155500
除了每个时间单位专用的方法外,这些类还支持plus/minus(TemporalAmount amountToAdd)和plus/minus(long amountToAdd, TemporalUnit unit)。
现在,让我们关注Instant类。除了plus/minusSeconds()、plus/minusMillis()、plus/minusNanos()之外,Instant类还提供了plus/minus(TemporalAmount amountToAdd)方法。
为了举例说明这个方法,我们假设如下Instant:
// 2019-02-25T12:55:06.654155700Z Instant timestamp = Instant.now();
现在,让我们加减 5 个小时:
Instant timestampAfterAddingHours = timestamp.plus(5, ChronoUnit.HOURS); Instant timestampAfterSubtractingHours = timestamp.minus(5, ChronoUnit.HOURS);
输出将显示以下Instant
:
After adding 5 hours: 2019-02-25T17:55:06.654155700Z After subtracting 5 hours: 2019-02-25T07:55:06.654155700Z