1. Java中与日期相关的类
1.1 java.util包
类名 | 具体描述 |
---|---|
Date | Date对象算是JAVA中历史比较悠久的用于处理日期、时间相关的类了,但是随着版本的迭代演进,其中的众多方法都已经被弃用,所以Date更多的时候仅被用来做一个数据类型使用,用于记录对应的日期与时间信息 |
Calender | 为了弥补Date对象在日期时间处理方法上的一些缺陷,JAVA提供了Calender抽象类来辅助实现Date相关的一些日历日期时间的处理与计算。 |
TimeZone | Timezone类提供了一些有用的方法用于获取时区的相关信息 |
① Date类
@Test
void test06(){
Date date1 = new Date();
// 获取当前时间后 +100 ms时间
Date date2 = new Date(System.currentTimeMillis() + 100);
System.out.println(date1);
System.out.println(date1.compareTo(date2));
System.out.println(date1.before(date2));
}
结果
Fri Jul 22 15:31:16 CST 2022
-1
true
② Calendar 日历类
总体来说,Date是一个设计相当糟糕的类,因此Java官方推荐尽量少用Date的构造器和方法。
如果需要对日期、时间进行加减运算,或获取指定时间的年、月、日、时、分、秒信息,可使用Calendar工具类。
示例
@Test
void test05(){
Calendar calendar = Calendar.getInstance();
// Calendar.YEAR 表示当前年
int year = calendar.get(Calendar.YEAR);
// Calendar.MONTH表示月份,但是为了计算方便,是从0开始算,所以显示出来是月份 -1 的
int month = calendar.get(Calendar.MONTH);
// Calendar.DAY_OF_MONTH 在这个月 的这一天
int dom = calendar.get(Calendar.DAY_OF_MONTH);
// Calendar.DAY_OF_YEAR 在这一年 的这一天
int doy = calendar.get(Calendar.DAY_OF_YEAR);
// Calendar.DAY_OF_WEEK 在这一周 的这一天,从星期日当第一天从1开始算的,所以会是 +1
int dow = calendar.get(Calendar.DAY_OF_WEEK);
// Calendar.DAY_OF_WEEK_IN_MONTH 在这一个月 这一天在 第几周
int dowim = calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH);
System.out.println(year+"年"+ month+"月");
System.out.println(dom+"日");
System.out.println(doy+"日");
System.out.println(dow+"日");
System.out.println(dowim);
}
结果
2022年6月20日11时8分19秒859毫秒
AM_PM: 0
HOUR: 11
DAY_OF_MONTH: 20日
DAY_OF_YEAR: 201日
DAY_OF_WEEK: 4日
DAY_OF_WEEK_IN_MONTH: 3
- Calendar.DAY_OF_MONTH 在这个月 的这一天,但是为了计算方便,是从0开始算,所以显示出来是月份 -1 的
- Calendar.DAY_OF_YEAR 在这一年 的这一天
- Calendar.DAY_OF_WEEK 在这一周 的这一天,从星期日当第一天从1开始算的,所以会是 +1
- Calendar.DAY_OF_WEEK_IN_MONTH 在这一个月 这一天在 第几周
- Calendar.HOUR 表示今天这一天的小时(0-11),分上午和下午
具体可以看Calendar的静态属性,不需要刻意记
常用api
Calendar类提供了大量访问、修改日期时间的方法 ,常用方法如下:
方法 | 描述 |
---|---|
void add(int field, int amount) | 根据日历的规则,为给定的日历字段添加或减去指定的时间量。 |
int get(int field) | 返回指定日历字段的值。 |
int getActualMaximum(int field) | 返回指定日历字段可能拥有的最大值。例如月,最大值为11。 |
int getActualMinimum(int field) | 返回指定日历字段可能拥有的最小值。例如月,最小值为0。 |
void roll(int field, int amount) | 与add()方法类似,区别在于加上 amount后超过了该字段所能表示的最大范围时,也不会向上一个字段进位。 |
void set(int field, int value) | 将给定的日历字段设置为给定值。 |
void set(int year, int month, int date) | 设置Calendar对象的年、月、日三个字段的值。 |
void set(int year, int month, int date, int hourOfDay, int minute, int second) | 设置Calendar对象的年、月、日、时、分、秒6个字段的值。 |
上面的很多方法都需要一个int类型的field参数, field是Calendar类的类变量,如 Calendar.YEAR、Calendar.MONTH等分别代表了年、月、日、小时、分钟、秒等时间字段。**需要指出的是, Calendar.MONTH字段代表月份,月份的起始值不是1,而是O,所以要设置8月时,用7而不是8。**如上面演示的程序就示范了Calendar类的常规用法。
add和roll的区别
add
add(int field, int amount)的功能非常强大,add主要用于改变Calendar的特定字段的值。
- 如果需要增加某字段的值,则让 amount为正数;
- 如果需要减少某字段的值,则让 amount为负数即可。
具体的field操作可以看:Calendar的add()方法介绍
add(int field, int amount)有如下两条规则:
- 当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大。
- 如果下一级字段也需要改变,那么该字段会修正到变化最小的值。
@Test
void test07(){
Calendar cal1 = Calendar.getInstance();
// 2003-8-23
cal1.set(2003, 7, 23, 0, 0, 0);
// 2003-8-23 => 2004-2-23
cal1.add(Calendar.MONTH, 6);
System.out.println(cal1.getTime());
Calendar cal2 = Calendar.getInstance();
// 2003-8-31
cal2.set(2003, 7, 31, 0, 0, 0);
// 因为进位后月份改为2月,2月没有31日,自动变成29日,若不是闰年则变成28日
// 2003-8-31 => 2004-2-29
cal2.add(Calendar.MONTH, 6);
System.out.println(cal2.getTime());
}
对于上面的例子,8-31就会变成2-29。**因为MONTH 的下一级字段是DATE,从31到29改变最小(若不是闰年则变成28日)。**所以上面2003-8-31的MONTH字段增加6后,不是变成2004-3-2,而是变成2004-2-29。
结果
Mon Feb 23 00:00:00 CST 2004
Sun Feb 29 00:00:00 CST 2004
roll
roll()的规则与add()的处理规则不同—— 当被修改的字段超出它允许的范围时,上一级字段不会增大。
@Test
void test08(){
Calendar cal1 = Calendar.getInstance();
// 2003-8-23
cal1.set(2003, 7, 23, 0, 0, 0);
// 2003-8-23 => 2003-2-23
cal1.roll(Calendar.MONTH, 6);
System.out.println(cal1.getTime());
Calendar cal2 = Calendar.getInstance();
cal2.set(2003, 7, 31, 0, 0, 0);
// MONTH字段“进位”后变成2,2月没有31日
// YEAR字段不会改变,2003年2月只有28天
// 2003-8-31 => 2003-2-28
cal2.roll(Calendar.MONTH, 6);
System.out.println(cal2.getTime());
}
结果
Sun Feb 23 00:00:00 CST 2003
Fri Feb 28 00:00:00 CST 2003
设置Calendar的容错性
调用Calendar对象的set()方法来改变指定时间字段的值时,有可能传入一个不合法的参数,例如为MONTH字段设置13,这将会导致怎样的后果呢?看如下程序:
@Test
void test09(){
Calendar cal = Calendar.getInstance();
System.out.println(cal.getTime());
// ① 结果是Year字段+1,MONTH字段为1(2月)
cal.set(Calendar.MONTH, 13);
System.out.println(cal.getTime());
// 关闭容错性
cal.setLenient(false);
// ② 导致运行异常
cal.set(Calendar.MONTH, 13);
System.out.println(cal.getTime());
}
上面程序①②两处的代码完全相似,但它们运行的结果不一样:
- ①处代码可以正常运行,因为设置MONTH字段的值为13,将会导致YEAR字段加1;
- ②处代码将会导致运行时异常,因为设置的MONTH字段值超出了MONTH字段允许的范围。
关键在于程序中粗体字代码行,Calendar提供了一个setLenient()用于设置它的容错性,Calendar默认支持较好的容错性,通过 setLenient(false)可以关闭Calendar的容错性,让它进行严格的参数检查。
Calendar有两种解释日历字段的模式:lenient模式和non-lIenient模式:
- 当Calendar 处于lenient模式时,每个时间字段可接受超出它允许范围的值;
- 当Calendar 处于 non-lenient模式时,如果为某个时间字段设置的值超出了它允许的取值范围,程序将会抛出异常。
set
set()方法延迟修改 :set(f, value)方法将日历字段f更改为value,此外它还设置了一个内部成员变量,以指示日历字段f已经被更改。
尽管日历字段f是立即更改的,但该Calendar所代表的时间却不会立即修改,直到下次调用get()、getTime()、getTimeInMillis()、add()或roll()时才会重新计算日历的时间。
这被称为 set()方法的延迟修改,采用延迟修改的优势是多次调用set()不会触发多次不必要的计算(需要计算出一个代表实际时间的long型整数)。
@Test
void test10(){
Calendar cal = Calendar.getInstance();
// 2003-8-31
cal.set(2003, 7, 31);
cal.set(Calendar.MONTH, 8);
// ① 将月份设置为9月,但是9月没有31号,如果立即修改,系统会把cal自动调整为10月1日
// System.out.println(cal.getTime());
// 设置DATE字段为5
cal.set(Calendar.DATE, 5);
// 输出结果为 2003-9-5
System.out.println(cal.getTime());
}
结果
Fri Sep 05 16:59:50 CST 2003
如果程序将①处代码注释起来,因为Calendar的 set()方法具有延迟修改的特性,即调用set()方法后Calendar实际上并未计算真实的日期,它只是使用内部成员变量表记录MONTH字段被修改为8,接着程序设置DATE字段值为5,程序内部再次记录DATE字段为5——就是9月5日,因此最后输出2003-9-5。
1.2 java.time包
JAVA8之后新增了
java.time
包,提供了一些与日期时间有关的新实现类:
具体每个类对应的含义说明梳理如下表:
类名 | 含义说明 |
---|---|
LocalDate | 获取当前的日期信息,仅有简单的日期信息,不包含具体时间、不包含时区信息。 |
LocalTime | 获取当前的时间信息,仅有简单的时间信息,不含具体的日期、时区信息。 |
LocalDateTime | 可以看做是LocalDate和LocalTime的组合体,其同时含有日期信息与时间信息,但是依旧不包含任何时区信息。 |
OffsetDateTime | 在LocalDateTime基础上增加了时区偏移量信息。 |
ZonedDateTime | 在OffsetDateTime基础上,增加了时区信息 |
ZoneOffset | 时区偏移量信息, 比如+8:00或者-5:00等 |
ZoneId | 具体的时区信息,比如Asia/Shanghai或者America/Chicago |
① LocalDate 本地日期类
LocalDate localDate = LocalDate.now();
// 也可以通过 LocalDate.of(年,月,日)去构造
System.out.println("当前日期:"+localDate.getYear()+" 年 "+localDate.getMonthValue()+" 月 "+localDate.getDayOfMonth()+"日" );
// 计算
LocalDate pluslocalDate = localDate.plusDays(1);//增加一天
LocalDate pluslocalDate = localDate.plusYears(1);//增加一年
// 对两个日期的判断,是在前、在后、或者相等。
LocalDate.isBefore(LocalDate);
LocalDate.isAfter();
LocalDate.isEqual();
//结果
当前日期:2021 年 10 月 27日
② LocalTime 本地时间类
LocalDate pluslocalDate = localDate.plusDays(1);//增加一天
LocalDate pluslocalDate = localDate.plusYears(1);//增加一年
LocalDate和LocalTime 都有类似作用的api
LocalDate.plusDays(1) 增加一天
LocalTime.plusHours(1) 增加一小时 等等~
LocalTime.isBefore(LocalTime);
LocalTime.isAfter();
③ LocalDateTime 本地日期时间类
public final class LocalDateTime ...{
private final LocalDate date;
private final LocalTime time;
}
LocalDateTime = LocalDate + LocalTime
④ Instant 类
Instant 是瞬间,某一时刻的意思
Instant.ofEpochMilli(System.currentTimeMillis())
Instant.now()
复制代码
通过Instant可以创建一个 “瞬间” 对象,ofEpochMilli()可以接受某一个“瞬间”,比如当前时间,或者是过去、将来的一个时间。 比如,通过一个“瞬间”创建一个LocalDateTime对象
LocalDateTime now = LocalDateTime.ofInstant(
Instant.ofEpochMilli(System.currentTimeMillis()),ZoneId.systemDefault());
System.out.println("当前日期:"+now.getYear()+" 年 "+now.getMonthValue()+" 月 "+now.getDayOfMonth()+"日" );
⑤ Period 类
Period 是 时期,一段时间 的意思
Period有个between方法专门比较两个日期的
LocalDate startDate = LocalDateTime.ofInstant(
Instant.ofEpochMilli(1601175465000L), ZoneId.systemDefault())
.toLocalDate();//1601175465000是2020-9-27 10:57:45
Period p = Period.between(startDate, LocalDate.now());
System.out.println("目标日期距离今天的时间差:"+p.getYears()+" 年 "+p.getMonths()+" 个月 "+p.getDays()+" 天" );
//目标日期距离今天的时间差:1 年 1 个月 1 天
查看between源码
public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive) {
return startDateInclusive.until(endDateExclusive);
}
public Period until(ChronoLocalDate endDateExclusive) {
LocalDate end = LocalDate.from(endDateExclusive);
long totalMonths = end.getProlepticMonth() - this.getProlepticMonth(); // safe
int days = end.day - this.day;
if (totalMonths > 0 && days < 0) {
totalMonths--;
LocalDate calcDate = this.plusMonths(totalMonths);
days = (int) (end.toEpochDay() - calcDate.toEpochDay()); // safe
} else if (totalMonths < 0 && days > 0) {
totalMonths++;
days -= end.lengthOfMonth();
}
long years = totalMonths / 12; // safe
int months = (int) (totalMonths % 12); // safe
return Period.of(Math.toIntExact(years), months, days);
}
他只接受两个LocalDate对象,对时间的计算,算好之后返回Period对象
⑥ Duration 类
Duration 是期间持续时间的意思
示例代码
LocalDateTime end = LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis()), ZoneId.systemDefault());
LocalDateTime start = LocalDateTime.ofInstant(Instant.ofEpochMilli(1601175465000L), ZoneId.systemDefault());
Duration duration = Duration.between(start, end);
System.out.println("开始时间到结束时间,持续了"+duration.toDays()+"天");
System.out.println("开始时间到结束时间,持续了"+duration.toHours()+"小时");
System.out.println("开始时间到结束时间,持续了"+duration.toMillis()/1000+"秒");
可以看到between也接受两个参数,LocalDateTime对象,源码是对两个时间的计算,并返回对象。
2. 时间间隔计算
2.1 Period与Duration类
JAVA8开始新增的java.time
包中有提供Duration
和Period
两个类,用于处理日期时间间隔相关的场景,两个类的区别点如下:
类 | 描述 |
---|---|
Duration | 时间间隔,用于秒级的时间间隔计算 |
Period | 日期间隔,用于天级别的时间间隔计算,比如年月日维度的 |
Duration
与Period
具体使用的时候还需要有一定的甄别,因为部分的方法很容易使用中被混淆,下面分别说明下。
2.1.1 Duration
Duration的最小计数单位为纳秒,其内部使用seconds
和nanos
两个字段来进行组合计数表示duration总长度。
Duration的常用API方法梳理如下:
方法 | 描述 |
---|---|
between | 计算两个时间的间隔,默认是秒 |
ofXxx | 以of 开头的一系列方法,表示基于给定的值创建一个Duration实例。比如ofHours(2L),则表示创建一个Duration对象,其值为间隔2小时 |
plusXxx | 以plus 开头的一系列方法,用于在现有的Duration值基础上增加对应的时间长度,比如plusDays()表示追加多少天,或者plusMinutes()表示追加多少分钟 |
minusXxx | 以minus 开头的一系列方法,用于在现有的Duration值基础上扣减对应的时间长度,与plusXxx相反 |
toXxxx | 以to 开头的一系列方法,用于将当前Duration对象转换为对应单位的long型数据,比如toDays()表示将当前的时间间隔的值,转换为相差多少天,而toHours()则标识转换为相差多少小时。 |
getSeconds | 获取当前Duration对象对应的秒数, 与toXxx方法类似,只是因为Duration使用秒作为计数单位,所以直接通过get方法即可获取到值,而toDays()是需要通过将秒数转为天数换算 之后返回结果,所以提供的方法命名上会有些许差异。 |
getNano | 获取当前Duration对应的纳秒数“零头”。注意这里与toNanos()不一样,toNanos是Duration值的纳秒单位总长度,getNano()只是获取不满1s剩余的那个零头,以纳秒表示。 |
isNegative | 检查Duration实例是否小于0,若小于0返回true, 若大于等于0返回false |
isZero | 用于判断当前的时间间隔值是否为0 ,比如比较两个时间是否一致,可以通过between计算出Duration值,然后通过isZero判断是否没有差值。 |
withSeconds | 对现有的Duration对象的nanos零头值不变的情况下,变更seconds部分的值,然后返回一个新的Duration对象 |
withNanos | 对现有的Duration对象的seconds值不变的情况下,变更nanos部分的值,然后返回一个新的Duration对象 |
关于Duration的主要API的使用,参见如下示意:
@Test
void durationTEst(){
LocalTime target = LocalTime.parse("00:02:35.700");
// 获取当前日期,此处为了保证后续结果固定,注掉自动获取当前日期,指定固定日期
// LocalDate today = LocalDate.now();
LocalTime today = LocalTime.parse("12:12:25.600");
// 输出:12:12:25.600
System.out.println(today);
// 输出:00:02:35.700
System.out.println(target);
Duration duration = Duration.between(target, today);
// 输出:PT12H9M49.9S
System.out.println(duration);
// 输出:43789
System.out.println(duration.getSeconds());
// 输出:900000000
System.out.println(duration.getNano());
// 输出:729
System.out.println(duration.toMinutes());
// 输出:PT42H9M49.9S
System.out.println(duration.plusHours(30L));
// 输出:PT15.9S
System.out.println(duration.withSeconds(15L));
}
2.1.2 Period
Period相关接口与Duration类似,其计数的最小单位是天
,看下Period内部时间段记录采用了年、月、日三个field来记录:
常用的API方法列举如下:
方法 | 描述 |
---|---|
between | 计算两个日期之间的时间间隔。注意,这里只能计算出相差几年几个月几天。 |
ofXxx | of() 或者以of 开头的一系列static 方法,用于基于传入的参数构造出一个新的Period对象 |
withXxx | 以with 开头的方法,比如withYears 、withMonths 、withDays 等方法,用于对现有的Period对象中对应的年、月、日等字段值进行修改(只修改对应的字段,比如withYears方法,只修改year,保留month和day不变),并生成一个新的Period对象 |
getXxx | 读取Period中对应的year 、month 、day 字段的值。注意下,这里是仅get其中的一个字段值,而非整改Period的不同单位维度的总值。 |
plusXxx | 对指定的字段进行追加数值操作 |
minusXxx | 对指定的字段进行扣减数值操作 |
isNegative | 检查Period实例是否小于0,若小于0返回true, 若大于等于0返回false |
isZero | 用于判断当前的时间间隔值是否为0 ,比如比较两个时间是否一致,可以通过between计算出Period值,然后通过isZero判断是否没有差值。 |
关于Period的主要API的使用,参见如下示意:
@Test
void periodTest(){
LocalDate target = LocalDate.parse("2021-07-11");
// 获取当前日期,此处为了保证后续结果固定,注掉自动获取当前日期,指定固定日期
// LocalDate today = LocalDate.now();
LocalDate today = LocalDate.parse("2022-07-08");
// 输出:2022-07-08
System.out.println(today);
// 输出:2021-07-11
System.out.println(target);
Period period = Period.between(target, today);
// 输出:P11M27D, 表示11个月27天
System.out.println(period);
// 输出:0, 因为period值为11月27天,即year字段为0
System.out.println(period.getYears());
// 输出:11, 因为period值为11月27天,即month字段为11
System.out.println(period.getMonths());
// 输出:27, 因为period值为11月27天,即days字段为27
System.out.println(period.getDays());
// 输出:P14M27D, 因为period为11月27天,加上3月,变成14月27天
System.out.println(period.plusMonths(3L));
// 输出:P11M15D,因为period为11月27天,仅将days值设置为15,则变为11月15天
System.out.println(period.withDays(15));
// 输出:P2Y3M44D
System.out.println(Period.of(2, 3, 44));
}
2.2 Duration与Period的坑
Duration与Period都是用于日期之间的计算操作。
- Duration主要用于秒、纳秒等维度的数据处理与计算。
- Period主要用于计算年、月、日等维度的数据处理与计算。
Duration的坑
先看个例子,计算两个日期相差的天数,使用Duration的时候:
public void calculateDurationDays(String targetDate) {
LocalDate target = LocalDate.parse(targetDate);
LocalDate today = LocalDate.now();
System.out.println("today : " + today);
System.out.println("target: " + target);
long days = Duration.between(target, today).abs().toDays();
System.out.println("相差:" + days + "天");
}
运行后会报错:
today : 2022-07-07
target: 2022-07-11
Exception in thread "main" java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
at java.time.LocalDate.until(LocalDate.java:1614)
at java.time.Duration.between(Duration.java:475)
at com.veezean.demo5.DateService.calculateDurationDays(DateService.java:24)
点击看下
Duration.between
源码,可以看到注释上明确有标注着,这个方法是用于秒级的时间段间隔计算,而我们这里传入的是两个天
级别的数据,所以就不支持此类型运算,然后抛异常了。
Period的坑
同样是计算两个日期相差的天数,再看下使用Period的实现:
public void calculateDurationDays(String targetDate) {
LocalDate target = LocalDate.parse(targetDate);
LocalDate today = LocalDate.now();
System.out.println("today : " + today);
System.out.println("target: " + target);
// 注意,此处写法错误!这里容易踩坑:
long days = Math.abs(Period.between(target, today).getDays());
System.out.println("相差:" + days + "天");
}
复制代码
执行结果:
today : 2022-07-07
target: 2021-07-07
相差:0天
执行是不报错,但是结果明显是错误的。这是因为getDays()并不会将Period值换算为天数,而是单独计算年、月、日,此处只是返回天数这个单独的值。
再看下面的写法:
public void calculateDurationDays(String targetDate) {
LocalDate target = LocalDate.parse(targetDate);
LocalDate today = LocalDate.now();
System.out.println("today : " + today);
System.out.println("target: " + target);
Period between = Period.between(target, today);
System.out.println("相差:"
+ Math.abs(between.getYears()) + "年"
+ Math.abs(between.getMonths()) + "月"
+ Math.abs(between.getDays()) + "天");
}
结果为:
today : 2022-07-07
target: 2021-07-11
相差:0年11月26天
所以说,如果想要计算两个日期之间相差的绝对天数,用Period不是一个好的思路。
2.3 计算日期差
2.3.1 通过LocalDate来计算
LocalDate中的
toEpocDay
可返回当前时间距离原点时间之间的天数,可以基于这一点,来实现计算两个日期之间相差的天数:
代码如下:
public void calculateDurationDays(String targetDate) {
LocalDate target = LocalDate.parse(targetDate);
LocalDate today = LocalDate.now();
System.out.println("today : " + today);
System.out.println("target: " + target);
long days = Math.abs(target.toEpochDay() - today.toEpochDay());
System.out.println("相差:" + days + "天");
}
结果为:
today : 2022-07-07
target: 2021-07-11
相差:361天
2.3.2 通过时间戳来计算
如果是使用的
Date
对象,则可以通过将Date日期转换为毫秒时间戳
的方式相减然后将毫秒数转为天数的方式来得到结果。需要注意的是通过毫秒数计算日期天数的差值时,需要屏蔽掉时分秒带来的误差影响。
数学逻辑计算(不推荐)
分别算出年、月、日差值,然后根据是否闰年、每月是30还是31天等计数逻辑,纯数学硬怼方式计算。
不推荐、代码略…
计算接口处理耗时
在一些性能优化的场景中,我们需要获取到方法处理的执行耗时,很多人都是这么写的:
public void doSomething() {
// 记录开始时间戳
long startMillis = System.currentTimeMillis();
// do something ...
// 计算结束时间戳
long endMillis = System.currentTimeMillis();
// 计算相差的毫秒数
System.out.println(endMillis - startMillis);
}
当然啦,如果你使用的是JDK8+
的版本,你还可以这么写:
public void doSomething() {
// 记录开始时间戳
Instant start = Instant.now();
// do something ...
// 计算结束时间戳
Instant end = Instant.now();
// 计算相差的毫秒数
System.out.println(Duration.between(start, end).toMillis());
}
2.4 计算时间差
1️⃣ 使用Hutool工具进行计算
一款超厉害的国产Java工具——Hutool。Hutool是一个Java工具包类库,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类。适用于很多项目以及Web开发,并且与其他框架没有耦合性。
工具使用说明——Hutool 指南 API
引入依赖
<!-- https://mvnrepository.com/artifact/com.xiaoleilu/hutool-all -->
<dependency>
<groupId>com.xiaoleilu</groupId>
<artifactId>hutool-all</artifactId>
<version>3.3.2</version>
</dependency>
2️⃣ 封装时间类进行计算
制作Calendar工具类计算
基于Calendar对时间计算进行相应的封装处理,如下面两个例子,可以根据需求将相关的计算封装在一个Util工具类中
获取本周开始时间戳
/** * start * 本周开始时间戳 */
public static Date getWeekStartTime() {
Calendar calendar = Calendar.getInstance();
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
if (dayOfWeek == 0){
dayOfWeek = 7;
}
calendar.add(Calendar.DATE, - dayOfWeek + 1);
calendar.set(Calendar.HOUR_OF_DAY, 0);
//将分钟至0
calendar.set(Calendar.MINUTE, 0);
//将秒至0
calendar.set(Calendar.SECOND, 0);
//将毫秒至0
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
根据日期和天数进行计算
/** * 获取当前时间的月几号0点时间或第二天0时间戳(即几号的24点) * @param calendar 当前时间对象 * @param day 几号, 值范围 是1 到 当前时间月天数 + 1 整数, * 传入(day+1)为day号的第二天0点时间(day号的24点时间), * 如果值为当前时间月天数+1则结果为当前月的下个月1号0点(即当月最后一天的24点), * 如果当前月的天数为31天, 传入32时则为当前月的下个月1号0点(即当月最后一天的24点) * @return */
public static Date getDayOfMonthStartOrEndTime(Calendar calendar, int day) {
Calendar calendarTemp = Calendar.getInstance();
calendarTemp.setTime(calendar.getTime());
int days = getDaysOfMonth(calendarTemp);
int limitDays = days + 1;
if (day > limitDays) {
calendarTemp.set(Calendar.DAY_OF_MONTH, limitDays);
} else {
if (day >= 1) {
calendarTemp.set(Calendar.DAY_OF_MONTH, day);
} else {
calendarTemp.set(Calendar.DAY_OF_MONTH, 1);
}
}
//将小时至0
calendarTemp.set(Calendar.HOUR_OF_DAY, 0);
//将分钟至0
calendarTemp.set(Calendar.MINUTE, 0);
//将秒至0
calendarTemp.set(Calendar.SECOND, 0);
//将毫秒至0
calendarTemp.set(Calendar.MILLISECOND, 0);
//获得当前月几号0点或几号的第二天0点(即几号的24点)
Date startTime = calendarTemp.getTime();
return startTime;
}
制作Date工具类计算
3. 时间格式转换
项目中,时间格式转换是一个非常典型的日期处理操作,可能会涉及到将一个字符串日期转换为JAVA对象,或者是将一个JAVA日期对象转换为指定格式的字符串日期时间。
3.1 SimpleDataFormat实现
在JAVA8之前,通常会使用SimpleDateFormat
类来处理日期与字符串之间的相互转换:
public void testDateFormatter() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 日期转字符串
String format = simpleDateFormat.format(new Date());
System.out.println("当前时间:" + format);
try {
// 字符串转日期
Date parseDate = simpleDateFormat.parse("2022-07-08 06:19:27");
System.out.println("转换后Date对象: " + parseDate);
// 按照指定的时区进行转换,可以对比下前面转换后的结果,会发现不一样
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+5:00"));
parseDate = simpleDateFormat.parse("2022-07-08 06:19:27");
System.out.println("指定时区转换后Date对象: " + parseDate);
} catch (Exception e) {
e.printStackTrace();
}
}
输出结果如下:
当前时间:2022-07-08 06:25:31
转换后Date对象: Fri Jul 08 06:19:27 CST 2022
指定时区转换后Date对象: Fri Jul 08 09:19:27 CST 2022
G 年代标志符
y 年
M 月
d 日
h 时 在上午或下午 (1~12)
H 时 在一天中 (0~23)
m 分
s 秒
S 毫秒
E 星期
D 一年中的第几天
F 一月中第几个星期几
w 一年中第几个星期
W 一月中第几个星期
a 上午 / 下午 标记符
k 时 在一天中 (1~24)
K 时 在上午或下午 (0~11)
z 时区
补充说明:
SimpleDateFormat对象是非线程安全的,所以项目中在封装为工具方法使用的时候需要特别留意,最好结合ThreadLocal来适应在多线程场景的正确使用。 JAVA8之后,推荐使用DateTimeFormat替代SimpleDateFormat。
3.2 DataTimeFormatter实现
JAVA8开始提供
DataTimeFormatter
作为新的用于日期与字符串之间转换的类,它很好的解决了SimpleDateFormat多线程的弊端,也可以更方便的与java.time
中心的日期时间相关类的集成调用。
public void testDateFormatter() {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localDateTime = LocalDateTime.now();
// 格式化为字符串
String format = localDateTime.format(dateTimeFormatter);
System.out.println("当前时间:" + format);
// 字符串转Date
LocalDateTime parse = LocalDateTime.parse("2022-07-08 06:19:27", dateTimeFormatter);
Date date = Date.from(parse.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("转换后Date对象: " + date);
}
输出结果:
当前时间:2022-07-19 17:19:27
转换后Date对象: Fri Jul 08 06:19:27 CST 2022
3.3 日期时间格式模板
对于计算机而言,时间处理的时候按照基于时间原点的数字进行处理即可,但是转为人类方便识别的场景显示时,经常会需要转换为不同的日期时间显示格式,比如:
2022-07-08 12:02:34
2022/07/08 12:02:34.238
2022年07月08日 12点03分48秒
在JAVA中,为了方便各种格式转换,提供了基于时间模板
进行转换的实现能力:
时间格式模板中的字幕含义说明如下:
字母 | 使用说明 |
---|---|
yyyy | 4位数的年份 |
yy | 显示2位数的年份,比如2022年,则显示为22年 |
MM | 显示2位数的月份,不满2位数的,前面补0,比如7月份显示07月 |
M | 月份,不满2位的月份不会补0 |
dd | 天, 如果1位数的天数,则补0 |
d | 天,不满2位数字的,不补0 |
HH | 24小时制的时间显示,小时数,两位数,不满2位数字的前面补0 |
H | 24小时制的时间显示,小时数,不满2位数字的不补0 |
hh | 12小时制的时间显示,小时数,两位数,不满2位数字的前面补0 |
ss | 秒数,不满2位的前面补0 |
s | 秒数,不满2位的不补0 |
SSS | 毫秒数 |
z | 时区名称,比如北京时间东八区,则显示CST |
Z | 时区偏移信息,比如北京时间东八区,则显示+0800 |
4. 消失的八小时问题
4.1 日期字符串存入DB后差8小时
在后端与数据库交互的时候,可能会遇到一个问题,就是往DB中存储了一个时间字段之后,后面再查询的时候,就会发现时间数值差了8个小时
,这个需要在DB的连接信息中指定下时区信息:
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai
4.2 界面时间与后台时间差8小时
在有**一些前后端交互的项目中,**可能会遇到一个问题,就是前端选择并保存了一个时间信息,再查询的时候就会发现与设置的时间差了8个小时
,这个其实就是后端时区转换设置的问题。
SpringBoot的配置文件中,需要指定时间字符串转换的时区信息:
spring.jackson.time-zone=GMT+8
这样从接口json中传递过来的时间信息,jackson框架可以根据对应时区转换为正确的Date数据进行处理。
今天的文章Java日期时间类及计算分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/13804.html