Java日期时间类
我是你下药都得不到的男人 人气:01. 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为负数即可。
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(); //结果
② 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 计算时间差
使用Hutool工具进行计算
一款超厉害的国产Java工具——Hutool。Hutool是一个Java工具包类库,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类。适用于很多项目以及Web开发,并且与其他框架没有耦合性。
引入依赖:
<!-- https://mvnrepository.com/artifact/com.xiaoleilu/hutool-all --> <dependency> <groupId>com.xiaoleilu</groupId> <artifactId>hutool-all</artifactId> <version>3.3.2</version> </dependency>
封装时间类进行计算
制作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工具类计算
Java项目开发中常见的日期操作工具类封装:代码如下(示例):
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.Time; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalField; import java.time.temporal.WeekFields; import java.util.*; /** * @author hhlm * @description: Java日期类型相关操作类;该类负责对日期格式化转换、日期比较、日期加减、润年判断、获 * 取相关的日期信息等。 * * @date 2022年03月02日 16:24 */ public class DateUtil { private static final Logger LOGGER = LoggerFactory.getLogger(DateUtil.class); public static final long ONE_MINUTE_MILLISECOND = 60 * 1000L; public static final long ONE_HOUR_MILLISECOND = 60 * ONE_MINUTE_MILLISECOND; /** * 一天所对应的毫秒数 */ public static final long ONE_DAY_MILLISECOND = 24 * ONE_HOUR_MILLISECOND; /** * 一周所对应的毫秒数 */ public static final long ONE_WEEK_MILLISECOND = 7 * ONE_DAY_MILLISECOND; public static final long ONE_MONTH_MILLISECOND = 30 * ONE_DAY_MILLISECOND; public static final long ONE_YEAR_MILLISECOND = 365 * ONE_DAY_MILLISECOND; private static String defaultDatePattern = null; /** * 从配置文件中返回配置项"date.format",默认的日期格式符 (yyyy-MM-dd), * * @return a string representing the date pattern on the UI */ public static synchronized String getDatePattern() { defaultDatePattern = "yyyy-MM-dd"; return defaultDatePattern; } /** * 校验日期入参是否正确,如防止sql注入 * @author linjx 2018-12-24 * @param desc 入参的描述 * @param format * @param dateStr notEmptyString * @return */ public static Date validDate(String desc, String format, String dateStr) { LOGGER.debug("validDate.desc={}, format={}, dateStr={}", desc, format, dateStr); Date parse = DateUtil2.parse(dateStr, format, desc); /** format='yyyy-MM-dd',dateStr='2019-12-15' or '1'='1'是不会有ParseException的 所以需要将parse重新格式化成字符串,和dateStr比较 */ AssertApp.isTrue(dateStr.equals(DateUtil2.format(parse, format)), desc + "异常"); return parse; } /** * 获取日期的年份 * * @return 日期的年份 */ public static int getYear(Date date) { return getCalendar(date).get(Calendar.YEAR); } /** * 获取日期的月份(0-11) * * @param date * @return 日期的月份(0-11) */ public static int getMonth(Date date) { return getCalendar(date).get(Calendar.MONTH); } /** * 获取日期的一个月中的某天 * * @param date * @return 日期的一个月中的某天(1-31) */ public static int getDay(Date date) { return getCalendar(date).get(Calendar.DATE); } /** * 获取日期的一个星期中的某天 * * @param date * @return 日期的星期中日期(1:sunday-7:SATURDAY) */ public static int getWeek(Date date) { return getCalendar(date).get(Calendar.DAY_OF_WEEK); } /** * 将日期字符串按指定的格式转为Date类型 * * @param strDate 待解析的日期字符串 * @param format 日期格式 * @return 字符串对应的日期对象 */ public static final Date parseDate(String strDate, String format) { return DateUtil2.parse(strDate, format, strDate); } /** * 将日期字符串按系统配置中指定默认格式(getDatePattern()返回的格式)转为Date类型 * * @param strDate 待解析的日期字符串 * @return 字符串对应的日期对象 */ public static Date parseDate(String strDate) { return parseDate(strDate, getDatePattern()); } /** * <p>检查所给的年份是否是闰年</p> * * @param year 年 * @return 检查结果: true - 是闰年; false - 是平年 */ public static boolean isLeapYear(int year) { if (year / 4 * 4 != year) { return false; //不能被4整除 } else if (year / 100 * 100 != year) { return true; //能被4整除,不能被100整除 } else if (year / 400 * 400 != year) { return false; //能被100整除,不能被400整除 } else { return true; //能被400整除 } } /** * 按照默认格式化样式格式化当前系统时间 * * @return 日期字符串 */ public static String getCurrentTime() { return formatDate(new Date()); } /** * 按照默认格式化样式格式化当前系统时间 * * @param format String 日期格式化标准 * @return String 日期字符串。 */ public static String getCurrentTime(String format) { return formatDate(new Date(), format); } /** * 按照指定格式化样式格式化指定的日期 * * @param date 待格式化的日期 * @param format 日期格式 * @return 日期字符串 */ public static String formatDate(Date date, String format) { if (date == null) return ""; if (format == null) format = getDatePattern(); SimpleDateFormat formatter = new SimpleDateFormat(format); return formatter.format(date); } /** * 按照默认格式化样式格式化指定的日期 * * @param date 待格式化的日期 * @return 日期字符串 */ public static String formatDate(Date date) { long offset = System.currentTimeMillis() - date.getTime(); String pos = "前"; if (offset < 0) { pos = "后"; offset = -offset; } if (offset >= ONE_YEAR_MILLISECOND) return formatDate(date, getDatePattern()); StringBuilder sb = new StringBuilder(); if (offset >= 2 * ONE_MONTH_MILLISECOND) { return sb.append((offset + ONE_MONTH_MILLISECOND / 2) / ONE_MONTH_MILLISECOND).append("个月").append(pos).toString(); } if (offset > ONE_WEEK_MILLISECOND) { return sb.append((offset + ONE_WEEK_MILLISECOND / 2) / ONE_WEEK_MILLISECOND).append("周").append(pos).toString(); } if (offset > ONE_DAY_MILLISECOND) { return sb.append((offset + ONE_DAY_MILLISECOND / 2) / ONE_DAY_MILLISECOND).append("天").append(pos).toString(); } if (offset > ONE_HOUR_MILLISECOND) { return sb.append((offset + ONE_HOUR_MILLISECOND / 2) / ONE_HOUR_MILLISECOND).append("小时").append(pos).toString(); } if (offset > ONE_MINUTE_MILLISECOND) { return sb.append((offset + ONE_MINUTE_MILLISECOND / 2) / ONE_MINUTE_MILLISECOND).append("分钟").append(pos).toString(); } return sb.append(offset / 1000).append("秒").append(pos).toString(); } /** * 将date的时间部分清零 * * @param day * @return 返回Day将时间部分清零后对应日期 */ public static Date getCleanDay(Date day) { return getCleanDay(getCalendar(day)); } /** * 设置当天最后时间 * * @param day * @return 返回当天最后时间 */ public static Date getEndDay(Date day) { Calendar c = Calendar.getInstance(); c.setTime(day); c.set(Calendar.HOUR_OF_DAY, 23); c.set(Calendar.MINUTE, 59); c.set(Calendar.SECOND, 59); c.set(Calendar.MILLISECOND, 999); return c.getTime(); } /** * 获取day对应的Calendar对象 * * @param day * @return 返回date对应的Calendar对象 */ public static Calendar getCalendar(Date day) { Calendar c = Calendar.getInstance(); if (day != null) c.setTime(day); return c; } public static Date getCleanDay(Calendar c) { c.set(Calendar.HOUR_OF_DAY, 0); c.clear(Calendar.MINUTE); c.clear(Calendar.SECOND); c.clear(Calendar.MILLISECOND); return c.getTime(); } /** * 根据year,month,day构造日期对象 * * @param year 年份(4位长格式) * @param month 月份(1-12) * @param day 天(1-31) * @return 日期对象 */ public static Date makeDate(int year, int month, int day) { Calendar c = Calendar.getInstance(); getCleanDay(c); c.set(Calendar.YEAR, year); c.set(Calendar.MONTH, month - 1); c.set(Calendar.DAY_OF_MONTH, day); return c.getTime(); } private static Date getFirstCleanDay(int datePart, Date date) { Calendar c = Calendar.getInstance(); if (date != null) c.setTime(date); c.set(datePart, 1); return getCleanDay(c); } /** * Calendar.YEAR :1则代表的是对年份操作, * Calendar.MONTH :2是对月份操作; * Calendar.DATE : 5是对日期操作; * @param datePart * @param detal * @param date * @return */ public static Date add(int datePart, int detal, Date date) { Calendar c = Calendar.getInstance(); if (date != null) c.setTime(date); c.add(datePart, detal); return c.getTime(); } /** * 日期date所在星期的第一天00:00:00对应日期对象 * * @param date * @return 日期所在星期的第一天00:00:00对应日期对象 */ public static Date getFirstDayOfWeek(Date date) { return getFirstCleanDay(Calendar.DAY_OF_WEEK, date); } /** * 当前日期所在星期的第一天00:00:00对应日期对象 * * @return 当前日期所在星期的第一天00:00:00对应日期对象 */ public static Date getFirstDayOfWeek() { return getFirstDayOfWeek(null); } /** * 日期date所在月份的第一天00:00:00对应日期对象 * * @param date * @return 日期所在月份的第一天00:00:00对应日期对象 */ public static Date getFirstDayOfMonth(Date date) { return getFirstCleanDay(Calendar.DAY_OF_MONTH, date); } /** * 当前日期所在月份的第一天00:00:00对应日期对象 * * @return 当前日期所在月份的第一天00:00:00对应日期对象 */ public static Date getFirstDayOfMonth() { return getFirstDayOfMonth(null); } /** * 日期date所在月份的最后一天23, 59, 59对应日期对象 * * @param date * @return 日期date所在月份的最后一天23, 59, 59对应日期对象 */ public static Date getLastDayOfMonth(Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); int MaxDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), MaxDay, 23, 59, 59); return cal.getTime(); } /** * 日期date所在季度的第一天00:00:00对应日期对象 * * @param date * @return 日期date所在季度的第一天00:00:00对应日期对象 */ public static Date getFirstDayOfSeason(Date date) { Date d = getFirstDayOfMonth(date); int delta = DateUtil.getMonth(d) % 3; if (delta > 0) d = DateUtil.getDateAfterMonths(d, -delta); return d; } /** * 当前日期所在季度的第一天00:00:00对应日期对象 * * @return 当前日期所在季度的第一天00:00:00对应日期对象 */ public static Date getFirstDayOfSeason() { return getFirstDayOfMonth(null); } /** * 日期date所在年份的第一天00:00:00对应日期对象 * * @param date * @return 日期date所在年份的第一天00:00:00对应日期对象 */ public static Date getFirstDayOfYear(Date date) { return makeDate(getYear(date), 1, 1); } /** * 获取某年的第一天 * @param year * @return 日期date所在年份的第一天00:00:00对应日期对象 */ public static Date getFirstDaylOfYear(int year){ return makeDate(year, 1, 1); } /** * 当前日期年度的第一天00:00:00对应日期对象 * * @return 当前日期年度第一天00:00:00对应日期对象 */ public static Date getFirstDayOfYear() { return getFirstDayOfYear(new Date()); } /** * 当前日期年度的最后一天23:59:59对应日期对象 * * @return 当前日期年度的最后一天23:59:59对应日期对象 */ public static Date getLastDayOfYear() { return parseDate(getYear(new Date()) + "-12-31 23:59:59", "yyyy-MM-dd HH:mm:ss"); } /** * 获取某年最后一天 * @return 传入年份的最后一天23:59:59对应日期对象 */ public static Date getLastDayOfYear(int year){ return DateUtil2.parse(year + "-12-31 23:59:59", "yyyy-MM-dd HH:mm:ss", ""); } /** * 计算N周后的日期 * * @param start 开始日期 * @param weeks 可以为负,表示前N周 * @return 新的日期 */ public static Date getDateAfterWeeks(Date start, int weeks) { return getDateAfterMs(start, weeks * ONE_WEEK_MILLISECOND); } /** * 计算N月后的日期, 特殊情况:如果是'2016-1-31'一个月后是 '2017-2-28' * * @param start 开始日期 * @param months 可以为负,表示前N月 * @return 新的日期 */ public static Date getDateAfterMonths(Date start, int months) { return add(Calendar.MONTH, months, start); } /** * 计算N年后的日期, 特殊情况:如果是'2016-2-29'一年后是'2017-2-28' * * @param start 开始日期 * @param years 可以为负,表示前N年 * @return 新的日期 */ public static Date getDateAfterYears(Date start, int years) { return add(Calendar.YEAR, years, start); } /** * 计算N天后的日期 * * @param start 开始日期 * @param days 可以为负,表示前N天 * @return 新的日期 */ public static Date getDateAfterDays(Date start, int days) { return getDateAfterMs(start, days * ONE_DAY_MILLISECOND); } /** * 计算N毫秒后的日期 * * @param start 开始日期 * @param ms 可以为负,表示前N毫秒 * @return 新的日期 */ public static Date getDateAfterMs(Date start, long ms) { return new Date(start.getTime() + ms); } /** * 计算2个日期之间的间隔的周期数 * * @param start 开始日期 * @param end 结束日期 * @param msPeriod 单位周期的毫秒数 * @return 周期数 */ public static long getPeriodNum(Date start, Date end, long msPeriod) { return getIntervalMs(start, end) / msPeriod; } /** * 计算2个日期之间的毫秒数 * * @param start 开始日期 * @param end 结束日期 * @return 毫秒数 */ public static long getIntervalMs(Date start, Date end) { return end.getTime() - start.getTime(); } /** * 计算2个日期之间的天数 * * @param start 开始日期 * @param end 结束日期 * @return 天数 */ public static int getIntervalDays(Date start, Date end) { return (int) getPeriodNum(start, end, ONE_DAY_MILLISECOND); } /** * 计算2个日期之间的周数 * * @param start 开始日期 * @param end 结束日期 * @return 周数 */ public static int getIntervalWeeks(Date start, Date end) { return (int) getPeriodNum(start, end, ONE_WEEK_MILLISECOND); } /** * 比较日期前后关系 * * @param base 基准日期 * @param date 待比较的日期 * @return 如果date在base之前或相等返回true,否则返回false */ public static boolean before(Date base, Date date) { return date.before(base) || date.equals(base); } /** * 比较日期前后关系 * * @param base 基准日期 * @param date 待比较的日期 * @return 如果date在base之后或相等返回true,否则返回false */ public static boolean after(Date base, Date date) { return date.after(base) || date.equals(base); } /** * 返回对应毫秒数大的日期 * * @param date1 * @param date2 * @return 返回对应毫秒数大的日期 */ public static Date max(Date date1, Date date2) { if (date1.getTime() > date2.getTime()) return date1; else return date2; } /** * 返回对应毫秒数小的日期 * * @param date1 * @param date2 * @return 返回对应毫秒数小的日期 */ public static Date min(Date date1, Date date2) { if (date1.getTime() < date2.getTime()) return date1; else return date2; } /** * 判断date是否在指定的时期范围(start~end)内 * * @param start 时期开始日期 * @param end 时期结束日期 * @param date 待比较的日期 * @return 如果date在指定的时期范围内,返回true,否则返回false */ public static boolean inPeriod(Date start, Date end, Date date) { return (end.after(date) || end.equals(date)) && (start.before(date) || start.equals(date)); } /** * 获取当前日期是星期几<br> * * @param dt * @return 当前日期是星期几 */ public static String getWeekOfDate(Date dt) { String[] weekDays = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}; Calendar cal = Calendar.getInstance(); cal.setTime(dt); int w = cal.get(Calendar.DAY_OF_WEEK) - 1; if (w < 0) w = 0; return weekDays[w]; } private static final DateTimeFormatter SHORT_DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final DateTimeFormatter LONG_DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final TemporalField CHINA_DAY_OF_WEEK = WeekFields.of(Locale.CHINA).dayOfWeek(); /** * 获取指定日期所在周的第一天 * * @param date * @return */ public static LocalDate firstDayOfWeek(TemporalAccessor date) { return LocalDate.from(date).with(CHINA_DAY_OF_WEEK, 1); } /** * 获取指定日期所在周的最后一天 * * @param date * @return */ public static LocalDate lastDayOfWeek(TemporalAccessor date) { return LocalDate.from(date).with(CHINA_DAY_OF_WEEK, 7); } /** * 获取指定日期所在月的第一天 * * @param date * @return */ public static LocalDate firstDayOfMonth(TemporalAccessor date) { return LocalDate.from(date).withDayOfMonth(1); } /** * 获取指定日期所在周的最后一天 * * @param date * @return */ public static LocalDate lastDayOfMonth(TemporalAccessor date) { return LocalDate.from(date).plusMonths(1).withDayOfMonth(1).plusDays(-1); } /** * 短日期格式 * * @param date * @return */ public static String shortString(TemporalAccessor date) { return SHORT_DATETIME_FORMATTER.format(date); } /** * 长日期格式 * * @param date * @return */ public static String longString(TemporalAccessor date) { return LONG_DATETIME_FORMATTER.format(date); } /** * 将指定定的日期转换成LocalDateTime * * @param date * @return */ public static LocalDateTime asLocalDateTime(Date date) { return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); } /** * 将指定定的日期转换成LocalDate * * @param date * @return */ public static LocalDate asLocalDate(Date date) { return asLocalDateTime(date).toLocalDate(); } /** * LocalDate 转为 Date * * @param localDate * @return */ public static Date asDate(LocalDate localDate) { return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant()); } /** * LocalDateTime 转为 Date * * @param localDateTime * @return */ public static Date asDate(LocalDateTime localDateTime) { return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); } /** * 按照“年-月”生成当月的工作日 * @param yearMonth [yyyy-MM] * @return */ public static List<Date> getWorkdayOfMonth(String yearMonth){ int year; int month ; List<Date> results = new ArrayList<>(); Calendar cal = Calendar.getInstance(); String[] splitStr = yearMonth.split("-"); year = Integer.parseInt(splitStr[0]); month = Integer.parseInt(splitStr[1]); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, month - 1); cal.set(Calendar.DATE, 1); while(cal.get(Calendar.YEAR) == year && cal.get(Calendar.MONTH) < month){ int day = cal.get(Calendar.DAY_OF_WEEK); if(!(day == Calendar.SUNDAY || day == Calendar.SATURDAY)){ results.add((Date) cal.getTime().clone()); } cal.add(Calendar.DATE, 1); } return results; } /** * 按照年份生成一年内的工作日 * @param year * @return */ public static List<Date> getWorkdayOfYear(int year){ List<Date> results = new ArrayList<>(); Calendar cal = Calendar.getInstance(); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, 0); cal.set(Calendar.DATE, 1); while(cal.get(Calendar.YEAR) == year){ int day = cal.get(Calendar.DAY_OF_WEEK); if(!(day == Calendar.SUNDAY || day == Calendar.SATURDAY)){ results.add((Date) cal.getTime().clone()); } cal.add(Calendar.DATE, 1); } return results; } /** * 获取上一个月的第一天时间 */ public static Date getFirstDayOfLastMonth(){ Calendar cal = Calendar.getInstance();//获取当前日期 /* * 2018-03-31时通过测试: * 不设置cal.set(Calendar.DAY_OF_MONTH, 1)时,得到的日期是2018-02-28而不是3月 */ cal.add(Calendar.MONTH, -1); cal.set(Calendar.DAY_OF_MONTH, 1);//设置为1号,当前日期既为本月第一天 cal.set(Calendar.HOUR_OF_DAY, 0); cal.clear(Calendar.MINUTE); cal.clear(Calendar.SECOND); cal.clear(Calendar.MILLISECOND); return cal.getTime(); } /** * 获取指定日期的上月一号 * @param date * @return */ public static Date getLastMonthFirst(Date date) { Calendar c = Calendar.getInstance();//获取当前日期 c.setTime(date); c.add(Calendar.MONTH, -1); c.set(Calendar.DAY_OF_MONTH, 1);// 设置为1号 return getCleanDay(c); } /** * 获取上月月末 * @param date * @return */ public static Date getLastMonthEnd(Date date) { Calendar c = Calendar.getInstance();//获取当前日期 c.setTime(date); c.add(Calendar.MONTH, -1); int maxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH); c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), maxDay, 23, 59, 59); return getCleanDay(c); } public static Date getLastMonthDay(Date date, int day) { Calendar c = Calendar.getInstance();//获取当前日期 c.setTime(date); c.add(Calendar.MONTH, -1); c.set(Calendar.DAY_OF_MONTH, day);// 设置为1号 return getCleanDay(c); } /** * 本月月末 * @param date * @return */ public static Date getMonthEnd(Date date) { Calendar c = Calendar.getInstance();//获取当前日期 c.setTime(date); int maxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH); c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), maxDay, 23, 59, 59); return getCleanDay(c); } /** * 获取下月1号 * @param date * @return */ public static Date getNextMonthFirst(Date date) { Calendar c = Calendar.getInstance();//获取当前日期 c.setTime(date); c.add(Calendar.MONTH, 1); c.set(Calendar.DAY_OF_MONTH, 1);// 设置为1号 return getCleanDay(c); } /** * 获取下月 月末 * @param date * @return */ public static Date getNextMonthEnd(Date date) { Calendar c = Calendar.getInstance();//获取当前日期 c.setTime(date); c.add(Calendar.MONTH, 1); int maxDay = c.getActualMaximum(Calendar.DAY_OF_MONTH); c.set(c.get(Calendar.YEAR), c.get(Calendar.MONTH), maxDay, 23, 59, 59); return getCleanDay(c); } /** * 判断两个日期之间是否为一整年 * @param start * @param end * @return */ public static boolean isOneYear(Date start, Date end) { Calendar startday = Calendar.getInstance(); Calendar endday = Calendar.getInstance(); startday.setTime(start); endday.setTime(end); if (startday.after(endday)) { return false; } long sl = startday.getTimeInMillis(); long el = endday.getTimeInMillis(); long days = ((el - sl) / (1000 * 60 * 60 * 24)); if (days == 365 || days == 366) { if (startday.get(Calendar.MONTH) <= 1) { startday.set(Calendar.MONTH, 1); int lastDay = startday.getActualMaximum(Calendar.DAY_OF_MONTH); return (lastDay == 28 && days == 365) || (lastDay == 29 && days == 366); } else { endday.set(Calendar.MONTH, 1); int lastDay = endday.getActualMaximum(Calendar.DAY_OF_MONTH); return (lastDay == 28 && days == 365) || (lastDay == 29 && days == 366); } } else { return false; } } /** * @return 上一天Date */ public static Date getPreviousDate(Date date) { Calendar c = Calendar.getInstance(); c.setTime(date); int day = c.get(Calendar.DATE); c.set(Calendar.DATE, day - 1); return c.getTime(); } /** * @return 后一天Date */ public static Date getNextDate(Date date) { Calendar c = Calendar.getInstance(); c.setTime(date); int day = c.get(Calendar.DATE); c.set(Calendar.DATE, day + 1); return c.getTime(); } /** * 判断两个时间是否在同一天 * @param date1 * @param Date2 * @return */ public static boolean inSameDay(Date date1, Date Date2) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date1); int year1 = calendar.get(Calendar.YEAR); int day1 = calendar.get(Calendar.DAY_OF_YEAR); calendar.setTime(Date2); int year2 = calendar.get(Calendar.YEAR); int day2 = calendar.get(Calendar.DAY_OF_YEAR); if ((year1 == year2) && (day1 == day2)) { return true; } return false; } /** * 传入多少分钟 * 获取两个时间的差值,有多少个小时,用于计算请了多少小时假 * 尾数不足0.5小时按0.5小时计,超过0.5小时按1小时计 * * ((int) diff / 30) 有n个半小时要转int * 最后 如果余数不为0 就要补上0.5 * return 单位:小时 */ public static double getLeaveValue(double differenceValue){ double needLeaveTs = ((int)(differenceValue / 30))*0.5; if(differenceValue % 30 != 0) needLeaveTs+= 0.5; return needLeaveTs; } /** * 传入一个日期 * 打卡开始时间在下午,开始时间与14:00上班时间请多少个小时 */ public static double getLeaveValue(Date startTime){ double hour = startTime.getHours(); double minute = startTime.getMinutes(); double needLeaveTs = ((int)(minute / 30))*0.5; if(minute % 30 != 0) needLeaveTs += 0.5; needLeaveTs += hour - 14; return needLeaveTs; } /** * 获取指定日期下午上班时间 * @param date * @return */ public static Date getGotoWorkAfternoon(Date date){ return parseDate(getYear(date) + "-"+ (getMonth(date)+1) +"-" + getDay(date)+" 14:00:00", "yyyy-MM-dd HH:mm:ss"); } /** * 根据传进来的Time和Date,获取到那天对应完成日期 * @param date * @param time * @return */ public static Date getDateByTime(Date date, Time time){ int hour = time.getHours(); int minutes = time.getMinutes(); int seconds = time.getSeconds(); String timeStr = " " + hour + ":" + minutes + ":" + seconds; return parseDate(getYear(date) + "-"+ (getMonth(date)+1) +"-" + getDay(date) + timeStr, "yyyy-MM-dd HH:mm:ss"); } /** * 获取XX:XX:XX的时间字符串 * @param t 秒 * @return [XX:XX:XX],[XX:XX] */ public static String getTimeSpanStr(int t) { StringBuilder sb = new StringBuilder(); if(t >= 3600) { int h = t / 3600; if(h < 10) sb.append("0"); sb.append(h + ":"); } if(t >= 60) { int m = t%3600/60; if(m < 10) sb.append("0"); sb.append(m + ":"); } int s = t%60; if(s < 10) sb.append("0"); sb.append(s); return sb.toString(); } /** * 根据传入日期获取目标日期 * @param date nullable 原始日期 * @param monthDiff nullable 月份偏移量 * @param dayOfMonth nullable 当月几号 * @return null if date is null */ public static Date getDiffDate(Date date, Integer monthDiff, Integer dayOfMonth) { if(date == null) return null; Calendar c = Calendar.getInstance(); c.setTime(date); if(monthDiff == null) monthDiff = 0; int month = c.get(Calendar.MONTH) + monthDiff; c.set(Calendar.MONTH, month); if(dayOfMonth != null) c.set(Calendar.DAY_OF_MONTH, dayOfMonth); return c.getTime(); } /** * 获取n天[前/后]的日期 * @return */ public static Date getDiffDay(Date date, Integer diffDay) { Calendar c = Calendar.getInstance(); c.setTime(date); c.add(Calendar.DAY_OF_YEAR, diffDay); return c.getTime(); } /** * @param d1 notNull * @param d2 notNull * @return 是否是同一个月 */ public static boolean isSameMonth(Date d1, Date d2) { Calendar c = Calendar.getInstance(); c.setTime(d1); int y1 = c.get(Calendar.YEAR); int m1 = c.get(Calendar.MONTH); c.setTime(d2); int y2 = c.get(Calendar.YEAR); int m2 = c.get(Calendar.MONTH); return y1 == y2 && m1 == m2; } /** * @param d1 notNull * @param d2 notNull * @return 是否是同一天 */ public static boolean isSameDay(Date d1, Date d2) { Calendar c = Calendar.getInstance(); c.setTime(d1); int y1 = c.get(Calendar.YEAR); int m1 = c.get(Calendar.MONTH); int day1 = c.get(Calendar.DAY_OF_MONTH); c.setTime(d2); int y2 = c.get(Calendar.YEAR); int m2 = c.get(Calendar.MONTH); int day2 = c.get(Calendar.DAY_OF_MONTH); return y1 == y2 && m1 == m2 && day1 == day2; } /** * @return d1>=d2 */ public static boolean greaterThanOrEqualTo(Date d1, Date d2) { return d1.compareTo(d2) >= 0; } /** * @return d1<=d2 */ public static boolean lessThanOrEqualTo(Date d1, Date d2) { return d1.compareTo(d2) <= 0; } /** * @return d1>d2 */ public static boolean greaterThan(Date d1, Date d2) { return d1.compareTo(d2) > 0; } /** * 获取某个月的实际最大天数, 如2016-02, 最大天数为29 */ public static int getMaximum(Date date) { Calendar c = Calendar.getInstance(); c.setTime(date); return c.getActualMaximum(Calendar.DAY_OF_MONTH); } public static Date getFirstDayOfWeek(int year, int week) { Calendar c = Calendar.getInstance(); c.set(year, Calendar.JANUARY, 1, 0, 0, 0);//定到第一天 c.add(Calendar.DATE, (week - 1) * 7);//直接add天数 c.setFirstDayOfWeek(Calendar.SUNDAY); c.setTime(c.getTime());//必须先set一次time,否则是错误的! c.set(Calendar.DAY_OF_WEEK, 1); return c.getTime(); } public static Date getHalfPastNineDateTime(Date date){ return parseDate(getYear(date) + "-"+ (getMonth(date)+1) +"-" + getDay(date)+" 9:30:00", "yyyy-MM-dd HH:mm:ss"); } public static Date getTenOClockDateTime(Date date){ return parseDate(getYear(date) + "-"+ (getMonth(date)+1) +"-" + getDay(date)+" 9:30:00", "yyyy-MM-dd HH:mm:ss")
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数据进行处理。
加载全部内容