Java8新日期时间系统中LocalDate用来表示日期,例如某员工的入职日期就可以用LocalDate类的对象来表示。在LocalDate类对象中只包含年、月、日的数值,不包含时、分、秒的数值。相对应的,新日期时间系统中用LocalTime来表示时间,LocalTime类对象中只包含只包含时、分、秒的数值而不包含年、月、日的数值,因此LocalTime类对象特别适合用来表示不包含年月日的时间,例如某路公交车的首发时间就可以用LocalTime类的对象来表示,因为这一路公交车无论哪一天的首发时间都不会改变,因此根本不需要表示出日期。有时候程序中需要精确的表示出某件事情发生的日期和时间,比如在电商系统中,需要记录每一次交易发生的时间,此时不仅仅要记录交易发生的日期,还需要记录交易发生在几点几分几秒。针对这样的业务需求,Java8新日期时间系统专门提供了一个叫做LocalDateTime的类,这个类的对象能够表示出日期和时间两部分信息,因此这个类的对象包含年、月、日、时、分、秒这六个时间分量的数值。从这个类的名称也可以看出:LocalDateTime类对象是由一个表示日期的LocalDate类对象和一个表示时间的LocalTime类对象组合而成的。
LocalDate、LocalTime和LocalDateTime这3个类虽然表示的概念不同,但它们的使用方式却非常类似,本小节重点来讲解这3个类的使用。
10.4.1 对象的创建
Java8新日期时间系统中的类都是通过静态方法创建的,LocalDate、LocalTime和LocalDateTime这3个类也不例外。如果希望创建一个表示操作系统当前日期时间的对象,需要调用now()静态方法完成,如果创建一个表示特定日期或时间对象,则需要调用of()静态方法完成。下面的【例10_08】展示了如何创建这3个类的对象。
【例10_08 创建日期时间类对象】
Exam10_08.java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
public class Exam10_08 {
public static void main(String[] args){
//创建表示当前日期的对象
LocalDate localDate1 = LocalDate.now();
//创建表示2022年3月10日的对象
LocalDate localDate2 = LocalDate.of(2022,3,10);//①
//创建表示2022年3月10日的对象
LocalDate localDate3 = LocalDate.of(2022, Month.MARCH,10);//②
//创建表示当前时间的对象
LocalTime localTime1 = LocalTime.now();
//创建表示16点25分的对象
LocalTime localTime2 = LocalTime.of(16,25);
//创建表示16点25分15秒的对象
LocalTime localTime3 = LocalTime.of(16,25,15);
//创建表示当前日期时间的对象
LocalDateTime localDateTime1 = LocalDateTime.now();
//创建表示2022年3月10日16点25分15秒的对象
LocalDateTime localDateTime2 = LocalDateTime.of(2022,3,10,16,25,15);
//创建表示2022年3月10日16点25分15秒的对象
LocalDateTime localDateTime3 = LocalDateTime.of(localDate2,localTime3);//③
System.out.println(localDate1);
System.out.println(localDate2);
System.out.println(localDate3);
System.out.println(localTime1);
System.out.println(localTime2);
System.out.println(localTime3);
System.out.println(localDateTime1);
System.out.println(localDateTime2);
System.out.println(localDateTime3);
}
}
从【例10_08】的程序代码可以看出: LocalDate、LocalTime和LocalDateTime这3个类创建对象的方式都是一样的。如果创建一个表示当前日期或时间的对象,这3个类都是调用now()方法完成的,而如果创建一个表示特定日期或时间的对象,都是调用of()方法完成。虽然of()方法的参数比较多,但这些参数的意义却非常简单易记,它们都表示各种时间分量,并且这些参数有两个特点。首先,这些表示时间分量的参数都是按照时间单位从大到小排列的,例如LocalDate类的of()方法有3个参数,按照单位的大小顺序,这3个参数分别代表年、月、日。其次,这些参数的值都代表了真实的时间分量,例如把LocalDate类的of()方法第二个参数设置为2,那么这个参数就表示2月而不是3月。从这两个特点可以看出:Java8新日期时间系统中的类设计非常合理,程序员只要按照正常书写时间日期的方式就能创建出正确的对象,不必在传递参数时修正时间分量的数值。
【例10_08】中创建LocalDate类对象时,代表月份的参数可以是一个整数,也可以是一个枚举值。代码中语句①在创建对象时把月份设置为整数,而语句②把月份设置为一个枚举值。此外,代码中语句③在创建LocalDateTime类对象时采用了一种特殊的方式,这种方式在创建对象时把一个LocalDate对象和一个LocalTime对象作为参数传递给of()方法。这说明LocalDateTime类对象是由一个表示日期的LocalDate类对象和一个表示时间的LocalTime类对象组合而成的。
【例10_08】的运行结果如图10-6所示。
图10-6【例10_08】运行结果
从图10-6可以看出,LocalDate和LocalTime类的对象不需要经过格式化就能被显示为一种国际通用的日期和时间格式,这使得任何国家的人都能很容易的看懂输出结果。此外还可以看到:表示当前时间的LocalTime对象可以精确掉小数点之后的9位,达到了纳秒级别。如果是输出一个LocalDateTime类对象,那么这个对象是由日期和时间两部分组成,并且日期和时间中间用大写字母T分开。需要提醒各位读者:程序中的localDate1、LocalTime1和localDateTime1都代表的是系统当前的日期时间,因此读者在实际运行程序时,这3个对象的输出结果以操作系统的实际日期时间为准,与图10-6中的输出结果不一定相同。
10.4.2获取时间分量
LocalDate、LocalTime和LocalDateTime这3个类都提供了很多获取时间分量的方法,下面的表10-6列出了所有获得时间分量方法的意义和作用。
表10-6 获得时间分量的方法
方法名称 | 所属类 | 作用 |
int getYear() | LocalDate、LocalDateTime | 获得年份 |
int getMonth() | LocalDate、LocalDateTime | 获得月份枚举值 |
int getMonthValue() | LocalDate、LocalDateTime | 获得月份数值(范围1-12) |
int getDayOfMonth() | LocalDate、LocalDateTime | 获得日期 |
int getDayOfYear() | LocalDate、LocalDateTime | 计算日期位于一年中的第几天 |
int getDayOfWeek() | LocalDate、LocalDateTime | 获得星期枚举值 |
int getHour() | LocalTime、LocalDateTime | 获得小时 |
int getMinute() | LocalTime、LocalDateTime | 获得分钟 |
int getSecond() | LocalTime、LocalDateTime | 获得秒钟 |
int getNano() | LocalTime、LocalDateTime | 获得纳秒 |
从表10-6可以看出:通过getDayOfWeek()获得的星期是一个枚举值,如果想把这个枚举值转换为数字,需要调用枚举对象的getValue()方法获得。getValue()方法所获得的星期数值范围是1-7,其中数字1代表星期一,数字2代表星期二,以此类推,数字7代表星期天。下面的【例10_09】展示了如何使用表10-6中的方法获得各项时间分量。
【例10_09 获取时间分量1】
Exam10_09.java
import java.time.LocalDateTime;
public class Exam10_09 {
public static void main(String[] args){
//创建表示2022年3月10日16点25分15秒的对象
LocalDateTime localDateTime = LocalDateTime.of(2022,3,10,16,25,15);
System.out.println("年:"+localDateTime.getYear());
System.out.println("月:"+localDateTime.getMonthValue());
System.out.println("日:"+localDateTime.getDayOfMonth());
System.out.println("时:"+localDateTime.getHour());
System.out.println("分:"+localDateTime.getMinute());
System.out.println("秒:"+localDateTime.getSecond());
System.out.println("星期(枚举):"+localDateTime.getDayOfWeek());
System.out.println("星期(数值):"+localDateTime.getDayOfWeek().getValue());
}
}
因为LocalDateTime类兼具有LocalDate和LocalTime两个类的获得时间分量方法,因此代码中选用LocalDateTime类对象为例进行演示。【例10_09】的运行结果如图10-7所示。
图10-7【例10_09】运行结果
实际上,LocalDate、LocalTime和LocalDateTime这3个类还提供了get()方法获得时间分量。这个get()方法与Calendar类的get()方法一样,程序员也要通过为get()方法传递不同的参数来表明要获得哪一个时间分量。get()方法的参数是一个枚举,这个枚举叫做ChronoField。下面的表10-7列出了ChronoField常用枚举值所代表的时间分量。
表10-7 ChronoField枚举值的意义
枚举值 | 适用类 | 意义 |
YEAR | LocalDate、LocalDateTime | 年 |
YEAR_OF_ERA | LocalDate、LocalDateTime | 从公元1年开始算的年(效果与YEAR相同) |
MONTH_OF_YEAR | LocalDate、LocalDateTime | 月(范围1-12) |
DAY_OF_MONTH | LocalDate、LocalDateTime | 日期 |
DAY_OF_WEEK | LocalDate、LocalDateTime | 星期(范围1-7) |
HOUR_OF_DAY | LocalTime、LocalDateTime | 小时(范围0-23) |
MINUTE_OF_HOUR | LocalTime、LocalDateTime | 分钟 |
SECOND_OF_MINUTE | LocalTime、LocalDateTime | 秒钟 |
需要特别注意:ChronoField枚举值有一定的适用范围。例如不能为LocalTime类对象的get()方法传递枚举值YEAR作为其参数,因为YEAR表示年份,而LocalTime类中根本不包含年的数值。下面的【例10_10】展示了如何使用get()方法来获得时间分量。
【例10_10 获取时间分量2】
Exam10_10.java
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
public class Exam10_10 {
public static void main(String[] args){
//创建表示2022年3月10日16点25分15秒的对象
LocalDateTime localDateTime = LocalDateTime.of(2022,3,10,16,25,15);
System.out.println("年:"+localDateTime.get(ChronoField.YEAR));
System.out.println("月:"+localDateTime.get(ChronoField.MONTH_OF_YEAR));
System.out.println("日:"+localDateTime.get(ChronoField.DAY_OF_MONTH));
System.out.println("时:"+localDateTime.get(ChronoField.HOUR_OF_DAY));
System.out.println("分:"+localDateTime.get(ChronoField.MINUTE_OF_HOUR));
System.out.println("秒:"+localDateTime.get(ChronoField.SECOND_OF_MINUTE));
System.out.println("星期:"+localDateTime.get(ChronoField.DAY_OF_WEEK));
}
}
读者可以自行运行【例10_10】以观察使用get()方法获得各种时间分量的效果。
10.4.3修改时间分量
LocalDate、LocalTime和LocalDateTime这3个类不仅仅能够获得时间分量,还可以修改时间分量。需要特别说明:Java8新日期时间系统中的类都是不可变类,因此修改时间分量的操作并不是在原有对象上直接进行的,而是会产生一个新对象。修改时间分量需要用到一系列以with开头的方法,下面的表10-8展示了直接修改时间分的方法。
表10-8修改时间分量的方法
方法名称 | 所属类 | 作用 |
withYear() | LocalDate、LocalDateTime | 修改年份 |
withMonth() | LocalDate、LocalDateTime | 修改月份 |
withDayOfMonth() | LocalDate、LocalDateTime | 修改日期 |
withHour() | LocalTime、LocalDateTime | 修改小时 |
withMinute() | LocalTime、LocalDateTime | 修改分钟 |
withSecond() | LocalTime、LocalDateTime | 修改秒钟 |
表10-8没有标出所列方法的返回值类型,这是因为这些方法的返回值与方法所属类相同,例如定义在LocalTime类中的withSecond()方法返回值类型为LocalTime,定义在LocalDateTime类中的withSecond()方法返回值类型为LocalDateTime。LocalDate、LocalTime和LocalDateTime这3个类还提供了一个with()方法用于修改各种时间分量,这个方法也是通过参数指定要修改哪一个时间分量。with()方法有两个参数,第一个参数是ChronoField枚举,它用于指定修改哪一个时间分量,而第二个参数则指定把时间分量的值修改为多少。下面的【例10_11】展示了如何修改时间分量。
【例10_11 修改时间分量2】
Exam10_11.java
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
public class Exam10_11 {
public static void main(String[] args){
//创建表示2022年3月10日16点25分15秒的对象
LocalDateTime localDateTime = LocalDateTime.of(2022,3,10,16,25,15);
System.out.println(localDateTime.withYear(2023));//①修改年份为2023年
System.out.println(localDateTime.withMonth(8));//②修改月份为8月
System.out.println(localDateTime.withHour(18));//修改时间为18点
System.out.println(localDateTime.with(ChronoField.MINUTE_OF_HOUR,20));//修改分钟为20分
}
}
【例10_11】的运行结果如图10-8所示。
图10-8【例10_11】运行结果
从图10-8可以看出:语句①把年份改为2023,而语句②对月份进行修改时,localDateTime对象的年份依然是2022,这说明语句①不是直接修改了localDateTime对象的年份,由此证明localDateTime是一个不可变类。
10.4.4日期时间的比较
程序中经常要拿两个日期或时间对象进行比较,例如比较两个日期是不是同一天,或者比较二者之间的早晚关系。Java8新日期时间系统中定义了一些专门日期时间关系的方法,如表10-9所示。
表10-9 判断日期时间关系的方法
方法名称 | 作用 |
boolean equals() | 判断两个对象所代表的日期或时间是否相同 |
boolean isBefore() | 判断当前对象所代表的日期或时间是否早于参数对象 |
boolean isAfter() | 判断当前对象所代表的日期或时间是否晚于参数对象 |
需要注意,表10-9所列出的3个方法都不能一次性确定两个日期或时间的确切关系。例如,有两个LocalDate类的对象date1和date2,如果使用equals ()方法判断date1和date2这两个对象是否相同,假如比较结果是false,我们只能知道date1和date2不是同一天,但它们所代表的日期到底哪一个更早就无从得知。同样,如果调用isBefore()方法来判断date1是不是比date2出现的更早,假如比较结果是false,我们还是没有办法确切的知道date1和date2代表同一天,还是date1比date2出现的更晚,因为这两种情况下调用isBefore方法判断得到的结果都是false。
如果想要一次性确定两个日期或时间的早晚关系,就必须要调用compareTo()方法来实现,这个方法的返回值为int类型,通过返回值就能确定两个对象的确切关系。例如调用date1的compareTo()方法,并以date2作为方法的参数,如果返回值是一个正数,说明date2比date1要晚,如果返回值为0,说明date2和date1是同一天,如果返回值为负数说明date2比date1要早。下面的【例10_12】展示了如何对日期时间进行比较。
【例10_12 日期时间的比较】
Exam10_12.java
import java.time.LocalDateTime;
public class Exam10_12 {
public static void main(String[] args){
LocalDateTime ldt1 = LocalDateTime.of(2022,3,10,16,25,15);
LocalDateTime ldt2 = LocalDateTime.of(2022,5,10,16,25,15);
System.out.println("ldt1与ldt2相同:"+ldt1.equals(ldt2));
System.out.println("ldt1比ldt2更早:"+ldt1.isBefore(ldt2));
System.out.println("ldt1比ldt2更晚:"+ldt1.isAfter(ldt2));
int r = ldt1.compareTo(ldt2);
System.out.print("ldt1与ldt2之间的关系是:");
if(r<0){
System.out.println("ldt1比ldt2更早");
}else if(r==0){
System.out.println("ldt1与ldt2相同");
}else{
System.out.println("ldt1比ldt2更晚");
}
}
}
【例10_12】的运行结果如图10-9所示。
图10-9【例10_12】运行结果
10.4.4日期时间的计算
实际开发过程中经常需要对日期时间进行一些计算。通常来讲日期时间的计算分为两种情况,第一种情况是计算某一个时间点经过向前或向后推移一段时间后会到达哪一个时间点,例如计算2022年5月18日9点30分经过245天之后是什么时间。第二种情况是计算两个时间点相差了多长时间,例如计算2022年3月12日8点20分与2024年7月31日10点相差了多少分钟。
如果计算在某个时间点的基础上向前或向后推移一段时间到达哪一个时间点,都要用一系列以plus或minus开头的方法,其中以plus开头的方法用于计算未来的时间点,而以minus开头的方法用计算过去的时间点。下面的表10-9列出了这些方法的作用。
表10-9 增减时间分量的方法
方法名称 | 所属类 | 作用 |
plusYears()/minusYears() | LocalDate、LocalDateTime | 以年为单位向前/向后推移 |
plusMonths()/minusMonths() | LocalDate、LocalDateTime | 以月为单位向前/向后推移 |
plusDays()/minusDays() | LocalDate、LocalDateTime | 以天为单位向前/向后推移 |
plusHours()/minusHours() | LocalTime、LocalDateTime | 以小时为单位向前/向后推移 |
plusMinutes()/minusMinutes() | LocalTime、LocalDateTime | 以分钟为单位向前/向后推移 |
plusSeconds()/minusSeconds () | LocalTime、LocalDateTime | 以秒钟为单位向前/向后推移 |
表11-9中所列出的方法也与方法所属的类相同。实际上,LocalDate、LocalTime和LocalDateTime这3个类还提供了plus()和minus()方法用于计算过去或未来的时间点,这两个方法都有两个参数,第一个参数指定推移时间的数量,第二个参数指定推移时间的单位。下面的【例10_13】展示了如何通过以上所介绍的方法计算过去或未来的时间点。
【例10_13 日期时间的计算】
Exam10_13.java
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class Exam10_13 {
public static void main(String[] args){
//创建表示2022年3月10日16点25分15秒的对象
LocalDateTime localDateTime = LocalDateTime.of(2022,3,10,16,25,15);
System.out.println("当前日期时间:"+localDateTime);
System.out.println("3年后:"+localDateTime.plusYears(3));
System.out.println("4个月前:"+localDateTime.minusMonths(4));
System.out.println("300天后:"+localDateTime.plus(300,ChronoUnit.DAYS));
System.out.println("18小时前:"+localDateTime.minus(18,ChronoUnit.HOURS));
}
}
从【例10_13】可以看到,如果使用plus()或minus()方法时,方法的第二个参数是ChronoUnit枚举,这个枚举的每一个值都代表一个时间单位。【例10_13】的运行结果如图10-10所示。
图10-10【例10_13】运行结果
如果希望计算两个时间点之间相差多久,需要用until()方法进行计算。until()方法有两个参数,第一个参数是另一个时间点,第二个参数是用于衡量时间差的单位,这个时间单位通过ChronoUnit枚举指定。下面的【例10_14】展示了如何用until()方法来计算两个时间点。
【例10_14 计算时间差】
Exam10_14.java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class Exam10_14 {
public static void main(String[] args){
LocalDate ld1 = LocalDate.of(2022,6,1);
LocalDate ld2 = LocalDate.of(2024,8,7);
LocalDateTime ldt1 = LocalDateTime.of(2022,3,19,5,48,25);
LocalDateTime ldt2 = LocalDateTime.of(2022,3,20,9,33,48);
System.out.println("ld1与ld2相差的天数:"+ld1.until(ld2, ChronoUnit.DAYS));
System.out.println("ldt1与ldt2相差的小时数:"+ldt1.until(ldt2,ChronoUnit.HOURS));
System.out.println("ldt1与ldt2相差的天数:"+ldt1.until(ldt2,ChronoUnit.DAYS));
}
}
【例10_14】中,语句①调用ld1的until()方法计算时间差,ld2作为until()方法的第一个参数,这表示要计算ld1与ld2的时间差。而以ChronoUnit.DAYS作为until()方法的第二个参数,这表示计算时间差时以天作为计量单位。需要特别说明的是:日期时间对象在计算时间差时不能以对象不支持的时间单位作为第二个参数。例如语句①计算两个LocalDate类对象的时间差,LocalDate类对象中只有年、月、日的数值,没有时、分、秒的数值,所以只能以年、月、日作为时间差的计量单位,而不能以时、分、秒作为计量单位,否则程序运行会出现异常。同理,如果计算两个LocalTime类对象的时间差,就只能以时、分、秒作为计量单位而不能以年、月、日作为计量单位。语句②和③计算了两个LocalDateTime类对象的时间差,由于LocalDateTime对象中既有年、月、日的数值,又有时、分、秒的数值,所以用天和小时作为时间差的计量单位都是可以的。【例10_14】的运行结果如图10-11所示。
图10-11【例10_14】运行结果
从图10-11可以看出:ld1与ld2相差798天。需要说明,时间差的正负也能反映出两个日期时间对象的早晚关系。如果调用ld2的until()方法,并且以ld1作为方法的参数,那么计算出的时间差将会变为-798天。此外,同图10-11还可以看出,ldt1与ldt2相差27小时,27小时应该是1天多一点,但以天作为计量单位时,计算得到的时间差为1天,这说明until()方法的计算结果会忽略“零头”。
本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。