一、Java8 之前时间存在的问题
Java 8 (又称为 jdk1.8) 是 Java 语言开发的一个主要版本,它支持函数式编程,新的日期 API,新的Stream API 等。Java 8通过发布新的 Date-Time API (JSR 310) 来进一步加强对日期与时间的处理。在旧版的 Java 中,我们使用的是 SimpleDateFormat 对日期进行格式化,日期时间 API(Date,calendar) 存在诸多问题,其中有:
1、非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
2、设计很差 − Java的日期/时间类的定义并不一致,在 java.util 和 java.sql 的包中都有日期类,此外用于格式化和解析的类在 java.text 包中定义。java.util.Date 同时包含日期和时间,而 java.sql.Date 仅包含日期,将其纳入 java.sql 包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
3、时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此 Java 引入了 java.util.Calendar 和 java.util.TimeZone 类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
(1)Local(本地) − 简化了日期时间的处理,没有时区的问题。
(2)Zoned(时区) − 通过制定的时区处理日期时间。
二、Java8 新特性之新的时间和日期的使用
传统的时间 API 存在线程安全的问题,在多线程开发中必须要上锁,所以 java8 现在为我们提供了一套全新的时间日期 API。
Java 8 的日期和时间类包含:日期( LocalDate)、时间(LocalTime)、时刻(Instant)、过程(Duration) 以及时钟(clock),这些类都包含在 java.time 包中,Java 8 新的时间 API 的使用方式,包括创建、格式化、解析、计算、修改。
Instant:时间戳
Duration:持续时间,时间差
LocalDate:只包含日期,比如:2021-09-02
LocalTime:只包含时间,比如:23:12:10
LocalDateTime:包含日期和时间,比如:2019-02-02 23:14:21
Period:时间段
ZoneOffset:时区偏移量,比如:+8:00
ZonedDateTime:带时区的时间
Clock:时钟,比如获取目前美国纽约的时间
1、LocalDate、LocalTime、LocalDateTime
LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601 (ISO-8601 日历系统是国际化组织制定的现代化公民的日期和时间的表达法)日历系统的日期、时间、日期和时间。
从名称也可以看出来,第一种表示日期 年月日,第二种表示时间 时分秒,第三种表示年月日时分秒。
由于的实例是不可变的对象,所以我们如果对时间对象进行更改,包括修改时间,加减时间都需要重新返回一个新的实例。
// 获取当前时间 LocalDate localDate = LocalDate.now();
// 获取指定日期时间 LocalDateTime localDateTime = LocalDateTime.of(2020,06,02,12,22,23);
// 当前日期+1天 LocalDate localDate =LocalDate.now(); LocalDate tomorrow = localDate.plusDays(1); // 对日期时间进行加减 /** * 对日期时间进行加操作,使用 localDateTime.plus***(num) * 对日期时间进行减操作,使用 localDateTime.minus***(num) * 修改不限于,年、月、日、时、分、秒、纳秒 */ LocalDateTime localDateTime2 = localDateTime.plusMonths(2); System.out.println("指定时间添加2月:"+localDateTime2); LocalDateTime localDateTime3 = localDateTime.minusDays(2); System.out.println("指定时间减少2天:"+localDateTime3);
对日期时间进行加操作,使用 localDateTime.plusXXX(num)
对日期时间进行减操作,使用 localDateTime.minusXXX(num)
修改不限于,年、月、日、时、分、秒、纳秒
2、Instant: 时间戳(以Unix 元年 : 1970-01-01 00:00:00 到某个时间之间的毫秒数)
//获取当前时间戳 时间戳已UTC 时间展示,与中国时间差距8小时 Instant instant = Instant.now();
//如果想要获取中国时间 可以通过设置偏移量来获取 OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
使用Instant获取当前时间戳默认为UTC格式时间,我们如果想要获取中国时间的话,必须给instant设置偏移量,instant.atOffset(ZoneOffset.ofHours(8));
可以设置偏移量为加8小时,即为中国时间。也可设置负值,则为减8小时。
//toEpochMilli 获取毫秒数 long l = instant.toEpochMilli();
获取毫秒数(时间戳):以前来获取当前时间戳是用:new Date().getTime()
以及 System.currentTimeMillis
获取。现在可用使用instant.toEpochMilli()
来获取,他们三个获取的时间戳是一样的。
3、Duration、Period:计算两个日期时间的时间差
(1)Duration获取2个时间之间的间隔(LocalDateTime、Instant)
使用Duration.between(instant, instant1)获取两个时间时间差返回一个 Duration对象,可以通过对象内部的实例方法获取时间间隔。
Duration between = Duration.between(instant, instant1); System.out.println("毫秒:"+between.toMillis()); System.out.println("秒:"+between.getSeconds());
Duration不仅可以获取Instant时间的间隔,获取LocalDateTime 的时间间隔也是可以的。
LocalDateTime localDateTime = LocalDateTime.now(); LocalDateTime localDateTime1 = LocalDateTime.of(2020,09,18,15,02,03); Duration between1 = Duration.between(localDateTime, localDateTime1); System.out.println("LocalDateTime间隔小时:"+between1.toHours());
(2)Period获取两个时间之间的间隔 (LocalDate)
Period between2 = Period.between(LocalDate.now(), LocalDate.of(2020, 07, 06)); System.out.println("日期间隔天数:"+between2.getDays());
4、TemporalAdjuster:时间矫正器。
有时我们可能需要获取一个周末,或者下一个工作日等时间,这里 java8 就为我们提供了一个时间校正器,让我们对时间进行校准。
TemporalAdjusters:该类通过静态方法提供了大量的常用的TemporalAdjuster的实现供我们使用。
在localDateTime中,有一个with方法,其中可以让我们去写一TemporalAdjuster接口,而TemporalAdjusters类中,有许多常用的方法
//时间校正器 @Test public void testTemporalAdjuster(){ LocalDateTime localDateTime = LocalDateTime.now(); System.out.println("当前时间:"+localDateTime); LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(10); System.out.println("手动指定日期时间:"+localDateTime1); //获取当月第一天 LocalDateTime with = localDateTime.with(TemporalAdjusters.firstDayOfMonth()); System.out.println("使用TemporalAdjuster获取当月第一天:"+with); System.out.println(localDateTime.with(TemporalAdjusters.firstDayOfMonth())); //获取下一年的第一天 System.out.println(localDateTime.with(TemporalAdjusters.firstDayOfNextYear())); //获取年中第一天 System.out.println(localDateTime.with(TemporalAdjusters.lastDayOfYear())); //获取月中最后一天 System.out.println(localDateTime.with(TemporalAdjusters.lastDayOfMonth())); //获取下个星期一 System.out.println(localDateTime.with(TemporalAdjusters.next(DayOfWeek.MONDAY))); }
TemporalAdjusters 包含许多静态方法,可以直接调用,以下列举一些:
方法名 | 描述 |
---|---|
dayOfWeekInMonth | 返回同一个月中每周的第几天 |
firstDayOfMonth | 返回当月的第一天 |
firstDayOfNextMonth | 返回下月的第一天 |
firstDayOfNextYear | 返回下一年的第一天 |
firstDayOfYear | 返回本年的第一天 |
firstInMonth | 返回同一个月中第一个星期几 |
lastDayOfMonth | 返回当月的最后一天 |
lastDayOfNextMonth | 返回下月的最后一天 |
lastDayOfNextYear | 返回下一年的最后一天 |
lastDayOfYear | 返回本年的最后一天 |
lastInMonth | 返回同一个月中最后一个星期几 |
next / previous | 返回后一个/前一个给定的星期几 |
nextOrSame / previousOrSame | 返回后一个/前一个给定的星期几,如果这个值满足条件,直接返回 |
5、时间格式化工具:DateTimeFormatter
DateTimeFormatter是专门为LocalDateTime进行格式化的工具类,内置了很多基本格式,也可自定义时间格式化公式。写法也比较特别,可以是formatter.format(dateTime)
,也可以是dateTime.format(formatter)
;
//本身内置了很多格式化格式 DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; // 也可自定义格式 DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy年MMdd hh:mm:ss");
6、LocalDateTime 与 Date 互转
LocalDateTime 与 Date 互转主要是利用两者共有的属性 Instant 进行相互转换。
(1)LocalDateTime 转 Date:
LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
或者 date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
(2)Date 转 LocalDateTime :
Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
@Test public void LocalDateToDate(){ Date date = new Date(); LocalDateTime localDateTime1 = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); LocalDateTime localDateTime = LocalDateTime.now(); Date from = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); System.out.println("LocalDateTime转换成date:"+from); SimpleDateFormat ss = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(ss.format(from)); }
7、其他一些常用的方法
(1)比较两个时间的先后
LocalDate date15 = LocalDate.of(2021,9,31); date.isAfter(date15); // false date.isBefore(date15); // true
(2)MonthDay类的使用
MonthDay只包含月日信息,可以用于存放类似于生日,结婚纪念日等信息。
比如:生日检查或账单检查,我们在购物时注册会员时都会有生日祝福,例如,用户的生日为1996-09-10,如果今天是2021-09-10,那么今天就是用户的生日(按公历/身份证日期来算),那么通过java8新的日期库,我们该如何来进行实现呢?
LocalDate birthday = LocalDate.of(1996, 09, 10); MonthDay birthdayMd = MonthDay.of(birthday.getMonth(), birthday.getDayOfMonth()); MonthDay today = MonthDay.from(LocalDate.of(2021, 09, 10)); System.out.println(today.equals(birthdayMd)); // 输出结果为 :true
这里用到了:
Java中MonthDay类的 from() 方法从时间对象获取MonthDay的实例,
Java中MonthDay类的of(Month month,int dayOfMonth)方法用于获取MonthDay的实例
MonthDay monthday = MonthDay.of(9, 18); // 09-18 MonthDay date = MonthDay.from(ZonedDateTime.now()); // 09-18