摒弃DateCalendar,使用新的日期API
Posted 恒奇恒毅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了摒弃DateCalendar,使用新的日期API相关的知识,希望对你有一定的参考价值。
感谢传智播客老师深入细致的讲解
为什么会出现新的日期类API
将java.util.Date类束之高阁才是正确之道 -> Tim Yates
在Java面世之初,标准库就引入了两种用于处理日期和时间的类,它们是java.util.Date和java.util.Calendar,而前者堪称类糟糕设计的典范,浏览API可以发现,从Java1.1开始,Date类中的所有方法就已经被弃用,Java1.1推荐采用Calendar类处理日期和时间,但是这个类同样存在不少问题。
对于日期的计算困难问题.
- 毫秒值与日期直接转换比较繁琐,其次通过毫秒值来计算时间的差额步骤较多,并且可能还存在误差.
/*** 计算当前时间距离2000年6月1日相差了多少天.
** 通过距离1970年1月1日的毫秒差值,可以计算出两个日期之间相隔的天数.
*/
public class Demo01 {
public static void main(String[] args) {
//1.初始化Date对象,无参构造(无参构造默认代表的就是当前时间)
Date dateNow = new Date();
//2.获取当前时间的距离1970年1月1日过了多少毫秒.
long dateTimeNow = dateNow.getTime();
//3.初始化Calendar对象并设时间为2006年6月1日并且将Calendar对象转换 为Date对象.
Calendar paramterTime = Calendar.getInstance();
paramterTime.set(2000, Calendar.JUNE, 1);
Date paramterDateTime = paramterTime.getTime();
//4.计算paramterDateTime与dateTimeNow之间的毫秒差额.
Long intervalTime = dateTimeNow - paramterDateTime.getTime();
//5.对intervalTime进行计算获取差额,毫秒值/1000->/60->/60->/24
long intervalDay = intervalTime / 1000 / 60 / 60 / 24;
System.out.println("当前时间距离2000年6月1日已经过了" + intervalDay+"天."); } }
- 线程安全问题
SimpleDateFormat类是线程不安全的,在多线程的情况下,全局共享一个SimpleDateFormat类中的Calendar对象有可能会出现异常。 - 另外的一个问题就是在java.util.Date和java.util.Calendar类之前,枚举类型(ENUM)还没有出现,所以在字段中使用整数常量导致整数常量都是可变的,而不是线程安全的.为了处理实际开发中遇到的问题,标准库随后引入了java.sql.Date作为java.util.Date的子类,但是还是没能彻底解决问题。
- 最终JavaSE 8中引入了java.time包,这种全新的包从根本上解决了长久以来的存在的诸多弊端,java.time包基于Joda-Time库构件,是一种免费的开源解决方案,实际上在Java 8没有出现之前,公司中已经广泛使用Joda-Time来解决Java中的日期与时间问题,Joda-Time的设计团队也参与了java.time包的开发。
Date-Time新API的使用
Instant类
Instant类对时间轴上的单一瞬时点建模,可以用于记录应用程序中的事件时间戳,在之后类型转换中,均可以使用Instant类作为中间类完成转换。
Duration类
Duration类表示秒或纳秒时间间隔,适合处理较短的时间,需要更高的精确性。
Period类
Period类表示一段时间的年、月、日。
LocalDate类
LocalDate是一个不可变的日期时间对象,表示日期,通常被视为年月日。
LocalTime类
LocalTime是一个不可变的日期时间对象,代表一个时间,通常被看作是小时-秒,时间表示为纳秒精度。
LocalDateTime类
LocalDateTime是一个不可变的日期时间对象,代表日期时间,通常被视为年-月-日- 时-分-秒。
ZonedDateTime类
ZonedDateTime是具有时区的日期时间的不可变表示,此类存储所有日期和时间字段,精度为纳秒,时区为区域偏移量,用于处理模糊的本地日期时间。
Date-Time API中的所有类均生成不可变实例,它们是线程安全的,并且这些类不提供公共构造函数,也就是说没办法通过new的方式直接创建,需要采用工厂方法加以实例化。
now方法获取当前日期时间
Instant instantNow = Instant.now();
LocalDate localDateNow = LocalDate.now();
LocalTime localTimeNow = LocalTime.now();
LocalDateTime localDateTimeNow = LocalDateTime.now();
ZonedDateTime zonedDateTimeNow = ZonedDateTime.now();
Year year = Year.now();
YearMonth yearMonth = YearMonth.now();
MonthDay monthDay = MonthDay.now();
System.out.println("Instant:"+instantNow);
System.out.println("LocalDate:"+localDateNow);
System.out.println("LocalTime:"+localTimeNow);
System.out.println("LocalDateTime:"+localDateTimeNow);
System.out.println("ZonedDateTime:"+zonedDateTimeNow);
System.out.println("Year:"+year);
System.out.println("YearMonth:"+yearMonth);
System.out.println("MonthDay:"+monthDay);
Instant:2021-05-24T04:57:20.151Z
LocalDate:2021-05-24
LocalTime:12:57:20.333
LocalDateTime:2021-05-24T12:57:20.333
ZonedDateTime:2021-05-24T12:57:20.334+08:00[Asia/Shanghai]
Year:2021
YearMonth:2021-05
MonthDay:–05-24
of方法获取指定的日期时间
//初始化2018年8月8日的LocalDate对象.
LocalDate date = LocalDate.of(2018, 8, 8);
System.out.println("LocalDate:" + date);
/*初始化晚上7点0分0秒的LocalTime对象.
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 time = LocalTime.of(19, 0, 0, 0);
System.out.println("LocalTime:" + time);
/*
初始化2018年8月8日下午7点0分的LocalDateTime对象.
LocalDateTime.of方法的重载形式有以下几种,可以根据事情自行使用.
LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond) -> 根据年/月/日/时/分/秒生成对象.
LocalDateTime of(int year, int month, int dayOfMonth, int hour, int minute) -> 根据年/月/日/时/分生成对象.
注意:LocalDateTime of(LocalDate date, LocalTime time)方法可以将一个 LocalDate对象和一个LocalTime对象合并封装为一个LocalDateTime对象.
*/
LocalDateTime.of(2018, 8, 8, 19, 0, 0, 0);
LocalDateTime localDateTime = LocalDateTime.of(date, time);
System.out.println("LocalDateTime:" + localDateTime);
LocalDate:2018-08-08
LocalTime:19:00
LocalDateTime:2018-08-08T19:00
为LocalDateTime添加时区信息
ZoneId表示时区,getAvailableZoneIds获取系统支持的时区。
//获取所有的时区信息
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();//600
System.out.println(availableZoneIds.size());
for (String zoneId : availableZoneIds) {
System.out.println(zoneId);
}
systemDefault获取系统默认时区
//获取当前系统默认的时区信息
ZoneId zoneId = ZoneId.systemDefault();
System.out.println(zoneId);//Asia/Shanghai
LocalDateTime添加时区信息就能获取到包含时区信息的
//1.封装LocalDateTime对象,参数自定义 -> 2018年11月11日 8点54分38秒
LocalDateTime time = LocalDateTime.of(2018, 11, 11, 8, 54, 38);
//2.封装完成后的time对象只是封装的是一个时间,并没有时区相关的数据,所以添加时区到 对象中,使用atZone方法.
ZonedDateTime zonedDateTime = time.atZone(ZoneId.of("Asia/Shanghai"));
System.out.println("Asia/Shanghai的时间是:" + zonedDateTime);
//3.更改时区查看其它时区的当前时间,通过withZoneSameInstant方法即可更改.
ZonedDateTime otherZonedTime = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println("在同一时刻,Asia/Tokyo的时间是:" + otherZonedTime);
Asia/Shanghai的时间是:2018-11-11T08:54:38+08:00[Asia/Shanghai]
在同一时刻,Asia/Tokyo的时间是:2018-11-11T09:54:38+09:00[Asia/Tokyo]
Month、DayOfWeek枚举类
java.time包中引入了Month的枚举,Month中包含标准日历中的12个月份的常量(从JANURAY到DECEMEBER)。
java.time包中引入了DayOfWeek的枚举,DayOfWeek中包含一周的常量(从MONDAY到SUNDAY)
根据现有实例创建日期与时间对象
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) 增加年数
LocalTime中定义了多种对时间进行增减操作的方法
LocalTime plusNanos(long nanos) 增加纳秒
LocalTime plusSeconds(long seconds) 增加秒
LocalTime plusMinutes(long minutes) 增加分钟
LocalTime plusHours(long hours) 增加小时
plus相关方法有对应的减少方法,以minus开头的方法对应的即为减少,实际上minus方法调用的也是plus方法,只不过传入的参数是负数。
plus(TemporaAmount amountToAdd)
TemporaAmount是一个接口,根据查看这个接口的体系,可以看到这个接口有两个典型实现类,Period和Duration。
LocalDate date = LocalDate.now();
//date表示当前时间. //固然可以使用对于年月日依次+2,+3,+8的方式来操作,但是有些繁琐,首先我 们先将2年3月8天封装为一段时间,也就是封装为一个Period对象.
Period time = Period.of(2, 3, 8);
//使用plus方法对于date对象直接进行增加的操作.
LocalDate endDate = date.plus(time);
System.out.println("今天是" + date + ",保险到期的时间是" + endDate);
plus(long l,TemporaUnit unit)
ChronoUnit
LocalDateTime marryTime = LocalDateTime.of(2020, Month.FEBRUARY, 2, 11, 11, 11);
//使用plus方法进行计算,添加1个,ChronoUnit.DECADES(十年).
LocalDateTime time = marryTime.plus(1, ChronoUnit.DECADES);
System.out.println("如果在" + marryTime + "结婚,那么锡婚是" + time);
//如果锡婚后的半天准备要请所有亲戚朋友吃饭,那么吃饭的时间是.
LocalDateTime eatTime = time.plus(1, ChronoUnit.HALF_DAYS);
System.out.println("半天后吃饭,吃饭的时候是:" + eatTime);
with方法在LocalDateTime类的应用
如果不需要对日期进行加减而是要直接修改日期的话,那么可以使用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)
TemporalField是一个接口,通过查看体系结构,可以使用它的子类ChronoField,ChronoField中封装了一些日期时间中的组成部分,可以直接选择之后传入第二个参数进行修改。
with调节器TemporalAdjuster
TemporalAdjusters的类可以给我们提供一些常用的方法,方法如下.
TemporalAdjusters类中常用静态方法的使用
static TemporalAdjuster firsyDayOfNextMonth()
下个月的第一天
static TemporalAdjuster firstDayOfNextYear()
下一年的第一天
static TemporalAdjuster firstDayOfYear()
当年的第一天
static TemporaAdjuster firstInMonth(DayOfWeek dayOfWeek)
当月的第一个周x(通过参数确定)
static TemporaAdjuster lastDayOfMonth()
当月的最后一天
static TemporaAdjuster lastDayOfYear()
当年的最后一天
static TemporaAdjuste lastInMonth(DayOfWeek dayOfWeek)
当月的最后一个周x(通过参数确定)
static TemporaAdjuster next(DayOfWeek dayOfWeek)
下一个周x(通过参数确定)
static TemporaAdjuster previous(DayOfWeek dayOfWeek)
上一个周x(通过参数确定)
可以自定义
/*** 假如员工一个月中领取工资,发薪日是每个月的15号,如果发薪日是周末,则调整为周五. */
public class PayDayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
//1.将temporal转换为子类对象LocalDate,from方法可以将任何时态对象转换为 LocalDate.
LocalDate payDay = LocalDate.from(temporal);
//2.判断当前封装的时间中的日期是不是当月15日,如果不是,则更改为15日.
int day;
if (payDay.getDayOfMonth() != 15) {
day = 15;
} else {
day = payDay.getDayOfMonth();
}
LocalDate realPayDay = payDay.withDayOfMonth(day);
//3.判断realPayDay对象中封装的星期数是不是周六或者是周日,如果是周末或者是周日则 更改为周五.
if (realPayDay.getDayOfWeek() == DayOfWeek.SUNDAY || realPayDay.getDayOfWeek() == DayOfWeek.SATURDAY) {
//说明发薪日是周末,则更改为周五.
realPayDay = realPayDay.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
}
return realPayDay;
}
}
TemporalQuery
时态类对象(LocalDate,LocalTime)都有一个方法叫做query,可以针对日期进行查询.
R query(TemporalQuery query)这个方法是一个泛型方法,返回的数据就是传入的泛型类型,TemporalQuery是一个泛型接口,里面有一个抽象方法是R queryFrom(TemporalAccessor temporal),TemporalAccessor是Temporal的父接口,实际上也就是LocalDate,LocalDateTime相关类的顶级父接口,这个queryFrom的方法的实现逻辑就是,传入一个日期/时间对象通过自定义逻辑返回数据。
如果要计划日期距离某一个特定天数差距多少天,可以自定义类实现TemporalQuery接口并且作为参数传入到query方法中。
/*** 获取某一天距离下一个劳动节的相隔天数的实现类. */
public class UntilDayQueryImpl implements TemporalQuery<Long> {
@Override public Long queryFrom(TemporalAccessor temporal) {
//获取当前的年/月/日信息.
int year = temporal.get(ChronoField.YEAR);
int month = temporal.get(ChronoField.MONTH_OF_YEAR);
int day = temporal.get(ChronoField.DAY_OF_MONTH);
//将获取到的数据封装为一个LocalDate对象.
LocalDate time = LocalDate.of(year, month, day);
//封装劳动节的时间,年参数传递year,month和day是5和1.
LocalDate laborDay = LocalDate.of(year, Month.MAY,1);
//判断当前时间是否已经超过了当年的劳动节,如果超过了,则laborDay+1年.
if (time.isAfter(laborDay)){
laborDay = laborDay.plusYears(1);
}
//通过ChronoUnit的between方法计算两个时间点的差额.
return ChronoUnit.DAYS.between(time, laborDay);
}
}
老API和新API之间的转换
java.util.Date与java.time.LocalDate的转换
Java8中的java.time包中并没有提供太多的内置方式来转换java.util包中用预处理标准日期和时间的类,我们可以使用Instant类作为中介,也可以使用java.sql.Date和java.sql.Timestamp类提供的方法进行转换。
//初始化Date对象.
Date d = new Date();
//将Date类对象转换为Instant类对象.
Instant i = d.toInstant();
//Date类包含日期和时间信息,但是并不提供时区信息,和Instant类一样,可以通过 Instant类的atZone方法添加时区信息之后进行转换.
ZonedDateTime zonedDateTime = i.atZone(ZoneId.systemDefault());
//将ZonedDateTime通过toLocalDate方法转换为LocalDate对象.
LocalDate localDate = zonedDateTime.toLocalDate();
System.out.println("转换之前的Date对象是:" + d);
System.out.println("转换之后的LocalDate对象是:" + localDate);
java.sql.Date类中的转换方法使用
java.sql.Date类中提供直接转换为LocalDate的方法,toLocalDate
//初始化java.sql.Date对象.
Date d = new Date(System.currentTimeMillis());
//将java.sql.Date对象通过toLocalDate方法转换为LocalDate对象.
LocalDate localDate = d.toLocalDate();
System.out.println("转换前的java.sql.Date对象是:" + d);
System.out.println("转换后的LocalDate对象是:" + localDate);
java.sql.Timestamp类中的转换方法使用
//初始化java.sql.Timestamp对象.
Timestamp t = new Timestamp(System.currentTimeMillis());
//将java.sql.Timestamp对象通过toLocalDateTime方法转换为LocalDateTime对 象.
LocalDateTime localDateTime = t.toLocalDateTime();
System.out.println("转换之前的Timestamp对象是:" + t);
System.out.println("转换之后的LocalDateTime对象是:" + localDateTime);
将java.util包中的类和java.time包中的相应类相互转换
/**
* 将java.sql.Date转换为LocalDate
*
* @param date
* @return
*/
public static LocalDate convertFromSqlDateToLocalDate(java.sql.Date date) {
return date.toLocalDate();
}
/**
* 将LocalDate转换为java.sql.Date
* @param date
* @return
*/
public static java.sql.Date convertFromLocalDateToSqlDate(LocalDate date) {
return java.sql.Date.valueOf(date);
}
/**
* 将java.util.Date转换为LocalDate
* @param date
* @return
*/
public static LocalDate convertFromUtilDateToLocalDate(java.util.Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
/**
* 将java.sql.Timestamp转换为LocalDateTime
* @param timestamp
* @return
*/
public static LocalDateTime convertFromTimestampToLocalDateTime(java.sql.Timestamp timestamp) {
return timestamp.toLocalDateTime();
}
/**
* 将LocalDateTime转换为java.sql.Timestamp
* @param localDateTime
* @return
*/
public static java.sql.Timestamp convertFromLocalDateTimeToTimestamp(LocalDateTime localDateTime) {
return java.sql.Timestamp.valueOf(localDateTime);
}
/**
* 将LocalDate转换为java.util.Date
* @param date
* @return
*/
public static java.util.Date convertFromLocalDateToUtilDate(LocalDate date){
ZonedDateTime zonedDateTime = date.atStartOfDay(ZoneId.systemDefault());
return Date.from(zonedDateTime.toInstant());
}
将java.util.Calendar类转换为java.time.ZonedDateTime类
Calendar对象自Java1.1开始提供了一个方法获取时区对象的方法,getTimeZone,要将Calendar对象转换为ZonedDateTime需要先获取到时区对象.从Java1.8开始TimeZone类提供了一个方法可以获取到ZonedId.获取到zoneId之后就可以初始化ZonedDateTime对象了,ZonedDateTime类有一个ofInstant方法,可以将一个Instant对象和ZonedId对象作为参数传入构造一个ZonedDateTime对象。
//初始化Canlendar对象.
Calendar cal = Calendar.getInstance();
//Calendar对象自Java1.1开始提供了一个方法获取时区对象的方法,getTimeZone,要将 Calendar对象转换为ZonedDateTime需要先获取到时区对象.
TimeZone timeZone = cal.getTimeZone();
//从Java1.8开始TimeZone类提供了一个方法可以获取到ZonedId.
ZoneId zoneId = timeZone.toZoneId();
//获取到zoneId之后就可以初始化ZonedDateTime对象了,ZonedDateTime类有一个 ofInstant方法,可以将一个Instant对象和ZonedId对象作为参数传入构造一个ZonedDateTime对 象.
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(cal.toInstant(), zoneId); System.out.println("转换前的Calendar对象是:" + cal);
System.out.println("转换后的ZonedDateTime对象是:" + zonedDateTime);
将java.util.Calendar类转换为java.time.LocalDateTime类
//初始化Canlendar对象.
Calendar cal = Calendar.getInstance();
//通过Getter方法获取到Calendar对象中封装的数据.
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int day = cal.get(Calendar.DAY_OF_MONTH);
int hour = cal.get(Calendar.HOUR);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
//将以上获取到的数据作为LocalDateTime的of方法的参数进行对象的封装.
LocalDateTime localDateTime = LocalDateTime.of(year, month + 1, day, hour, minute, second);
System.out.println("转换前的Calendar对象是:" + cal);
System.out.println("转换后的LocalDateTime对象是:" + localDateTime);
日期的解析与格式化DateTimeFormatter
SimpleDateFormat类在刚开始的讲过了是线程不安全的,所以Java8提供了新的格式化类 DateTimeFormatter.DateTimeFormatter类提供了大量预定义格式化器,包括常量(如ISO_LOCAL_DATE),模式字母(如yyyy-MM-dd)以及本地化样式。与SimpleDateFormat不同的是,新版本的日期/时间API的格式化与解析不需要在创建转换器对象再进行转换了,通过时间日期对象的parse/format方法可以直接进行转换。
//对LocalDateTime进行格式化与解析,初始化LocalDateTime对象.
LocalDateTime time = LocalDateTime.now();
//DateTimeFormatter类中定义了很多方式,通过常量名可以指定格式化方式.
String result = time.format(DateTimeFormatter.ISO_DATE_TIME); System.out.println("ISO_DATE_TIME格式化之后的String是:" + result);
String result1 = time.format(DateTimeFormatter.ISO_DATE);
System.out.println("ISO_DATE格式化之后的String是:" + result1);
//解析字符串的方式通过LocalDateTime类的静态方法parse方法传入需要解析的字符串即可.
LocalDateTime localDateTime = LocalDateTime.parse(re以上是关于摒弃DateCalendar,使用新的日期API的主要内容,如果未能解决你的问题,请参考以下文章
为什么不建议使用Date,而是使用Java8新的时间和日期API?
Java日期时间API系列11-----Jdk8中java.time包中的新的日期时间API类,使用java8日期时间API重写农历LunarDate