类名 | 具体描述 |
---|---|
Date | Date对象算是JAVA中历史比较悠久的用于处理日期、时间相关的类了,但是随着版本的迭代演进,其中的众多方法都已经被弃用,所以Date更多的时候仅被用来做一个数据类型使用,用于记录对应的日期与时间信息 |
Calender | 为了弥补Date对象在日期时间处理方法上的一些缺陷,JAVA提供了Calender抽象类来辅助实现Date相关的一些日历日期时间的处理与计算。 |
TimeZone | Timezone类提供了一些有用的方法用于获取时区的相关信息 |
????@Test
????void?test06(){
????????Date?date1?=?new?Date();
????????
????????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
总体来说,Date是一个设计相当糟糕的类,因此Java官方推荐尽量少用Date的构造器和方法。
如果需要对日期、时间进行加减运算,或获取指定时间的年、月、日、时、分、秒信息,可使用Calendar工具类。
示例
????@Test
????void?test05(){
????????Calendar?calendar?=??Calendar.getInstance();
????????
????????int?year?=?calendar.get(Calendar.YEAR);
????????
????????int?month?=?calendar.get(Calendar.MONTH);
????????
????????int?dom?=?calendar.get(Calendar.DAY_OF_MONTH);
????????
????????int?doy?=?calendar.get(Calendar.DAY_OF_YEAR);
????????
????????int?dow?=?calendar.get(Calendar.DAY_OF_WEEK);
????????
????????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的静态属性,不需要刻意记
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(int field, int amount)的功能非常强大,add主要用于改变Calendar的特定字段的值。
如果需要增加某字段的值,则让 amount为正数;
如果需要减少某字段的值,则让 amount为负数即可。
具体的field操作可以看:Calendar的add()方法介绍[1]
add(int field, int amount)有如下两条规则:
当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大。
如果下一级字段也需要改变,那么该字段会修正到变化最小的值。
????@Test
????void?test07(){
????????Calendar?cal1?=?Calendar.getInstance();
????????
????????cal1.set(2003,?7,?23,?0,?0,?0);
????????
????????cal1.add(Calendar.MONTH,?6);
????????System.out.println(cal1.getTime());
????????Calendar?cal2?=?Calendar.getInstance();
????????
????????cal2.set(2003,?7,?31,?0,?0,?0);
????????
????????
????????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()的规则与add()的处理规则不同—— 当被修改的字段超出它允许的范围时,上一级字段不会增大。
????@Test
????void?test08(){
????????Calendar?cal1?=?Calendar.getInstance();
????????
????????cal1.set(2003,?7,?23,?0,?0,?0);
????????
????????cal1.roll(Calendar.MONTH,?6);
????????System.out.println(cal1.getTime());
????????Calendar?cal2?=?Calendar.getInstance();
????????cal2.set(2003,?7,?31,?0,?0,?0);
????????
????????
????????
????????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对象的set()方法来改变指定时间字段的值时,有可能传入一个不合法的参数,例如为MONTH字段设置13,这将会导致怎样的后果呢?看如下程序:
????@Test
????void?test09(){
????????Calendar?cal?=?Calendar.getInstance();
????????System.out.println(cal.getTime());
????????
????????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(f, value)方法将日历字段f更改为value,此外它还设置了一个内部成员变量,以指示日历字段f已经被更改。
尽管日历字段f是立即更改的,但该Calendar所代表的时间却不会立即修改,直到下次调用get()、getTime()、getTimeInMillis()、add()或roll()时才会重新计算日历的时间。
这被称为 set()方法的延迟修改,采用延迟修改的优势是多次调用set()不会触发多次不必要的计算(需要计算出一个代表实际时间的long型整数)。
????@Test
????void?test10(){
????????Calendar?cal?=?Calendar.getInstance();
????????
????????cal.set(2003,?7,?31);
????????cal.set(Calendar.MONTH,?8);
????????
????????
????????
????????cal.set(Calendar.DATE,?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。
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.now();
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日
LocalDate pluslocalDate = localDate.plusDays(1)
LocalDate pluslocalDate = localDate.plusYears(1)
LocalDate和LocalTime 都有类似作用的api
LocalDate.plusDays(1) 增加一天
LocalTime.plusHours(1) 增加一小时 等等~
LocalTime.isBefore(LocalTime);
LocalTime.isAfter();
public?final?class?LocalDateTime?...{
????
????private?final?LocalDate?date;
????private?final?LocalTime?time;
}
LocalDateTime = LocalDate + LocalTime
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有个between方法专门比较两个日期的
LocalDate startDate = LocalDateTime.ofInstant(
Instant.ofEpochMilli(1601175465000L), ZoneId.systemDefault())
.toLocalDate();
Period p = Period.between(startDate, LocalDate.now());
System.out.println("目标日期距离今天的时间差:"+p.getYears()+" 年 "+p.getMonths()+" 个月 "+p.getDays()+" 天" );
查看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();??
????int?days?=?end.day?-?this.day;
????if?(totalMonths?>?0?&&?days?<?0)?{
????????totalMonths--;
????????LocalDate?calcDate?=?this.plusMonths(totalMonths);
????????days?=?(int)?(end.toEpochDay()?-?calcDate.toEpochDay());??
????}?else?if?(totalMonths?<?0?&&?days?>?0)?{
????????totalMonths++;
????????days?-=?end.lengthOfMonth();
????}
????long?years?=?totalMonths?/?12;??
????int?months?=?(int)?(totalMonths?%?12);??
????return?Period.of(Math.toIntExact(years),?months,?days);
}
他只接受两个LocalDate对象,对时间的计算,算好之后返回Period对象
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对象,源码是对两个时间的计算,并返回对象。
JAVA8开始新增的java.time
包中有提供Duration
和Period
两个类,用于处理日期时间间隔相关的场景,两个类的区别点如下:
类 | 描述 |
---|---|
Duration | 时间间隔,用于秒级的时间间隔计算 |
Period | 日期间隔,用于天级别的时间间隔计算,比如年月日维度的 |
Duration
与Period
具体使用的时候还需要有一定的甄别,因为部分的方法很容易使用中被混淆,下面分别说明下。
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");
????????
????????
????????LocalTime?today?=?LocalTime.parse("12:12:25.600");
????????
????????
????????System.out.println(today);
????????
????????System.out.println(target);
????????Duration?duration?=?Duration.between(target,?today);
????????
????????
????????System.out.println(duration);
????????
????????System.out.println(duration.getSeconds());
????????
????????System.out.println(duration.getNano());
????????
????????System.out.println(duration.toMinutes());
????????
????????System.out.println(duration.plusHours(30L));
????????
????????System.out.println(duration.withSeconds(15L));
????????
????}
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.parse("2022-07-08");
????????
????????System.out.println(today);
????????
????????System.out.println(target);
????????Period?period?=?Period.between(target,?today);
????????
????????System.out.println(period);
????????
????????System.out.println(period.getYears());
????????
????????System.out.println(period.getMonths());
????????
????????System.out.println(period.getDays());
????????
????????System.out.println(period.plusMonths(3L));
????????
????????System.out.println(period.withDays(15));
????????
????????System.out.println(Period.of(2,?3,?44));
????}
Duration与Period都是用于日期之间的计算操作。
Duration主要用于秒、纳秒等维度的数据处理与计算。
Period主要用于计算年、月、日等维度的数据处理与计算。
先看个例子,计算两个日期相差的天数,使用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的实现:
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不是一个好的思路。
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天
如果是使用的
Date
对象,则可以通过将Date日期转换为毫秒时间戳
的方式相减然后将毫秒数转为天数的方式来得到结果。需要注意的是通过毫秒数计算日期天数的差值时,需要屏蔽掉时分秒带来的误差影响。
分别算出年、月、日差值,然后根据是否闰年、每月是30还是31天等计数逻辑,纯数学硬怼方式计算。
不推荐、代码略...
在一些性能优化的场景中,我们需要获取到方法处理的执行耗时,很多人都是这么写的:
public?void?doSomething()?{
????
????long?startMillis?=?System.currentTimeMillis();
????
????
????
????long?endMillis?=?System.currentTimeMillis();
????
????
????System.out.println(endMillis?-?startMillis);
}
当然啦,如果你使用的是JDK8+
的版本,你还可以这么写:
public?void?doSomething()?{
????
????Instant?start?=?Instant.now();
????
????
????Instant?end?=?Instant.now();
????
????System.out.println(Duration.between(start,?end).toMillis());
}
一款超厉害的国产Java工具——Hutool。Hutool是一个Java工具包类库,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类。适用于很多项目以及Web开发,并且与其他框架没有耦合性。
工具使用说明——Hutool 指南 API[2]
引入依赖
????????
????????<dependency>
????????????<groupId>com.xiaoleilu</groupId>
????????????<artifactId>hutool-all</artifactId>
????????????<version>3.3.2</version>
????????</dependency>
基于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);
????
????calendar.set(Calendar.MINUTE,?0);
????
????calendar.set(Calendar.SECOND,?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);
???}
??}
?????
?????calendarTemp.set(Calendar.HOUR_OF_DAY,?0);
?????
?????calendarTemp.set(Calendar.MINUTE,?0);
?????
?????calendarTemp.set(Calendar.SECOND,?0);
?????
?????calendarTemp.set(Calendar.MILLISECOND,?0);
?
?????
?????Date?startTime?=?calendarTemp.getTime();
????return?startTime;
?}
Java项目开发中常见的日期操作工具类封装——DateUtil[3]
项目中,时间格式转换是一个非常典型的日期处理操作,可能会涉及到将一个字符串日期转换为JAVA对象,或者是将一个JAVA日期对象转换为指定格式的字符串日期时间。
在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。
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);
????
????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
对于计算机而言,时间处理的时候按照基于时间原点的数字进行处理即可,但是转为人类方便识别的场景显示时,经常会需要转换为不同的日期时间显示格式,比如:
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 |
在后端与数据库交互的时候,可能会遇到一个问题,就是往DB中存储了一个时间字段之后,后面再查询的时候,就会发现时间数值差了8个小时
,这个需要在DB的连接信息中指定下时区信息:
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=Asia/Shanghai
在有一些前后端交互的项目中,可能会遇到一个问题,就是前端选择并保存了一个时间信息,再查询的时候就会发现与设置的时间差了8个小时
,这个其实就是后端时区转换设置的问题。
SpringBoot的配置文件中,需要指定时间字符串转换的时区信息:
spring.jackson.time-zone=GMT+8
这样从接口json中传递过来的时间信息,jackson框架可以根据对应时区转换为正确的Date数据进行处理。