【视频笔记】Java8日期

视频地址

历史 Api

在 Java 面世之初,标准库中就引入了两种用于处理日期和时间的类,但是由于很多问题,很多方法都已经弃用,在 JavaSE8 中引入java.time 包解决了长久以来存在的诸多弊端,java.time 包基于 Joda-Time 库构件,是一种免费的开源解决方法,多年来一直作为处理 ]ava 日期和时间的事实标准。
]ava 原本自带的 java.util.Date 和 java.util.Calendar 类,实际上这两种类有线程不安全的风险

  • 对日期的计算困难问题

    • 毫秒值与日期直接转换比较繁琐,其次通过毫秒值来计算时间差额步骤较多。
    //使用示例
    //1.程序员效力出生于 1995 年 12 月 16 日,计算到现在已经出生了多少天?
    //思路:当前时间戳 - 出生日期时间戳,再除以 1000*60*60*24
    Date d = new Date();
    long s1 = d.getTime();
    Calendar c = Calendar.getInstance();
    c.set(1995,11,16);
    Date d2 = c.getTime();
    long s2 = d2.getTime();
    long result = (s1-s2)/1000/60/60/24;
        
    //java8 Api 实现
    long day = ChronoUnit.DAYS.between(LocalDate.of(1995,12,16),LocalDate.now());
    
    
  • 线程安全问题

    • SimpleDateFormat 类是线程不安全的,在多线程的情况下,全局共享一个 SimpleDateFormat;
    • SimpleDateFormat 类中的 Calendar 对象有可能会出现异常。
//重现示例
//2.创建 10 个线程,将字符串 "2018-12-12 12:12:12" 转换为 Date 对象后打印控制台。

final static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(int i=0;i<10;i++){
    new Thread(new Runnable(){
        public void run(){
            Date date = SIMPLE_DATE_FORMAT.parse("2018-12-12 12:12:12");
            System.out.println(date);
        }
    }).start();
}

执行代码出现以下异常:

原因:多线程中,一个线程执行clear()后,其他线程会丢失了待处理数据。

解决:添加同步块

final static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(int i=0;i<10;i++){
    new Thread(new Runnable(){
        public void run(){
            synchronized(SIMPLE_DATE_FORMAT){ //添加同步块
                Date date = SIMPLE_DATE_FORMAT.parse("2018-12-12 12:12:12");
            	System.out.println(date);
            }
        }
    }).start();
}
  • 另外的一个问题就是在 java.util.Date 和 java.util.Calendar 类之前,枚举类型( ENUM )还没有出现,所以在字段中使用整数常量导致整数常量都是可变的,而不是线程安全的。为了处理实际开发中遇到的问题,标准库随后引入了 java.sql.Date 作为 java.util.Date 的子类,但是还是没能彻底解决问题。
//解释示例
//3.初始化 Calender 对象,封装日期为 2008 年 8 月 8 日
Calendar calendar = Calendar.getInstance();
calendar.set(2008,7,8);//通过 set 方法设置年/月/日参数->开发规范:不允许使用没有定义的魔法数字。

内容简介

  • 掌握 ]ava8 中的提供的 java.time 包中的常用日期类与相关方法

  • 可以从 java.util 包的下的日期类相关类过渡到 java.time 包下的日期类

  • 掌握 java8 中的日期与字符串之间的相互转换

第一章 Date-Time API 中的基础类使用

1.常用类的概述与功能介绍

Instant 类

Instant 类对时间轴上的单一瞬时点建模,可以用于记录应用程序中的事件时间戳,在之后学习的类型转换中,均可以使用 Instant 类作为中间类完成转换

Duration 类

Duration 类表示秒或纳秒时间间隔,适合处理较短的时间,需要更高的精确性。

Period 类

Period 类表示一段时间的年、月、日。

LocalDate 类

LocalDate 类是一个不可变的日期时间对象,表示日期,通常被视为年月日。

LocalTime 类

LocalTime 类是一个不可变日期时间的时间对象,代表是一个时间,通常背看作是小时-秒,时间表示为纳秒精度。

LocalDateTime 类

LocalDateTime 是一个不可变日期时间对象,代表日期时间,通常被视为年月日时分秒。

ZonedDateTime 类

ZonedDateTime 具有时区的日期时间不可变表示,此类存储所有日期和时间字段,精度为纳秒,时区为区域偏移量,用于处理模糊的本地日期时间。

2.Now 方法在日期/时间类中的使用

Date-Time API 中的所有类均生成不可变实例,它们是线程安全的,并且这些类不提供公共构造函数,也就是说没办法通过 new 的方式直接创建,需要采用工厂方法加以实例化。

练习:创建当前时间

//示例
//now 方法可以根据当前日期或时间创建示例对象
//使用 now 方法创建 Instant 的实例对象.
Instant instantNow = Instant.now();
//使用 now 方法创建 LocalDate 的实例对象.
LocalDate localDateNow = LocalDate.now();
//使用 now 方法创建 LocalTime 的实例对象.
LocalTime localTimeNow = LocalTime.now();
//使用 now 方法创建 LocalDateTime 的实例对象.
LocalDateTime localDateTimeNow = LocalDateTime.now();
//使用 now 方法创建 ZonedDateTime 的实例对象.
ZonedDateTime zonedDateTimeNow = ZonedDateTime.now();

上述代码打印结果:

image

//示例
//now 方法创建 Year YearMonth MonthDay 类的实例对象
//使用now方法创建Year类的实例对象.
Year year = Year.now();
//使用now方法创建YearMonth类的实例对象.
YearMonth yearMonth = YearMonth.now();
//使用now方法创建MonthDay类的实例对象.
MonthDay monthDay = MonthDay.now();

上述代码打印结果:

image

3.of 方法获取指定日期时间

of 方法可以根据给定的参数生成对应的日期/时间对象,基本上每个基本类都有 of 方法用于生成的对应的对象,而且重载形式多变,可以根据不同的参数生成对应的数据。

练习:创建指定时间

//示例
//初始化 2018 年 8 月 8 日的 LocalDate 对象
LocalDate.of(2018,8,8); //2018-08-08
/*
初始化晚上 8 点 0 分 0 秒的 LocalTime 对象 -> 如果是晚上的时间,需要加 12。
LocalTime.of 方法的重载形式有以下几种,可以根据实际情况自行使用。
LocalTime of(int hour,int minute) -> 根据小时/分钟生成对象。
LocalTime of(int hour,int minute,int second) -> 根据小时/分钟/秒生成对象。
LocalTime of(int hour,int minute,int second,int nanoOfSecond) -> 根据小时/分钟/毫秒/纳秒生成对象。
注意:如果秒和纳秒是0的话,那么默认不会封装这些数据,只显示小时和分钟。
*/
LocalTime.of(20,0);//20:00


/*

初始化 2018 年 8 月 8 日下午 8 点 0 分的 LocalDateTime 对象。
LocalDateTime.of 方法的重载形式有以下几种,可以根据实际情况自行使用。
LocalDaterime of(int year,int month, int dayofilonth,int hour,int minmute,int second,int nanoofSecond)
->根据年/月/日/时分/秒生成对象
LocalDateTime of(int year,int month,int dayOfonth,int hour,int minute)
->根据年/月/日/时/分生成对象

*/
LocalDateTime.of(2018,8,8,20,0);//2018-08-08T20:00

//LocalDateTime的 of 的特殊用法
LocalDateTime.of(LocalDate.now(),LocalTime.now());

4.为 LocalDateTime 添加时区信息

在学习 ZonedDateTime 的时候,发现了这个对象里面封装的不仅有时间日期,并且还有偏移量+时区,那么时区如何在 Java 中获取呢,通过提供的一个 ZoneId 的 getAvailableZoneIds 方法可获取到一个 set 集合,集合中封装了 600 个时区。

练习:获取时区

Set<String>set = ZoneId.getAvailableZoneIds();
set.forEach(System.out::println);

//获取当前时区
ZoneId zoneId= ZoneId.systemDefault();

练习:为 LocalDateTime 添加时区信息

//示例
//为 LocalDateTime 添加时区信息
//1.封装 LocalDateTime 对象,参数自定义 -> 2018 年 11 月 11 日 8 点 54 分 38 秒。
LocalDateTime localDateTime = LocalDateTime.of(2018, 11, 11, 8, 54, 38);
//2.localDateTime对象现在只是封装了一个时间,并没有时区相关的数据,所以要添加时区信息到对象中,使用atzone()方法。
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
//3.更改时区查看其他时区的当前时间,通过 withZoneSameInstant() 方法即可更改。
ZonedDateTime tokyozonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));

Month 枚举类的使用

java.time 包中引入了 Month 的枚举,Month 中包含标准日历中的 12 个月份的常量(从 JANURAY 到 DECEMBER )也提供了一些方便的方法供我们使用。

// Month 枚举类-> of 可以根据传入的数字返回对应的月份枚举。
Month month = Month.of(12);

练习

创建当前时间(不带时区)

LocalDateTime.now();

创建当前时间(只包含年月日)

LocalDate.now();

创建当前时间(包含年月日时分秒并且带有时区)

ZonedDateTime.now();

创建 2012 年 12 年 31 日 7 时 38 分 46 秒的日期对象月份使用枚举表示

LocalDateTime.of(2012,Month.DECEMBER,31,7,38,46);

创建 2012 年 12 年 31 日的日期对象月份使用枚举表示

LocalDate.of(2012,Month.DECEMBER,31);

创建7时38分46秒的时间对象

LocalTime.of(7,38,46);

第二章 根据现有实例创建日期与时间对象

想要修改某个日期/时间对象的现有实例时,我们可以使用 plus 和 minus 方法来完成操作。

Java8 中日期时间相关的 API 中的所有实例对象都是不可改变的,一旦创建LocalDate,LocalTime,LocalDateTime 就无法修改他们(类似 String ),这对于线程安全非常有利。

  • plus 和 minus 方法在 LocalDate 与 LocalTime 中的使用

    • LocalDate中定义了多种对日期进行增减操作的方法

      • LocalDate plusDays( long days) 增加天数
      • LocalDate plusWeeks(long weeks) 增加周数
      • LocalDate plusMonths( long months) 增加月数
      • LocalDate plusYears( long years) 增加年数

练习:使用 plusXXX/minusXXX 修改日期

//示例
//2016 年 2 月 13 日
LocalDate ld = LocalDate.of(2016,2,13);
// 计算 4 天后的时间
ld.plusDays(4);
//计算当前时间的 3 周后的时间
LocalDate.now().plusWeeks(3);
//计算当前时间 5 个月后的时间
LocalDate.now().plusMonths(5);
//计算当前时间 2 年后的时间
LocalDate.now().plusYears(2);
  • LocalTime 中定义了多种对时间进行增减操作的方法

    • LocalTime plusNanos ( long nanos) 增加纳秒
    • LocalTime plusSeconds ( long seconds) 增加秒
    • LocalTime plusMinutes ( long minutes) 增加分钟
    • LocalTime plusHours( long hours) 增加小时

练习:使用 plusXXX/minusXXX 修改时间

//示例
//8 点 14 分 39 秒 218 纳秒
LocalTime time = LocalTime.of(8,14,39,218);
//计算当前时间 500 纳秒后的时间
LocalTime.now().plusNanos(500);
//计算当前时间45秒后的时间
LocalTime.now().plusSeconds(45);
//计算当前时间19分钟后的时间
LocalTime.now().plusMinutes(19);
//计算当前时间3小时后的时间
LocalTime.now().plusHours(3);
  • plus 和 minus 方法

    • plus(period);
    • plus(1,ChronoUnit)

练习:使用 plus 和 minus 方法

//示例
//今天程序员小张查看自己的车辆保险记录的时候看到还有比 2 年 3 月 8 天就到期了,计算到期时间是什么时候。
//我的思路: 当前时间+2年+3月+8天
LocalDateTime.now().plusYears(2).plusMonths(3).plusDays(8);
//或
Period period = Period.of(2,3,8);//来表示一段时间,三个参数代表年月日
LocalDateTime.now().plus(period);

//结婚10年称为锡婚,2020年2月2日11点11分11秒称为对称日,很多情侣准备在那天结婚,如果在那天结婚了,那么锡婚会发生在什么时候?
LocalDateTime.of(2020,2,2,11,11,11).plus(1,ChronoUnit.DECADES)

//加一个半天
LocalDateTime.now().plus(1,ChronoUnit.HALF_DAYS);

  • 如果不需要对日期进行加减而是要直接修改日期的话,那么可以使用with方法,with方法提供了很多种修改时间的方式。
LocalDateTime withNano(int i) //修改纳秒
LocalDateTime withSecond(int i) //修改秒
LocalDateTime withMinute(int i) //修改分钟
LocalDateTime withHour(int i) //修改小时
LocalDateTime withDayOfMonth(int i) //修改日
LocalDateTime withMonth(int i) //修改月
LocalDateTime withYear(int i) //修改年
    
with(TemporalField field, long newValue)

练习:使用 with 修改日期

//示例
LocalDateTime.now().withDayOfMonth(1); //修改为1号
LocalDateTime.now().with(ChronoField.DAY_OF_MONTH,1);  //修改为1号

练习

//使用三种方式计算 2019 年 7 月 19 日 14 时 38 分 34 秒后的 3 年 7 个月 18 天后是什么时候?
LocalDateTime time = LocalDateTime.now();
time = LocalDateTime.of(2019,7,19,14,38,34);
//1.
Period period = Period.of(3,7,18);
time.plus(period);
//2.
time.plusYears(3).plusMonths(7).plusDays(18);
//3.
//我不会~


第三章 调节器TemporalAdjuster与查询TemporalQuery

在上一节学习的with方法中学习了可以通过with方法修改日期时间对象中封装的数据,但是有一些时候可能会做一些复杂的操作,比如说将时间调整到下个周的周日,下一个工作日,或者本月中的某一天,这个时候可以使用调节器TemporalAdjuster来更方便的处理日期

//示例
//通过with方法传入TemporalAdjuster类的实现类对象,就可以进行更改,实现类对象是由TemporalAdjusters类的静态方法来提供的
LocalDate now = LocalDate.now();
//1.修改时间为当前时间为当月第一天
now.with(TemporalAdjusters.firstDayOfMonth());
//修改时间为下个月的第一天.
LocalDate firstDayoOfNextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());

//修改时间为下一年的第一天.
LocalDate firstDayofNextYear = now.with(TemporalAdjusters.firstDayOfNextYear());

//修改时间为本年的第一天.
LocalDate firstDayofYear = now.with(TemporalAdjusters.firstDayOfYear());

//修改时间为本月的最后一天.
LocalDate lastDayofMonth = now.with(TemporalAdjusters.lastDayOfMonth());

//修改时间为本年的最后一天.
LocalDate lastDayofYear = now.with(TemporalAdjusters.lastDayOfYear());

TemporalAdjuster 是一个接口, with 方法实际上传入的是这个接口的实现类对象, TremporalAdjusters 并不是 TemporalAdjuster 的实现类,只不过 TemporalAdjusters 的静态方法实现了 TemporalAdjuster ,并且将实现类对象返回了。

练习:使用TemporalAdjusters

//示例
//将当前时间改为下一个周日
LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
//将当前时间该上一个周三
LocalDate.now().with(TemporalAdjusters.previous(DayOfWeek.WEDNESDAY));

自定义 TemporalAdjuster 调节器

通过Java8本身提供的TemporalAdjusters中的方法可以完成一些常用的操作,如果要自定义日期时间的更改逻辑,可以通过实现TemporalAdjuster类接口中的方式来完成.
。创建类实现TemporalAdjuster接口
。实现TemporalAdjuster中的adjustInto方法,传入一个日期时间对象,完成逻辑之后返回日期时间对象。
。通过with方法传入自定义调节器对象完成更改.

练习:使用自定义TemporalAdjuster

//假如员工一个月中领取工资,发薪日是每月15号,如果发薪日时周末,则调整为周五。
 LocalDate date = LocalDate.of(2021,5,15).with(d->{
            LocalDate readyDay = LocalDate.from(d);
            readyDay = readyDay.withDayOfMonth(15);
            if(readyDay.getDayOfWeek()== DayOfWeek.SUNDAY||readyDay.getDayOfWeek()==DayOfWeek.SATURDAY){
                readyDay = readyDay.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
            }
            return readyDay;
        });

TemporalQuery的应用

学习的时态类对象 (Loca,Date,LocalTime) 都有一个方法叫做 query,可以针对日期进行查询 R query(TemporalQuery query) 这个方法是一个泛型方法,返回的数据就是传入的泛型类的类型,TemporalQuery 是一个泛型接口,里面有一个抽象方法是R queryFrom(TemporalAccessor temporal), TemporalAccessor 是 Temporal 的父接口,实际上也就是 LocalDate、LocalDateTime 相关类的顶级父接口,这个 queryFrom 的方法的实现逻辑就是,传入一个日期/时间对象通过自定义逻辑返回数据。

如果要计划日期距离某一个特定天数差距多少天,可以自定义类实现TemporalQuery接口并且作为参数传入到query方法中.

练习:计算当前时间距离下一个劳动节还有多少天?
//示例
//计算当前时间距离下一个劳动节还有多少天?
Long  days = LocalDate.of(2021,4,1).query(d->{
            //1.TemporalAccessor是LocalDate和LocalDateTime的顶级父接口,相当于LocalDate就是这个接口的实现类,将temporal转换为LocalDate进行使用.
            LocalDate now = LocalDate.from(d); //now -> 2020 - 1 - 17
            //2.封装当年的劳动节时间,年份: now来获取,月份:Month.MAY,日期:1.
            LocalDate laborDay = LocalDate.of(now.getYear(), Month.MAY,1);//laborDay -> 2020 - 5

            //2.1.判断当前时间是否已经超过了当年的劳动节,如果超过了当年的劳动节,则laborDay+1年.
            if (now.isAfter(laborDay)) {
                laborDay = laborDay.plusYears(1);
            }

            //3.通过Chronounit的between方法来计算两个时间点的差额.
            return ChronoUnit.DAYS.between( now,laborDay);
        });

练习

计算任意时间与下一个的圣诞节/儿童节/劳动节各相差多少天?
//示例
 Long[]  days = LocalDate.of(2021,4,1).query(d->{
            //1将temporal转换为LocalDate对象.
            LocalDate now = LocalDate.from(d);
            //2.封装当年的圣诞节/儿童节/劳动节国日期时间对象,年:date中的来获取year,月+日期:对应的月份
            LocalDate d1 = LocalDate.of(now.getYear(), Month.MAY,1);
            LocalDate d2 = LocalDate.of(now.getYear(), Month.JUNE,1);
            LocalDate d3= LocalDate.of(now.getYear(), Month.DECEMBER,25);

            //2.1.判断date是否已经超过了d1/d2/d3三个节日,如果超过了,则顺延计算下一年的日期.
            if (now.isAfter(d1)) {
                d1 = d1.plusYears(1);
            }
            if (now.isAfter(d2)) {
                d2 = d2.plusYears(1);
            }
            if (now.isAfter(d1)) {
                d3 = d3.plusYears(1);
            }

            //3.通过Chronounit的between方法来计算两个时间点的差额.
            return new Long[]{ChronoUnit.DAYS.between( now,d1),ChronoUnit.DAYS.between( now,d2),ChronoUnit.DAYS.between( now,d3)};
        });

第四章 java.util.Date与java.time. LocalDate的转换

对于老项目的改造,需要将 Date 或者 Calendar 转换为 java.time 包中相应的类的,可以根据本小节中提供的方法进行改造。
Java8 中的 java.time 包中并没有提供太多的内置方式来转换 java.util 包中用预处理标准日期和时间的类,我们可以使用 Instant 类作为中介,也可以使用 java.sql.Date 和 java.sql.Timestamp 类提供的方法进行转换。

1.使用 Instant 类将 java.util.Date 转换为 java.time.LocalDate

java.time 包中并没有提供很多的方式来进行直接转换,但是给之前的 Date 类,Calendar类在 Java1.8 都提供了一个新的方法,叫做 toInstant,可以将当前对象转换为 Instant 对象,通过给 Instant 添加时区信息之后就可以转换为 LocalDate 对象。

//示例
//1.创建Date
Date date= new Date();
//2.Date->Instant
Instant i = date.toInstant();
//3.Instant->ZonedDateTime
ZonedDateTime z = i.atZone(ZoneId.systemDefault());
LocalDate d = z.toLocalDate();
System.out.println(d);
2.将java.sql.Date转换为java.time.LocalDate
Date date= new Date(System.currentTimeMillis());
date.toLocalDate();
3.将java.sql.Timestamp 转换为java.time.LocalDate
Timestamp  date= new Timestamp(System.currentTimeMillis());
date.toLocalDateTime();
4.使用java.sql.Date类将java.util.Date转换为java.time.LocalDate
//示例
Date d= new Date();
java.sql.Date date = new java.sql.Date(d.getTime());
date.toLocalDate();
5.将Calendar 转换为java.time.ZonedDateTime
//1.初始化calendar对象.
Calendar cal = Calendar.getInstance();
//2.calendar对象自Java1.1版本开始提供了一个方法用于获取时区对象getTimezone方法,要将calendar转换为zonedDateTime对象需要先获取到时区对象,
TimeZone timezone = cal.getTimeZone();
//3.从Java1.8开始Timezone类提供一个方法可以获取到ZoneId ->拿zoneTd对象来构建
ZoneId zoneId = timezone.toZoneId();
//4.ZonedDateTime类有一个ofInstant方法,可以将一个Instant对象和ZoneId对象封装为一个ZonedDateTime.
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(cal.toInstant(), zoneId);

6.通过 LocalDateTime.of 将Calendar 转换为java.time.LocalDateTime

calendar对象可以获取到年月日时分秒的信息,这些信息可以作为LocalDateTime构造方法的参数.

第五章 日期的解析与格式化DateTimeFormatter

SimpleDateFormat 类在刚开始的讲过了是线程不安全的,所以 Java8 提供了新的格式化类 DateTimeFormatter.
DateTimeFormatter 类提供了大量预定义格式化器,包括常量(如 Iso_LOCAL_DATE )、模式字母(如 yyyy-MM-dd )以及本地化样式.
与 SimpleDateFormat 不同的是,新版本的日期/时间 API 的格式化与解析不需要在创建转换器对象再进行转换了,通过时间日期对象的 parse/format 方法可以直接进行转换.

LocalDate类定义的parse和format方法
LocalDateTime now = LocalDateTime.now();
String s1 = now.format(DateTimeFormatter.ISO_DATE);
String s2= now.format(DateTimeFormatter.ISO_DATE_TIME);
System.out.println("s1 = " + s1);//s1 = 2021-07-07
System.out.println("s2 = " + s2);//s2 = 2021-07-07T07:09:21.056

LocalDateTime time = LocalDateTime.parse(s2);
System.out.println(time);
通过DateTimeFormatter的ofLocalizedDate指定解析格式
 //通过DateTimeFormatter的ofLocalizedDate指定解析格式.
 LocalDateTime now = LocalDateTime.now();
 String t1 = now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL));//2021年7月7日 星期三
 String t2 = now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG));//2021年7月7日
 String t3 = now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM));//2021-7-7
 String t4 = now.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT));//21-7-7
自定义格式化
//通过DateTimeFormatter提供的ofPattern方法自定义格式化方式.
String s = now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss:SSS"));
练习
将1998年3月18日17时19分28秒格式化为以下格式1998-03月-18---17:19分28秒|
LocalDateTime d= LocalDateTime.of(1998,3,18,17,19,28);
d.format(DateTimeFormatter.ofPattern("yyyy-MM月-dd---HH:mm分ss秒"));//1998-03月-18---17:19分28秒
posted @ 2021-07-07 22:55  写代码的华  阅读(150)  评论(0)    收藏  举报