在JDK1.8,Java基于原来的(java.util包)日期时间处理API,进行了重大的升级(java.time包),解决了旧API的种种问题。本文尝试着简单概要地将java.time包中的日期时间进行分类整理,并介绍它的基本用法。
目录
设计思想
方法
time包对比于util包的设计升级
Instant类
Clock时钟
日期时间
时期
时间显示格式
时间格式控制符
解析、格式化时间
时间调整
TemporalAdjuster接口
自定义时间调节器
TemporalQuery接口
util包的时间与time包的时间互转
util包Date转time包LocalDate
Calendar转LocalDateTime
Calendar转ZonedDateTime
背景:
初识java.time包:
特性:所有类均生成不可变实例(final修饰),它们是线程安全的。并且这些类不提供公共构造函数,也就是说没办法通过new的方式直接创建,需要采用静态工厂方式获取实例。
为什么不应该使用java.util包中的类处理时间:
Date、Calendar时间处理有误差
Date、Calendar时间处理有误差
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Calendar;
import java.util.Date;
/**
* 程序员小李出生于1995年12月16日,计算在当前这个时间他已经出生了多少天?
* 算法:
* 1.利用Date获取当前时间的毫秒值,利用Calendar获取小李出生时间的毫秒值
* 2.将两个毫秒值做差,再转换成天数
*/
public class Main {
public static void main(String[] args) {
Date nowDate = new Date();
Calendar birthDate = Calendar.getInstance();
birthDate.set(1995,11,16); //要想设置 12 月份,则需要写11,因为从 0 开始
long days = (nowDate.getTime() - birthDate.getTime().getTime()) / 1000 / 60 / 60 / 24;
System.out.println(days); //输出:9152
// 使用 java8 新版本的API来完成题目要求
long daysTmp = ChronoUnit.DAYS.between(LocalDate.of(1995, 12, 16), LocalDate.now());
System.out.println(daysTmp); //输出:9153
}
}
通过对java.time包中所有类的观察,我尝试将其中的类进行分类,以便记忆和在不同场景下应用。
日期时间:
年月日:
时间间隔:
时区:
工具类:
以下的方法,几乎time包下的所有可实例化类都能调用。
now()方法:
of()方法:
with()方法:修改时间
parse()方法:
plus()方法:
minus()方法:对时间进行减法操作
format()方法:对时间的显示格式进行转换
to()方法:把当前时间对象转换成另外一个
at()方法:把这个对象与另一个对象组合起来
time包与util包中的Calendar对比
import java.time.Instant;
public class Main {
public static void main(String[] args) {
Instant instant = Instant.now(); //获取当前瞬时的时间
System.out.println(instant); //输出:2021-02-10T14:04:54.647769800Z
}
}
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
public class Main {
public static void main(String[] args) {
//系统默认本地时钟:SystemClock
Clock clock = Clock.systemDefaultZone();
System.out.println(clock.getZone()); //获取时区信息:Asia/Singapore
Instant instant = clock.instant();
System.out.println(instant); //获取日期时间:2021-01-19T01:19:46.595993200Z
//偏移时钟:OffsetClock
Clock clock01 = Clock.systemDefaultZone();
Clock pastClock = clock01.offset(clock01, Duration.ofMillis(-10000));
System.out.println(pastClock.getZone()); //时区:Asia/Singapore
System.out.println(clock01.millis() - pastClock.millis());//时间差:当前时间和过去pastClock相差10000毫秒
}
}
LocalDate、LocalTime、LocalDateTime:获取所在时区的详细时间
实例:为LocalDateTime添加时区信息
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Main {
public static void main(String[] args) {
// 1、封装 LocalDateTime对象,参数自定义 ->2018年11月11日 8点54分38秒
LocalDateTime localDateTime = LocalDateTime.of(2018, Month.NOVEMBER, 11, 8, 54, 38);
// 2、 localDateTime对象现在只是封装了一个时间,并没有时区相关的数据,所以要添加时区信息到对象中,使用 atZone()方法
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of("Asia/Shanghai"));
//上海当前的时间是:2018-11-11T08:54:38+08:00[Asia/Shanghai]
System.out.println("上海当前的时间是:" + zonedDateTime);
// 3、更改时区查看其它时区的当前时间,通过 withZoneSameInstant()方法即可
ZonedDateTime tokyoZonedDateTime = zonedDateTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
//在上海同一时刻,Asia/Tokyo的时间是:2018-11-11T09:54:38+09:00[Asia/Tokyo]
System.out.println("在上海同一时刻,Asia/Tokyo的时间是:" + tokyoZonedDateTime);
}
}
Duration操作的时间跨度是时分秒,外加纳秒
Period操作的时间跨度是年月日
period实例:
import java.time.LocalDateTime;
import java.time.Period;
//需求:今天程序员小张查看自己的车辆保险记录的时候看到还有2年3月8天就到期了,计算到期的时间是什么时候
public class Main {
public static void main(String[] args) {
// 1、封装当前时间 -> now方法
LocalDateTime now = LocalDateTime.now();
// 2、在当前时间的基础上进行+2年,+3月,+8天的操作
// 然后获得一个截止日期对象,这个对象表示的时间就是保险到期的时间。
LocalDateTime endTime = now.plusYears(2).plusMonths(3).plusDays(8);
//当前的时间是:2020-05-03T13:52:34.316278400保险到期的时间是:2022-08-11T13:52:34.316278400
System.out.println("当前的时间是:" + now + "保险到期的时间是:" + endTime);
// plus(TemporalAmount amountToAdd)
// 1、首先封装 priod.of()方法传进去参数
Period period = Period.of(2, 3, 8);
// 2、通过 now()方法的 plus()方法传进去
LocalDateTime endTime1 = now.plus(period);
//当前的时间是:2020-05-03T14:06:16.239460900保险到期的时间是:2022-08-11T14:06:16.239460900
System.out.println("当前的时间是:" + now + "保险到期的时间是:" + endTime1);
}
}
把时间的显示格式指定为自己想要的格式,功能就如java.util.SimpleDateFormat类一样。
直接调用format方法进行时间格式化,直接调用parse方法进行时间的解析。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) {
LocalDateTime localDateTime = LocalDateTime.now();
// localDateTime对象可以直接调用format方法进行格式化
String s1 = localDateTime.format(DateTimeFormatter.ISO_DATE); //ISO_DATE:2020-05-03
String s2 = localDateTime.format(DateTimeFormatter.ISO_DATE_TIME); //ISO_DATE_TIME:2020-05-03T17:55:04.1945268
String s3 = "2066-01-06T17:55:04.1945268";
// 解析字符串的方式通过LocalDateTime类的静态方法parse传入需要解析的字符串即可
LocalDateTime parse = LocalDateTime.parse(s3); //如果不指定转换格式,则默认为ISO_LOCAL_DATE_TIME(看源码可得)
System.out.println(parse);
}
}
通过DateTimeFormatter的ofLocalizedDate的方法也可以调整格式化的方式。其参数需要FormaStyle枚举类。
FormaStyle枚举类:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
public class Main {
public static void main(String[] args) {
LocalDateTime localDateTime = LocalDateTime.now();
// 通过DateTimeFormatter 的 ofLocalizedDate指定解析格式
String r1 = localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL));
String r2 = localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG));
String r3 = localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM));
String r4 = localDateTime.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT));
//FULL:2020年5月3日星期日
System.out.println("FULL:" + r1);
//LONG:2020年5月3日
System.out.println("LONG:" + r2);
//MEDIUM:2020年5月3日
System.out.println("MEDIUM:" + r3);
//SHORT:2020/5/3
System.out.println("SHORT:" + r4);
}
}
按规则进行转换
java.time.format.DateTimeFormatter
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class Main {
//时间日期与String互转
public static void main(String[] args) throws ParseException {
/*
时间format
*/
//java.util包中对时间的处理
Date date = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
String dateStr = simpleDateFormat.format(date);
System.out.println("SimpleDateFormat: " +dateStr);
//java.time包中对时间的处理
LocalDateTime localDateTime = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy月MM月dd日 HH时mm分ss秒");
String dateFormatter = formatter.format(localDateTime);
System.out.println("DateTimeFormatter: " +dateFormatter);
/*
时间parse
*/
String dateStrParse01 = "2066-06-06 16:16:16";
SimpleDateFormat sdfParse = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //pattern中的'-',':'要和串中的一致
Date dateParse = sdfParse.parse(dateStrParse01); //向外抛ParseException异常
System.out.println("SimpleDateFormat Parse: " +dateParse);//+sdfParse.format(dateParse));
String dateStrParse02 = "2077-07-07 06:06:06";
DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTimeParse = LocalDateTime.parse(dateStrParse02, parser);
System.out.println("DateTimeFormatter Parse: " +localDateTimeParse);
}
}
在很多时候,我们需要对时间进行调整操作。例如,将当前时间调节到下个周的周日,下一个儿童节,上一个儿童节,我们就需要用到时间调节器。
有关时间调节器的API,都在java.time.temporal包中。
TemporalAdjuster功能:
设计思想:
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
public class Main {
//TemporalAdjusters枚举类用法实例
public static void main(String[] args) {
LocalDate now = LocalDate.now();
//返回当前月份的第一天的日期
System.out.println(now.with(TemporalAdjusters.firstDayOfMonth()));
//返回当前月份的最后一天
System.out.println(now.with(TemporalAdjusters.lastDayOfMonth()));
//返回下一个月的第一天的日期
System.out.println(now.with(TemporalAdjusters.firstDayOfNextMonth()));
}
}
要自定义日期时间的更改逻辑,可以通过实现TemporalAdjuster类接口的方式来完成。
步骤:
需求:假如员工一个月中领取工资,发薪日是每个月的15日,如果发薪日是周六、周末,则调整为周五。
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjuster;
import java.time.temporal.TemporalAdjusters;
public class Main {
public static void main(String[] args) {
// 封装 LocalDate 对象为2018年12月1日
LocalDate payDay = LocalDate.of(2018, Month.DECEMBER, 15);
// 计算 payDay 的真实发薪日是什么时候
LocalDate realPayDay = LocalDate.from(new PayDayAdjuster().adjustInto(payDay));
//预计的发薪日是:2018-12-15
System.out.println("预计的发薪日是:" + payDay);
//真实的发薪日是:2018-12-14
System.out.println("真实的发薪日是:" + realPayDay);
}
}
/**
* 自定义调整器
* 传入一个日期时间对象,判断是不是15号,如果不是就修改为15号,如果是周六或者是周日,则改为周五
*/
class PayDayAdjuster implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
// Temporal 是time包下所有日期事件类对象的顶级父接口,实际上可以理解为传入的是 LocalDate或者 LocalTime对象
// 1、需要将 temporal 转换为 LocalDate 对象
LocalDate payDay = LocalDate.from(temporal);
// 2、判断当前封装的时间中的日期是不是当月15号,如果不是就修改为15
int day;
if(payDay.getDayOfMonth() != 15) {
day = 15;
}else {
day = payDay.getDayOfMonth();
}
// 将 payDay中的值改为15
LocalDate realPayDay = payDay.withDayOfMonth(day);
// 3、判断 readPayDay 对象中封装的星期数是不是周六或者是周日,如果是就改为上一周周五
if (realPayDay.getDayOfWeek() == DayOfWeek.SUNDAY ||
realPayDay.getDayOfWeek() == DayOfWeek.SATURDAY) {
// 如果发薪日是周末则修改为上一个周五
realPayDay = realPayDay.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
}
return realPayDay;
}
}
计划日期距离某一天特定天数差距多少天,可以自定义类实现TemporalQuery接口并且作为参数传到query方法中。
需求:计算当前时间距离下一个劳动节还有多少天?
import java.time.LocalDate;
import java.time.Month;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQuery;
public class Main {
public static void main(String[] args) {
// 计算距离下一个劳动节还有多少天
LocalDate now = LocalDate.now();
// 调用now的query方法,然后将我们自己的实现类UtilDayQueryImpl作为参数传入
Long day = now.query(new UtilDayQueryImpl());
//2020-05-03
System.out.println(now);
//363
System.out.println(day);
}
}
//计算当前时间距离下一个劳动节还有多少天?
class UtilDayQueryImpl implements TemporalQuery {
@Override
public Long queryFrom(TemporalAccessor temporal) {
// 1、TemporalAccessor是LocalDateTime的顶级父接口,相当于LocalDate就是这个接口,将Temporal转换为
// LocalDate 进行使用
LocalDate now = LocalDate.from(temporal);
// 2、封装当年劳动节时间
LocalDate laborDay = LocalDate.of(now.getYear(), Month.MAY, 1);
// 3、判断当前时间是否已经超过了当年的劳动节
if (now.isAfter(laborDay)) {
laborDay = laborDay.plusYears(1);
}
// 4、通过ChronoUnit的between来计算两个时间电的差额
long days = ChronoUnit.DAYS.between(now, laborDay);
return days;
}
}
计算任意时间距离圣诞节、儿童节、劳动节相差多少天
import java.time.LocalDate;
import java.time.Month;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQuery;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// 1、封装任意日期
LocalDate date = LocalDate.of(2020, 5, 30);
// 2、调用date 对象的query方法查询距离三个节日的差额
Map query = date.query(new CalculatorHappyDay());
// 3、打印结果
System.out.println(query.get("ChristmasDay"));
System.out.println(query.get("ChildrensDay"));
System.out.println(query.get("LaborDay"));
}
}
/**
* 类型:计算任意时间距离圣诞节、儿童节、劳动节相差多少天
* 说明:TemporalQuery的泛型类型表明要返回的类型
* String表示节日类型;Long表示多少天
*/
class CalculatorHappyDay implements TemporalQuery
其中:
java.time.temporal.ChronoUnit
java.time.temporal.ChronoField
旧API转time包下的API
time包与JDBC
转换方案:
toInstant()方法
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
public class Main {
public static void main(String[] args) {
// 初始化Date()对象
Date d = new Date();
// 1、将Date()对象转换成Instant对象
Instant i = d.toInstant();
// 2、Date类包含日期和时间信息,但是不提高时区信息,和Instant一样,通过Instant的anZone()来进行添加时区信息
ZonedDateTime zonedDateTime = i.atZone(ZoneId.systemDefault());
// 3、通过LocalDate方法转换
LocalDate date = zonedDateTime.toLocalDate();
// 转换之前Sun May 03 17:21:18 CST 2020
System.out.println("转换之前" + d);
// 转换之后2020-05-03
System.out.println("转换之后" + date);
}
}
java.sql.Date和java.sql.TimeStamp类转换
import java.sql.Date;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
public class Main {
public static void main(String[] args) {
// 初始化 Date对象
Date d = new Date();
/*
* java.sql.Date类提供了转换为LocalDate的方法,那么可以将java.util.Date先转换为java.sql.Date
* 通过java.sql.Date提供的方式转换为LocalDate.
* java.sql.Date类构造的时候需要毫秒值, -> java.Util.Date类中提供了一个获取毫秒值的方法, getTime()
* */
java.sql.Date date = new java.sql.Date(d.getTime());
LocalDate localDate = date.toLocalDate();
// 转换前的java.util.Date对象是:Sun May 03 17:39:46 CST 2020
System.out.println("转换前的java.util.Date对象是:" + d);
// 转换前的java.sql.Date对象是:2020-05-03
System.out.println("转换前的java.sql.Date对象是:" + date);
// 转换前的java.time.LocalDate对象是:2020-05-03
System.out.println("转换前的java.time.LocalDate对象是:" + localDate);
}
public static void transfer() {
/*
* sql.Date转LocalDate
*/
// 初始化 java.sql.Date 对象
Date d = new Date(System.currentTimeMillis());
// java.sql.Date 自带了转换成LocalDate 的方法, toLocalDate.
LocalDate date = d.toLocalDate();
// 转换前的java.sql.Date对象是:2020-05-03
System.out.println("转换前的java.sql.Date对象是:" + d);
// 转换后的java.time.LocalDate对象是:2020-05-03
System.out.println("转换后的java.time.LocalDate对象是:" + date);
/*
* sql.TimeStamp转LocalDateTime
*/
// 初始化java.sql.Timestamp对象, 世界戳对象
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
// java.sql.Timestamp 中也自带了转换成LocalDate 的方法 toLocalDate
LocalDateTime localDateTime = timestamp.toLocalDateTime();
// 转换之前的java.sql.Timestamp对象是:2020-05-03 17:25:29.057
System.out.println("转换之前的java.sql.Timestamp对象是:" + timestamp);
// 转换之后的java.time.LocalDateTime对象是:2020-05-03T17:25:29.057
System.out.println("转换之后的java.time.LocalDateTime对象是:" + localDateTime);
}
}
Calendar对象可以获取到年月日时分秒的信息,这些信息可以作为LocalDateTime构造方法的参数
import java.time.LocalDateTime;
import java.util.Calendar;
public class Main {
public static void main(String[] args) {
// 1、初始化Calendar对象
Calendar calendar = Calendar.getInstance();
// 2、通过get获取Calendar对象中封装的数据
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 minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
// 3、将以上获取的六个参数作为LocalDateTime的of方法的参数传递,封装的月份是从0开始的,所以month要加1
LocalDateTime localDateTime = LocalDateTime.of(year, month + 1, day, hour, minute, second);
System.out.println(localDateTime);
}
}
Calendar对象字Java1.1开始提供了一个方法获取时区对象的方法,getTimeZone(),要将Calendar对象转换为ZonedDateTime需要先获取到时区对象。从Java1.8开始TimeZone类提供了一个方法可以获取到ZonedId。获取到ZonedId之后就可以初始化ZOnedDateTime对象了,ZonedDateTime类有一个ofInstant()方法,可以将一个Instant对象和ZonedId对象作为参数传入构造一个ZonedDateTime对象。
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.TimeZone;
public class Main {
public static void main(String[] args) {
// 1、初始化一个Calendar对象
Calendar calendar = Calendar.getInstance();
// 2、Calendar对象自java1.1以后开始提供了一个方法用于获取时区对象getTimeZone()方法,要将Calendar转换为
// ZoneedDateTime对象需要先获取到时区对象
TimeZone timeZone = calendar.getTimeZone();
// 3、从java1.8开始TimeZone类提供了一个方法可以获取到ZoneId -> 拿拿ZoneId对象来构建ZonedDateTime
ZoneId zoneId = timeZone.toZoneId();
// 4、zonedDateTime类有一个ofInstant方法。可以将Instant对象和ZoneId对象封装为一个ZonedDateTime
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(calendar.toInstant(), zoneId);
// 转换之前的Calendar对象是:java.util.GregorianCalendar[time=1588499141905,areFieldsSet=true,areAll
// FieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",.......
System.out.println("转换之前的Calendar对象是:" + calendar);
// 转换之后的ZonedDateTime对象是:2020-05-03T17:45:41.905+08:00[Asia/Shanghai]
System.out.println("转换之后的ZonedDateTime对象是:" + zonedDateTime);
}
}
总结:
参考资料: