如何在程序中表示出一个时间段呢?有人想到可以用ChronoUnit枚举值来表示,但ChronoUnit的每一个枚举值都只是一个时间单位,如果想准确的表示出一个时间段,还必须加上一个时间单位的数量。例如程序中想表示一个长度为3天的时间段,就必须用整数3和ChronoUnit.DAYS枚举值这两个数据来表示。这种表示方式导致必须把时间的数量和时间的单位组合起来才能表示出一个时间段,因此在程序中无法把一个时间段当作对象来看待。为了解决这个问题,Java8新日期时间系统中专门定义了两个用于表示时间段的类,它们分别是Period和Duration。其中Period用来表示比较长的时间段,它的计量单位是年、月、日,而Duration用来表示比较短的时间段,它的计量单位是秒和纳秒。本小节重点讲解这两个类的使用。
10.5.1创建Period类对象
Period类提供了专门用于创建对象的静态方法。其中of()方法能通过够指定年、月、日这3个时间单位来创建出一个Period类的对象,而ofYears()、ofMonths()、ofWeeks()和ofDays()这4个方法则可以通过指定年、月、星期和天的数量来创建出Period类的对象。实际上,通过计算两个日期相差多长时间也能得到一个以Period对象表示的时间段。10.4小节中曾经讲过通过until()方法就能计算出两个日期相差多长时间,而Period类的between()静态方法也能计算出两个日期相差多长时间。下面的【例10_15】展示了如何创建和计算出一个Period类的对象。
【例10_15 创建和计算Period类对象】
Exam10_15.java
【例10_15】用各种方式创建了Period类对象,并且通过计算ld1和ld2两个日期间隔多久也能获得Period类对象。通过阅读代码可以发现,通过until()方法也能获得一个Peroid类对象,但是需要提醒读者:只有定义在LocalDate类中的until()方法才能获得Peroid类对象,LocalTime和LocaDateTime这两个类中的until()方法没有这个功能。【例10_15】的运行结果如图10-12所示。
图10-12【例10_15】运行结果
从图10-12可以看出:所有Period类对象被输出到控制台上的结果都是以字母P开头,字母P就代表了Period类。Period类对象的输出结果中都Y、M、D这样的字母,这些字母是年、月、日英文单词的首字母,例如“1Y3M2D”就代表1年3个月零2天。以星期为单位构建的Period类对象会被转换成以天为单位的Peroid类对象,输出结果中的“P140D”就代表20周。此外还可以看出:通过of()方法创建的Peroid类对象不会产生进位,例如p2对象中的月份数值为15,15个月已经超过了一年的长度,但p2对象并没有把15个月转换成1年零3个月。
10.5.2获取和修改Period对象时间分量
Period类对象中有年、月、日3个时间分量共同来记录时间段的长度。程序员可以通过Period类所提供的相应方法来获取或修改这些时间分量的值,下面的表10-10列出了用于获取和修改Period类对象时间分量的方法。
表10-10 Period类获取和修改时间分量的方法
方法 |
作用 |
int getYears() |
获得年的数值 |
int getMonths() |
获得月的数值 |
int getDays() |
获得天的数值 |
longget() |
获得参数所指定时间分量的数值 |
Period withYears() |
修改年的数值 |
Period withMonths() |
修改月的数值 |
Period withDays() |
修改天的数值 |
下面的【例10_16】展示了如何使用这些方法获取和修改时间分量。
【例10_16 获取和修改Period类对象时间分量】
Exam10_16.java
【例10_16】用两种方式获得了p1对象的年、月、日时间分量,并且通过withYears()方法修改了p2对象的年数值。需要注意:Period也是不可变类,因此调用withYears()修改时间分量数值的操作实际上会产生一个新的Period对象。读者可尝试使用withMonths()和withDays()这两个方法修改Period对象的月和日两个时间分量。【例10_16】的运行结果如图10-13所示。
图10-13【例10_16】运行结果
10.5.3 Period对象的相关计算
Period类提供了一系列方法对时间段做各种计算,例如求两个时间段的和或差,此外还可以把一个时间段乘以n,或者求时间段的相反值。表10-11展示了Period类中用于时间段计算的相关方法。
表10-11 Period类计算时间段的方法
方法 |
作用 |
Period plusYears(long years)/Period minusYears(long years) |
增加/减少年的值 |
Period plusMonths(long months)/Period minusMonths(long months) |
增加/减少月的值 |
Period plusDays(long days)/Period minusDays(long days) |
增加/减少天的值 |
Period plus(TemporalAmount add)/Period minus(TemporalAmount minus) |
增加/减少一段时间 |
Period multipliedBy(int n) |
时间段长度乘以n |
Period negated() |
求时间段的相反值 |
表10-11中的negated()方法用于求一个时间段的相反值。所谓“相反值”就是把一个Period对象年、月、日3个时间分量的数值全部取反所得到的Period对象。例如1年的相反值就是-1年。Java语言中允许时间段的长度为负,这与数学中的负数的概念相同,与一个长度为负的时间段相加实际上等同于减去一段时间。
下面的【例10_17】展示了如何使用这些方法完成时间段的相关计算。
【例10_17 Period对象的相关计算1】
Exam10_17.java
【例10_17】的运行结果如图10-14所示。
图10-14【例10_17】运行结果
从图10-14可以看出,两个时间段相加或倍增之后,新的Period对象不会有进位操作。例如p1与p2相加之后的长度为7年19个月40天,其中19个月已经超过一年,但运行结果中并没有把19个月转换为1年零7个月。如果希望把计算结果转换为一个人们习惯的表示方式,可以调用Period类的normalized()来实现,这个方法用于格式化Period对象,它可以把12个月转换成1年。此外,Period类还定义了一个toTotalMonths()方法,这个方法可以计算出一个时间段总共包含多少个月。但这两个方法在进行计算时都忽略了天的数值,因此计算结果在某些情况下并不准确。下面的【例10_18】展示了normalized()和toTotalMonths()这两个方法实际运行效果。
【例10_18 Period对象的相关计算2】
Exam10_18.java
【例10_18】的运行结果如图10-15所示。
图10-15【例10_18】运行结果
从图10-15可以看出,p3对象中天的数值是40,已经超过了一个月,但在对p3对象进行格式化和计算包含多少个月的操作中都忽略了天的数值,因此计算结果并不准确。之所以会忽略Period类对象中天的数值,就是因为天与月直接没有固定的换算关系,无法进行计算,只能选择忽略。可以看出:只有Peroid类对象中天的值少于28时这两个方法的计算结果才是准确的,因为任何情况下少于28天都不够一个月。
在实际开发过程中,Period类对象还可以参与到日期的计算中,例如给一个日期加上或减去一段时间就可以得到另一个日期。Period类中的addTo()方法用来给日期加上一段时间,而subtractFrom()方法用来给日期减去一段时间。LocalDate类中除了定义有plusYears()、minusYears()、plusMonths()、minusMonths()这些以特定时间单位进行计算的方法外,还定义了plus()和minus()两个方法用来计算日期的加减,它们能够达到与addTo()方法和subtractFrom()方法同样的效果。plus()和minus()这两个方法的参数类型都是Period。下面的【例10_19】如何为一个日期加上或减去一个时间段得到另一个日期。
【例10_19 Period对象的相关计算3】
Exam10_19.java
【例10_19】的运行结果如图10-16所示。
图10-16【例10_19】运行结果
10.5.4创建Duration对象
Java8新日期时间系统中的Duration类用来表示更加精确的时间段,它以秒和纳秒作为计量单位。虽然Duration以秒和纳秒为单位计数,但程序员却可以通过设置天、小时、分钟、秒钟、毫秒以及纳秒这些时间单位的值来创建Duration类的对象。下面的表10-12展示了创建Duration类对象所的相关方法。
表10-12 创建Duration对象的相关方法
方法 |
作用 |
Duration ofDays(long days) |
以天为单位创建Duration对象 |
Duration ofHours(long hours) |
以小时为单位创建Duration对象 |
Duration ofMinutes(long minutes) |
以分钟为单位创建Duration对象 |
Duration ofSeconds(long seconds) |
以秒钟为单位创建Duration对象 |
Duration ofMillis(long millis) |
以毫秒为单位创建Duration对象 |
Duration ofNanos(long nanos) |
以纳秒为单位创建Duration对象 |
Duration of(long amount, TemporalUnit unit) |
以指定时间单位创建Duration对象 |
在表10-12所列出的方法中,ofSeconds()方法有两个版本,第一个版本仅以秒为单位创建Duration对象,第二个版本以秒和纳秒两个时间单位共同创建Duration类对象。下面的【例10_20】展示了Duration类对象创建的过程。
【例10_20 创建Duration类对象1】
Exam10_20.java
【例10_20】的运行结果如图10-17所示。
图10-17【例10_20】运行结果
从图10-17可以看出:所有Duration类对象被显示到控制上时都以“PT”开头,其中字母P是单词Period的首字母,而T是单词Time的首字母。每一个Duration类对象都以时、分、秒为单位进行显示,例如d1代表长度为3天的时间段,但它被显示为“PT72H”,意为72小时。此外,在创建d4对象时,为方法传递的第一个参数为100,这个参数代表100秒,而第二个参数的值为-2000000,这个参数值代表-2000000纳秒。在创建对象时,会用100秒减去2000000纳秒,从而得到“PT1M39.998S”这个输出结果表示1分钟零39.998秒。
通过计算两个LocalTime或LocalDateTime类对象所代表的时间点相隔多久也能得到一个Duration对象。这样的计算需要通过Duration类的between()方法完成。下面的【例10_21】展示了如何通过between()方法计算两个时间点的差值得到一个Duration对象。
【例10_21 创建Duration类对象2】
Exam10_21.java
【例10_21】中,语句①计算了两个LocalTime类对象之间的时间差。因为LocalTime类对象没有年、月、日的值,因此在计算这两个对象之间的时间差时是把这两个对象当作同一天当中的两个时间点进行计算的,计算出的时间差不会超过24小时。语句②计算两个LocalDateTime类对象的时间差,因为LocalDateTime类对象中不仅包含时、分、秒的值,还包含年、月、日的值,因此在进行计算时会把年、月、日的值一同计算在内。语句③比较特殊,这条语句计算了一个LocalTime类对象和一个LocalDateTime类对象的时间差。LocalTime类没有年、月、日的值,因此在计算过程中把LocalDateTime类对象中的年、月、日值全部忽略掉,只用时、分、秒的值的值与LocalTime类对象计算时间差。需要特别注意:这种特殊的计算会以between()方法的第一个参数的类型为标准进行计算。也就是说,如果把LocalDateTime类对象作为方法的第一个参数,而把LocalTime类对象作为方法的第二个参数,那么在进行计算时会把第二个参数由LocalTime类对象转换成LocalDateTime类对象,但LocalTime类对象中缺少含年、月、日的值,所以转换不会成功,程序运行时会出现异常。【例10_21】的运行结果如图10-18所示。
图10-18【例10_21】运行结果
10.5.5获取和修改Duration对象时间分量
同Period类一样,Duration也提供了获取和修改时间分量的方法。虽然程序员可以使用天、小时、分钟、秒钟、毫秒以及纳秒为单位创建Duration对象,但Duration类对象中只记录秒和纳秒的值,如果创建长度为1分钟的的Duration类对象时,会把1分钟转换为为60秒钟存储在对象中。介于这个原因,Duration类只提供了getSeconds()和getNano()这两个方法来获得秒和纳秒的数值,修改时间分量的方法也只有withSeconds()和withNanos()。但是需要提醒各位读者:Duration对象中纳秒的数值不能是负数,所以使用withNanos()方法修改纳秒数值时所传递的参数如果是负数,程序就会在运行时出现异常。【例10_20】中,创建d4对象时把纳秒的数值设为-2000000,但实际创建对象时,会把秒的数值减少1,然后用这“借来”的1秒再减去2000000纳秒,因此最终对象中纳秒的数值为998000000,读者可以调用d4对象的getNano()来验证这个结论。
虽然Duration类只提供了两个获取时间分量的方法,但程序员可以用一系列方法把一个Duration对象换算成各种时间单位的值。例如可以把7200秒换算为2小时,但是必须强调:这种换算都会忽略小数部分。例如7236秒如果换算成小时的结果为2,而不是2.01。下面的表10-13展示了用于时间换算的方法。
表10-13 换算时间的方法
方法 |
作用 |
long toDays() |
换算成天 |
long toHours() |
换算成小时 |
long toMinutes() |
换算成分钟 |
long toSeconds() |
换算成秒钟 |
long toMillis() |
换算成毫秒 |
long toNanos() |
换算成微秒 |
表10-13中的方法在完成换算的过程中会忽略“零头”,为了让程序员找回这些零头,Duration类还专门提供了用于计算零头的方法。零头是一个相对的概念,人们总是用某个时间单位去描述一个时间段的长度,而用一个更小的时间单位来描述这个时间段的零头部分。如果用天为单位来描述一个时间段,不足1天的那几个小时就是零头。例如一个时间段的长度是100天零5小时,其中5小时这一部分不足一天,它就是这个时间段的零头部分。在这个例子中,用“天”这个单位描述时间段的长度而用一个更小的时间单位,也就是“小时”来描述零头的长度。下面的表10-14列出了用不同单位计算零头的方法。
表10-14 以不同单位计量零头的方法
方法 |
作用 |
longtoDaysPart() |
以天为单位计算零头长度 |
int toHoursPart() |
以小时为单位计算零头长度 |
int toMinutesPart() |
以分钟为单位计算零头长度 |
int toSecondsPart() |
以秒钟为单位计算零头长度 |
int toMillisPart() |
以毫秒为单位计算零头长度 |
int toNanosPart() |
以微秒为单位计算零头长度 |
需要注意,在计算零头时,每个方法的计算结果也会忽略更小零头。例如一个时间段的长度为3小时5分30秒,如果以小时为单位描述这个时间段,那么5分30秒就是零头,但是如果调用toMinutesPart()计算零头的话,所得到的结果是5(分钟)而不是5.5(分钟),这个结果说明方法在进行计算时把30秒这个更小的零头忽略掉了。由于Duration类对象所能换算的最大时间单位是天,因此调用toDaysPart()方法计算零头的长度,其结果与toDays()方法的计算整个时间段长度的结果是相同的。
下面的【例10_22】展示了把一个时间段换算成特定单位并且计算零头的过程。
【例10_22 时间的换算】
Exam10_22.java
【例10_22】的运行结果如图10-19所示。
图10-19【例10_22】运行结果
10.5.6 Duration对象的相关计算
与Period类一样,Duration类也提供了很多用于时间段运算的方法。需要特别说明:Duration类比Period类多了一个用于做除法运算的dividedBy()方法,这个方法不仅仅可以求出时间段长度的n分之一,还可以求出一个时间段是另一个时间段的多少倍。此外,Duration类还设置了一个求时间段绝对值的方法abs()。表10-15展示了Duration类中用于时间段计算的相关方法。
表10-15 Duration类计算时间段的方法
方法 |
作用 |
plusDays(long d)/minusDays(long d) |
以天为单位增加/减少时间段的长度 |
plusHours(long h)/minusHours(long h) |
以小时为单位增加/减少时间段的长度 |
plusMinutes(long m)/minusMinutes(long m) |
以分钟为单位增加/减少时间段的长度 |
plusSeconds(long s)/minusSeconds(long s) |
以秒钟为单位增加/减少时间段的长度 |
plusMillis(long m)/minusMillis(long m) |
以毫秒为单位增加/减少时间段的长度 |
plusNanos(long n)/minusNanos(long n) |
以纳秒为单位增加/减少时间段的长度 |
plus(Duration d)/minus(Duration d) |
增加/减少一段时间 |
multipliedBy(long n) |
时间段长度乘以n |
dividedBy(long n)/ dividedBy(Duration d) |
求时间段长度的n分之一或是另一个时间段的多少倍 |
negated() |
求时间段的相反值 |
实际上,两个Duration对象还可以进行比较长度的操作,这个操作通过Duration类的compareTo()方法完成。如果当前对象的长度小于参数对象compareTo()方法返回-1,如果当前对象的长度等于参数对象compareTo()方法返回0,如果当前对象的长度大于参数对象compareTo()方法返回1。需要提醒各位读者:Period类中没有定义用于比较时间段长度的compareTo()方法,其原因是Period类对象所代表的时间段长度并不确定,例如1个月的长度可能是30天也可能是31天,因此无法做出比较。下面的【例10_23】展示了如何使用表10-15中的方法完成时间段的相关计算。
【例10_23 Duration对象的相关计算1】
Exam10_23.java
【例10_23】的运行结果如图10-20所示。
图10-20【例10_23】运行结果
Period对象在做加减操作时不会产生进位和退位。但从图10-20可以看出:Duration对象在做加减操作的时候,秒和纳秒这两个时间分量的数值会产生进位和退位操作。这是因为天和月之间没有固定的换算关系,而秒和纳秒之间却有非常明确的换算关系,因此可以完成进位和退位操作。此外还需说明:求一个Duration对象是另一个Duration对象的几倍,如果所求出的倍数不是一个整数,例如出现了3.5倍的情况,那么小数部分将直接被砍掉。
为了使对象能够参与到时间的计算中,Duration类也定义了addTo()方法和subtractFrom()方法用于时间的加减。下面的【例10_24】展示了Duration对象参与时间计算的效果。
【例10_24 Duration对象的相关计算2】
Exam10_24.java
【例10_24】的运行结果如图10-21所示。
图10-21【例10_24】运行结果
从图10-21可以看出:使用addTo()和subtractFrom()方法进行计算时,如果参数是LocalDateTime类对象,那么加减时间段的操作还会影响到LocalDateTime类对象的日期数值。语句①中的运行结果为“00:25:36”,读者可以理解为:在lt对象中也包含一个日期,lt对象加上100小时后到达了某年某月的0点25分36秒,之后把那个假想的日期部分去掉,就只剩0点25分36秒,这就是最终的运算结果。根据运行结果可以得出:addTo()和subtractFrom()这两个方法在参数是LocalTime类对象的情况下,无论增加或减少多长的时间段,LocalTime类对象小时这个时间分量的数值都会保持在0到23之间。
本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。