早期日期类的问题
Date 类在 JDK1.1 之前就已经有了,从 API 可以发现,从 JDK1.1开始 Date 类中的好多方法都已经被弃用,Java1.1推荐采用 Calendar 类处理日期和时间,但是这个类同样存在不少问题
老版本 API 计算困难问题
例子:小明出生于 1995年 12 月20日,计算当前这个时间他已经出生了多少天
步骤思路:
1、初始化 Date 对象,利用无参构造方法
2、获取当前时间距离 1970年1月1日过了多少毫秒
3、初始化 Calendar 对象并设置为 1995年6月20日,并将 Calendar 对象转化为 Date 对象,再转换 1995年6月20日距离 1970年1月1日过了多少毫秒
4、将两个毫秒数做相减操作,然后将毫秒数转换为天数
Date date = new Date();
long nowTime = date.getTime();
Calendar calendar = Calendar.getInstance();
calendar.set(1995, 11, 20);
Date birthdayDate = calendar.getTime();
long birthdayTime = birthdayDate.getTime();
long day = (nowTime-birthdayTime)/1000/60/60/24;
System.out.println("1995年 12 月20日 距离现在 "+ day+"天");
day = ChronoUnit.DAYS.between(LocalDate.of(1995, 12, 20), LocalDate.now());
System.out.println("1995年 12 月20日 距离现在 "+ day+"天");
可以看到两种方式计算的结果差了一天,如果自己用计算器算的话,发现后者是对的,说明前者在毫秒转换计算的时候出现了一点误差...
线程安全问题
我们之前对时间的格式化转换一般是用 SimpleDateFormat 类,但是这个类是线程不安全的,在多线程情况下,全局共享一个 SimpleDateFormat 类中的 Calendar 对象有可能出现异常。
* 创建 10 个线程,将字符串"2018-12-12 12:12:12" 转换为 Date 对象后打印到控制台上
*/
public class Test2 {
final static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
for(int i=0; i<10; i++){
new Thread(()->{
try {
Date date = SIMPLE_DATE_FORMAT.parse("2018-12-12 12:12:12");
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
},"threadName"+i).start();
}
}
}
当然啦,解决的方法无非就是加同步代码块呗,或者用ThreadLocal
而新版本日期 API 都是线程安全的
其它
另外一个问题就是在 java.util.Date 和 java.util.Calendar 类之前,枚举类型还没有出现,所以在字段中使用整数常量都是可变的,而不是线程安全的,为了处理实际开发中遇到的问题,标准库随后引入了 java.sql.Date 作为 java.util.Date 的子类,但是还是没能彻底解决问题。
最终 Java 8 引入了java.time 包,这种全新的包从根本上解决了长久以来存在的诸多弊端
Calendar calendar = Calendar.getInstance();
calendar.set(2018,8,8);
calendar.set(2018,Calendar.AUGUST,12);
Date-Time API 中基本类的使用
Date-Time API 中的所有类均生成不可变实例,它们是线程安全的,并且这些类不提供公共构造函数,也就是说没办法通过 new 直接创建,需要采用工厂方法加以实例化
System.out.println(Instant.now());
System.out.println(LocalDate.now());
System.out.println(LocalTime.now());
System.out.println(LocalDateTime.now());
System.out.println(ZonedDateTime.now());
不仅仅是刚才提供的几个类可以使用 now 方法,Java8 的 Time 包还提供了其它的几个类可以更精确的获取某些信息:
System.out.println(Year.now());
System.out.println(YearMonth.now());
System.out.println(MonthDay.now());
注意:没有 Month.now(),Month 是个枚举类,有1-12月
of 方法在日期 /时间类的应用
of 方法可以根据给定的参数生成对应的日期/时间对象,基本上每个类都有对应的 of 方法用于生成指定的日期/时间对象,而且重载形式多变,可以根据不同的参数生成对应的数据。
System.out.println(LocalDate.of(2018,8,8));
System.out.println(LocalTime.of(2,38,20));
System.out.println(LocalDateTime.of(2019,9,9,9,0));
System.out.println(YearMonth.of(2020,5));
System.out.println(MonthDay.of(4,22));
对于 LocalTime,如果是初始化晚上的时间,需要加12;如果秒和纳秒是0的话,那么默认不会封装这些数据,只显示小时和钟
System.out.println(LocalTime.of(18,30));
System.out.println(LocalTime.of(19,30,0,0));
注意 LocalDateTime 有个特殊重载方法:of(LocalDate date, LocalTime time)
为 LocalDateTime 添加时区信息
在打印 ZonedDateTime 的时候,发现这个对象封装不仅有时间日期,还有时区,那么时区在 Java 如何获取呢?通过提供的一个类 ZoneId 的 getAvailableZoneIds() 方法可以获取到一个 Set 集合,集合中封装了 600 个时区
System.out.println(ZoneId.systemDefault());
System.out.println(ZoneId.getAvailableZoneIds());
我们可以通过给 LocalDateTime 添加时区来查看到不同时区的时间,比如 LocalDateTime 中封装的时间是上海时间,想知道此时此刻纽约的时间是什么,就可以将纽约的时区添加进去即可查看到:
LocalDateTime localDateTime = LocalDateTime.of(2018, 11, 28, 8, 54, 38);
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println("Asia/Shanghai 时间:"+zonedDateTime);
zonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println("同一时刻 Asia/Tokyo 时间:"+zonedDateTime);
Month 枚举类的使用
java.time 包引入了 Month 枚举类,Month 中包含标准日历中的12个月份的常量,也提供了一些方法供我们使用,==推荐在初始化 LocalDate 和 LocalDateTime 对象的时候,月份的参数使用 Month 枚举类方式传入,这样简单易懂更不容易出错,因为如果旧的思维, Calendar 月份从0开始,在新 API 传0会出现异常==
System.out.println(LocalDate.of(2020, Month.OCTOBER, 23));
System.out.println(Month.of(12));
根据现有实例创建日期与时间对象
想要修改某个日期/时间对象的现有实例时,我们可以使用 plus 和 minus 方法来完成操作。
Java 8中时间相关API 的实例都是不可变的,一旦创建,LocalDate,LocalDateTime,LocalTime 就无法修改(类似 String),这对于线程安全非常有利。
LocalDate localDate = LocalDate.of(2020, 3, 20);
LocalDate plusDays = localDate.plusDays(4);
LocalDate plusWeeks = localDate.plusWeeks(3);
LocalDate plusMonths = localDate.plusMonths(5);
LocalDate plusYears = localDate.plusYears(6);
上面是加时间,减同理,minusXXX(),查看源码发现,底层调用的还是 plusXX方法
Period、Duration、ChronoUnit 计算时间差
Period类主要是方法getYears(),getMonths()和getDays()来计算,Period 更多的是计算一段时期,比如Period.between(LocalDate.of(2019,5,1),LocalDate.of(2020,5,1)).getDays()
是0,通过getYears 可以得出1 年
LocalDate today = LocalDate.now();
LocalDate birthDate = LocalDate.of(1995, Month.JUNE, 20);
Period p = Period.between(birthDate, today);
System.out.printf("年龄 : %d 年 %d 月 %d 日",p.getYears(),p.getMonths(),p.getDays());
==========sout==========
年龄 : 24 年 10 月 11 日
Duration 提供了使用基于时间的值(如秒,纳秒)测量时间量的方法。
Instant inst1 = Instant.now();
Instant inst2 = inst1.plus(Duration.ofSeconds(10));
System.out.println("相隔毫秒 : " + Duration.between(inst1, inst2).toMillis());
System.out.println("相隔秒 : " + Duration.between(inst1, inst2).getSeconds());
System.out.println("相隔纳秒 : " + Duration.between(inst1, inst2).getNano());
==========sout============
相隔毫秒 : 10000
相隔秒 : 10
相隔纳秒 : 0
ChronoUnit 类可用于在单个时间单位内测量一段时间,例如天数或秒。
LocalDate startDate = LocalDate.of(1993, Month.OCTOBER, 19);
LocalDate endDate = LocalDate.now();
long days = ChronoUnit.DAYS.between(startDate, endDate);
System.out.println("相隔天数 : " + days);
============sout===============
相隔天数 : 9691
当然要计算相隔多少小时得用 LocalDateTime 了,ChronoUnit.HOURS.between(LocalDateTime.of(2019, 5, 1,12,1,2), LocalDateTime.of(2020, 5, 1,2,4,1))
,非常简单。
plus 和 minus 方法的应用
上面我们都是对 日期/时间的 某一项进行加减,其实还有两个单独的 plus 方法,Temporal plus(TemporalAmount amount)
和 Temporal plus(long amountToAdd, TemporalUnit unit)
问题:今天小张查看自己车辆保险记录的时候发现还有 2年3个月8天就到期了,计算到期的时间是什么时候
方法一:plusXXX()方法
LocalDate now = LocalDate.now();
LocalDate endTime = now.plusYears(2).plusMonths(3).plusDays(8);
System.out.println("当前时间:"+now+" 保险到期时间:"+endTime);
方法二:plus(TemporalAmount amountToAdd)
TemporalAmount 是一个接口,当接口作为方法参数的时候,实际上传入的是接口的实现类,查看这个接口的实现体系,可以看到有一个类 Period,这个类表示一段时间
LocalDate now = LocalDate.now();
Period period = Period.of(2, 3, 8);
LocalDate endTime = now.plus(period);
System.out.println("当前时间:"+now+" 保险到期时间:"+endTime);
在实际的开发过程中,可能会更精确的去操作日期或者说增加一些特殊的时间,比如说一个实际,一个半天,1千年,Java8 提供了这些日期的表示方式而不需要单独进行计算了。
TemporaUnit 是一个接口,通过接口继承体现可以发现,可以使用子类 ChronoUnit 来表示,ChronoUnit 封装了很多时间段供我们使用。
LocalDateTime marryTime = LocalDateTime.of(2020, 2, 2, 11, 11, 11);
LocalDateTime time = marryTime.plus(1, ChronoUnit.DECADES);
LocalDateTime eatTime = time.plus(1, ChronoUnit.HALF_DAYS);
with 方法在日期/时间类的应用
以 LocalDateTime 为例,如果不需要对日期进行加减而是要直接修改日期的话可以使用 with 方法,with 方法提供了很多修改时间的方式
LocalDateTime time = getTime();
LocalDateTime resultTime1 = time.withDayOfMonth(1);
LocalDateTime resultTime2 = time.with(ChronoField.DAY_OF_MONTH,1);
System.out.println("原来的时间:"+time);
System.out.println("修正后的时间:"+resultTime1);
System.out.println("修正后的时间:"+resultTime2);
======输出===========
原来的时间:2018-12-12T08:28:44
修正后的时间:2018-12-01T08:28:44
修正后的时间:2018-12-01T08:28:44
当然还有 withHour()、withYear()、withDayOfYear()...等方法
调节器 TemporalAdjuster 与查询 TemporalQuery
之前我们通过 with 方法修改日期中封装的 数据,但是有些时候可能会做些复杂的操作,比如将时间调整到下个周的周日,下一个工作日,或者本月的某一天,这个时候可以使用 调节器TemporalAdjuster 来更方便的处理日期
LocalDate now = LocalDate.now();
LocalDate firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());
LocalDate lastDayOfYear = now.with(TemporalAdjusters.lastDayOfYear());
LocalDate firstDayOfNextYear = now.with(TemporalAdjusters.firstDayOfNextYear());
LocalDate firstDayOfNextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());
LocalDate nextSunday = now.with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
LocalDate previousWEDNESDAY = now.with(TemporalAdjusters.previous(DayOfWeek.WEDNESDAY));
自定义 TemporalAdjusters 调节器
通过 Java8 本身提供的 TemporalAdjusters 中方法可以完成一些常用的操作,如果要自定义日期的时间的更改逻辑,可以通过实现 TemporalAdjuster 接口的方式完成
思考:发薪日是每月 15号,如果发薪日是周末,则调整为周五
自定义调节器
public class DateAdjuest implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
LocalDate payDate = LocalDate.from(temporal);
LocalDate realPayDay = payDate.withDayOfMonth(15);
if (payDate.getDayOfWeek()== DayOfWeek.SUNDAY || payDate.getDayOfWeek()==DayOfWeek.SATURDAY)
realPayDay = realPayDay.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
return realPayDay;
}
}
LocalDate date = LocalDate.of(2019, 12, 15);
LocalDate realtime = LocalDate.from(new DateAdjuest().adjustInto(date));
System.out.println("原来的日期是:"+date+"真实发薪日:"+realtime);
========输出=============
原来的日期是:2019-12-15真实发薪日:2019-12-13
TemporalQuery 的使用
LocalDate、LocalTime 有一个方法叫做 query,可以针对日期进行查询,query(TemporalQuery<R> query)
是一个泛型方法,返回的数据就是传入的泛型类型,而 TemporalQuery 是一个泛型接口,里面有一个抽象方法 queryFrom(TemporalAccessor temporal)
,TemporalAccessor 是 Temporal 的父接口,实际上也就是 LocalDate、LocalTime 相关类的顶级父接口,这个 queryFrom 方法的实现逻辑就是 传入一个日期/时间对象,通过自定义逻辑返回数据。如果要计划日期距离某一特定天数差多少天,可以自定义实现类 TemporalQuery 并作为参数传递到 query 方法中。
思考:计算当前时间距离下一个劳动节还有多少天?
public class TemporalQueryImpl implements TemporalQuery<Long> {
@Override
public Long queryFrom(TemporalAccessor temporal) {
LocalDate now = LocalDate.from(temporal);
LocalDate laborDay = LocalDate.of(now.getYear(), Month.MAY, 1);
if (now.isAfter(laborDay)){
laborDay = laborDay.plusYears(1);
}
long days = ChronoUnit.DAYS.between(now, laborDay);
return days;
}
}
Long days = now.query(new TemporalQueryImpl());
System.out.println("当前时间是:"+now+" 距离下个劳动节还有:"+days);
思考:计算当前时间距离下一个圣诞节/儿童节/劳动节还有多少天?
public class TemporalQueryImpl implements TemporalQuery<Long[]> {
/**
* @param temporal
* @return 表示距离三个节日的差额,0索引表示圣诞节,1->儿童节,2->劳动节
*/
@Override
public Long[] queryFrom(TemporalAccessor temporal) {
//TemporalAccessor 是 LocalDate、LocalTime 的父接口,
//相当于 LocalDate就是这个接口的实现类,将 temporal 转换为 LocalDate 进行使用
LocalDate now = LocalDate.from(temporal);
//封装圣诞节
LocalDate d1 = LocalDate.of(now.getYear(), Month.DECEMBER, 25);
//封装儿童节
LocalDate d2 = LocalDate.of(now.getYear(), Month.JUNE, 1);
//封装当年的劳动节时间
LocalDate d3 = LocalDate.of(now.getYear(), Month.MAY, 1);
//判断当前时间是否已经超过当年的劳圣诞节/儿童节/劳动节,如果超过,加一年
if (now.isAfter(d1)) d1 = d1.plusYears(1);
if (now.isAfter(d2)) d2 = d2.plusYears(1);
if (now.isAfter(d3)) d3 = d3.plusYears(1);
//通过 ChronoUnit 类的 between 方法来计算两个时间点的差额
Long[] days = {ChronoUnit.DAYS.between(now, d1),ChronoUnit.DAYS.between(now, d2),ChronoUnit.DAYS.between(now, d3)};
//返回的就是相隔的天数
return days;
}
}
Long[] days = now.query(new TemporalQueryImpl());
System.out.println("当前时间是:"+now+" \n距离下个圣诞节还有:"+days[0]+" \n距离下个儿童节还有:"+days[1]+" \n距离下个劳动节还有:"+days[2]);
==============输出==============
当前时间是:2020-04-25
距离下个圣诞节还有:244
距离下个儿童节还有:37
距离下个劳动节还有:6
java.util.Date 与 java.time.LocalDate 转换
对于老项目的改造,需要将 Date 或者 Calendar 转换为 java.time包中相应的类,Java8 中 java.time 包并没有提供太多的内置方式来转换 java.util 包中预处理标准日期和时间的类,我们可以使用 Instant 类作为中介,也可以使用 java.sql.Date 和 java.sql.Timestamp 类提供的方法进行转换
使用 Instant 类将 java.util.Date 转换为 java.time.LocalDate
java.time 包并没有提供很多的方式进行直接转换,但是之前的 Date类、Calendar类在 Java8 都提供了一个新的方法叫 toInstant,可以将当前对象转换为 Instant 对象,通过给 Instant 添加时区信息后就可以转换为 LocalDate 对象。
Date date = new Date();
Instant instant = date.toInstant();
ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
LocalDate localDate = zonedDateTime.toLocalDate();
System.out.println("原生 date:"+date);
System.out.println("转换后 localDate: "+localDate);
========sout==========
原生 date:Sat Apr 25 13:00:24 CST 2020
转换后 localDate: 2020-04-25
java.sql.Date 类中的转换方法使用
看清楚 这个包,java.sql.Date
Date date = new Date(System.currentTimeMillis());
LocalDate localDate = date.toLocalDate();
System.out.println("java.sql.date:"+date);
System.out.println("java.time.localDate: "+localDate);
=========sout=========
java.sql.date:2020-04-25
java.time.localDate: 2020-04-25
java.util.Timestamp 类中的转换方法使用
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
LocalDateTime localDateTime = timestamp.toLocalDateTime();
将 java.util.Date 转换为 java.time.LocalDate 方法二
这种方法就是使用 java.sql.Date 作为中间桥梁,因为 java.sql.Date 提供了 toLocalDate() 方法..
java.util.Date date = new Date();
java.sql.Date sqlDate = new java.sql.Date(date.getTime());
LocalDate localDate = sqlDate.toLocalDate();
将java.util.Calendar 转换为 ZonedDateTime
Calendar calendar = Calendar.getInstance();
TimeZone timeZone = calendar.getTimeZone();
ZoneId zoneId = timeZone.toZoneId();
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(calendar.toInstant(), zoneId);
zonedDateTime.toLocalDate();
zonedDateTime.toLocalDateTime();
zonedDateTime.toLocalTime();
将java.util.Calendar 转换为 LocalDateTime
当然我们可以先得到 ZonedDateTime 再通过 toLocalDateTime 获取,就像上面一样。
但是 Calendar 本身就可以获取到年月日时分秒信息,这些信息可以作为 LocalDateTime 参数进行构造。
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR);
int minite = calendar.get(Calendar.MINUTE);
int seconds = calendar.get(Calendar.SECOND);
LocalDateTime time = LocalDateTime.of(year, month + 1, day, hour, minite, seconds);
System.out.println(time);
日期的解析与格式化 DateTimeFormatter
SimpleDateFormate 之前说过是线程不安全的,所以 Java8 提供了新的格式化类 DateTimeFormatter。DateTimeFormatter 提供了大量的预定义格式化器,包括常量如 ISOLOCALDATE,模式字母(yyyy-MM-dd)及本地化样式
新日期类的 format 和 parse 方法
LocalDateTime localDateTime = LocalDateTime.now();
String s1 = localDateTime.format(DateTimeFormatter.ISO_DATE);
String s2 = localDateTime.format(DateTimeFormatter.ISO_DATE_TIME);
System.out.println(localDateTime);
System.out.println("ISO_DATE 格式:"+s1);
System.out.println("ISO_DATE_TIME 格式:"+s2);
LocalDateTime time = LocalDateTime.parse(s2);
LocalDate date = LocalDate.parse(s1);
System.out.println(time);
System.out.println(date);
========sout==========
2020-04-25T13:41:16.639
ISO_DATE 格式:2020-04-25
ISO_DATE_TIME 格式:2020-04-25T13:41:16.639
2020-04-25T13:41:16.639
2020-04-25
DateTimeFormatter.ofLocalizedDate 指定解析格式
LocalDateTime localDateTime = LocalDateTime.now();
String s1 = localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL));
String s2 = localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG));
String s3 = localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM));
String s4 = localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT));
System.out.println("FULL: "+s1);
System.out.println("LONG: "+s2);
System.out.println("MEDIUM: "+s3);
System.out.println("SHORT: "+s4);
============sout=============
FULL: 2020年4月25日 星期六
LONG: 2020年4月25日
MEDIUM: 2020-4-25
SHORT: 20-4-25
这种方式在不同地区显示方式会不一样,在其它地区不会显示中文,会根据当前默认时区来进行区别显示
自定义格式化格式
LocalDateTime localDateTime = LocalDateTime.now();
String format = localDateTime.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
评论