从Java 8 开始,java.time包提供了新的日期和时间API,主要类型有
- 本地日期和时间:
LocalDateTime,LocalDate,LocalTime - 带时区的日期和时间:
ZonedDateTime - 时刻:
Instant - 时区:
ZoneId,ZoneOffset - 时间间隔 :
Duration
以及一套新的用于取代SimpleDateFormat的格式化类型DateTimeFormatter。
和旧的API相比,新API严格区分了时刻、本地日期、本地时间和带时区的日期时间,并且,对日期和时间进行运算更加方便。
此外,新API修正了旧API不合理的常量设计
- Month的范围用1~12 表示 1月到12月
- Week的范围用1~7表示周一到周日
最后,新API的类型几乎全部是不变类型,可以放心使用,不必担心被修改。
LocalDateTime
我们首先来看最常用的LocalDateTime,它表示一个本地日期和时间。
public class Main {public static void main(String[] args) {LocalDate d = LocalDate.now();LocalTime t = LocalTime.now();LocalDateTime dt = LocalDateTime.now();System.out.println(d);System.out.println(t);System.out.println(dt);}}
本地日期和时间通过now() 获取到的总是以当前默认时区返回的,和旧APi不同,LocalDateTime、LocalDate和LocalTime默认严格按照ISO 8601规定的日期和时间格式进行打印。
创建LocalDateTime的三种方式
上述代码其实有一个小问题,在获取3个类型的时候,由于执行一行代码总会消耗一点时间。因此,3个类型的日期和时间很可能对不上(时间的毫秒数基本上不同)。为了保证获取到同一时刻的日期和时间,可以改写
public class Main {public static void main(String[] args) {LocalDateTime dt = LocalDateTime.now();LocalDate d = dt.toLocalDate();LocalTime t = dt.toLocalTime();}}
反过来,通过指定的日期和时间创建LocalDateTime可以通过of()方法
LocalDate d2 = LocalDate.of(2091,12,12);LocalTime t2 = LocalTime.of(14:14:13);LocalDateTime dt2 = LocalDateTime.of(2022,12,12,12,12,12);LocalDateTime dt3 = LocalDateTime.of(d2,t2);
因为严格按照ISO 8601的格式,因此,将字符串转换为LocalDateTime就可以传入标准格式。
LocalDateTime dt = LocalDateTime.parse("2019-11-13T14:14:14");LocalDate d = LocalDate.parse("2019-11-12");LocalTime t = LocalTime.parse("14:13:12");
注意ISO 8601规定的日期和时间分隔符是 T。标准格式如下
- 日期: yyyy-MM-dd
- 时间: HH:mm:ss
- 带毫秒的时间,HH:mm:ss.SSS
- 日期和时间: yyyy-MM-dd’T’HH:mm:ss.SSS
带毫秒的日期和时间:yyyy-MM-dd’T’HH:mm:ss.SSS
格式化日期时间DateTimeFormatter
如果要自定义输出的格式,或者要把一个非ISO 8601格式的字符串解析成
LocalDateTime,可以使用新的DateTimeFormatter。public class Main {public static void main(String[] args) {//自定义格式化DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");System.out.println(dtf.format(LocalDateTime.now()));//用自定义格式解析LocalDateTime dt2 = LocalDateTime.parse("2019/11/39 14:14:14", dtf);System.out.println(dt2);}}
LocalDateTime提供了对日期和时间进行加减的非常简单的链式调用public class Main {public static void main(String[] args) {LocalDateTime dt = LocalDateTime.of(2020,10,21,12,21,12);System.out.println(dt);//加5天减3小时LocalDateTime dt2 = dt.plusDays(5).minusHours(3);System.out.println(dt2);//System.out.println(dt); dt不变//减1月LocalDateTime dt3 = dt2.minusMonth(1);System.out.println(dt3);}}
注意到月份加减会自动调整日期,例如从
2019-10-31减去一个月得到的结果是2019-09-30,因为9月没有31日。
调整日期 (with)
对日期和时间进行调整则使用withXxxx()方法,例如,withHour(15)会把10:11:12变成15:11:12
- 调整年:
withYear() - 调整月:
withMonth() - 调整日:
withDayOfMonth() - 调整时:
withHour() - 调整分:
withMinute() 调整秒:
withSecond()public class Main {public static void main(String[] args) {LocalDateTime ldt1 = LocalDateTime.of(2020,12,12,12,12,12);System.out.pritnln(ldt1);LocalDateTime ldt2 = ldt1.withDayOfMonth(31);System.out.println(ldt2);LocalDateTime ldt3 = Ldt2.withMonth(9);System.out.println(ldt3);}}
同样注意到调整月份时,会调整日期,即把
2019-10-31的月份调整为9时,日期也自动变为30。
实际上,LocalDateTime还有一个通用的with()方法允许我们做更复杂的运算。public class Main {public static void main(String[] args) {//本月第一天0:00时刻//LocalDateTime ldt = LocalDateTime.now().withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0);LocalDateTime ldt1 = LocalDateTime.now().withDayOfMonth(1).atStartOfDay();//Java8运行失败//本月最后一天LocalDateTime ldt2 = LocalDateTime.now().with(TemporalAdjusters.lastDayOfMonth());System.out.println(lastDay);//下月第一天LocalDateTime ldt3 = LocalDateTime.now().with(TemporalAdjusters.firstDayOfNextMonth());System.out.println(ldt3);//本月第一个周一LocalDateTime firstWeekDay = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));System.out.println(firstWeekDay);}}
对于计算某个月第一个周日这样的问题,新的API可以轻松完成。
判断两个LocalDateTime的先后 (isBefore,isAfter)
要判断两个LocalDateTime的先后,可以使用isBefore()、isAfter()方法,对于 LocalDate 和LocalTime类似。
public class Main {public static void main(String[] args) {LocalDateTime now = LocalDateTime.now();LocalDateTime target = LocalDateTime.of(2021,8,18,18,18,18);System.out.println(now.isBefore(target));System.out.println(LocalDate.now().isBefore(LocalDate.of(2021,8,18)));System.out.println(LocalTime.now().isAfter(LocalTime.parse("16:18:07")));}}
注意到LocalDateTime无法与时间戳进行转换,因为LocalDateTime没有时区,无法确定某一时刻。后面我们要介绍的ZoneDateTime相当于LocalDateTime加时区的组合,它具有时区,可以与Long表示的时间戳进行转换。
Duration和Period (时间间隔)
Duration表示两个时刻之间的时间间隔。另一个类似的Period表示两个日期之间的天数。
public class Main {public static void main(String[] args) {LocalDateTime now = LocalDateTime.now();LocalDateTime ldt = LocalDateTime.of(2022,8,23,21,32,12);Duration d = Duration.between(now,ldt);System.out.pirntln(d);Period p = LocalDate.of(2018,1,2).until(LocalDate.of(2020,2,10));System.out.println(p);}}
注意到两个LocalDateTime之间的差值用Duration表示,类似PT-14080H-54M-13.765S,表示14080小时54分13秒。而两个LocalDate之间的差值用Period表示,类似P-1Y-7M-8D,表示一年七个月八天。
Duration和Period的表示方法也符合ISO 8601的格式,它以P...T..的形式表示,P...T...之间表示日期间隔,T后面表示时间间隔。如果是PT...的格式表示仅有时间间隔。 自用ofSXxx()或者parse()方法也可以直接创建Duration
Duration d1 = Duration.ofHours(10);//10hoursDuration d2 = Duration.parse("P1DT2H3M");
小结
Java 8 引入了新的日期和时间API,,它们是不变类,默认按ISO 8601标准格式化和解析
使用LocalDateTime可以非常方便地对日期和时间进行加减,或者调整日期时间,它总是返回新对象
使用isBefore()和isAfte()可以判断日期和时间的先后。
使用Dureation和Period可以表示两个日期和时间的区间间隔
