【视频笔记】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();
上述代码打印结果:

//示例
//now 方法创建 Year YearMonth MonthDay 类的实例对象
//使用now方法创建Year类的实例对象.
Year year = Year.now();
//使用now方法创建YearMonth类的实例对象.
YearMonth yearMonth = YearMonth.now();
//使用now方法创建MonthDay类的实例对象.
MonthDay monthDay = MonthDay.now();
上述代码打印结果:

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秒

浙公网安备 33010602011771号