Java日期时间类及计算

发布时间:2024年01月03日
作者:阿龙不写bug

1.1 java.util包
类名具体描述
DateDate对象算是JAVA中历史比较悠久的用于处理日期、时间相关的类了,但是随着版本的迭代演进,其中的众多方法都已经被弃用,所以Date更多的时候仅被用来做一个数据类型使用,用于记录对应的日期与时间信息
Calender为了弥补Date对象在日期时间处理方法上的一些缺陷,JAVA提供了Calender抽象类来辅助实现Date相关的一些日历日期时间的处理与计算
TimeZoneTimezone类提供了一些有用的方法用于获取时区的相关信息

① Date类

????@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

② Calendar 日历类

总体来说,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的静态属性,不需要刻意记

常用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()方法介绍[1]

add(int field, int amount)有如下两条规则:

  1. 当被修改的字段超出它允许的范围时,会发生进位,即上一级字段也会增大。

  2. 如果下一级字段也需要改变,那么该字段会修正到变化最小的值。

????@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

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的容错性

调用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()方法延迟修改 :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。

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();

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();
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 类

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.1 Period与Duration类

JAVA8开始新增的java.time包中有提供DurationPeriod两个类,用于处理日期时间间隔相关的场景,两个类的区别点如下:

描述
Duration时间间隔,用于秒级的时间间隔计算
Period日期间隔,用于天级别的时间间隔计算,比如年月日维度的

DurationPeriod具体使用的时候还需要有一定的甄别,因为部分的方法很容易使用中被混淆,下面分别说明下。

2.1.1 Duration

Duration的最小计数单位为纳秒,其内部使用secondsnanos两个字段来进行组合计数表示duration总长度。

Duration的常用API方法梳理如下:

方法描述
between计算两个时间的间隔,默认是
ofXxxof开头的一系列方法,表示基于给定的值创建一个Duration实例。比如ofHours(2L),则表示创建一个Duration对象,其值为间隔2小时
plusXxxplus开头的一系列方法,用于在现有的Duration值基础上增加对应的时间长度,比如plusDays()表示追加多少天,或者plusMinutes()表示追加多少分钟
minusXxxminus开头的一系列方法,用于在现有的Duration值基础上扣减对应的时间长度,与plusXxx相反
toXxxxto开头的一系列方法,用于将当前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));
????????
????}

2.1.2 Period

Period相关接口与Duration类似,其计数的最小单位是,看下Period内部时间段记录采用了年、月、日三个field来记录:

常用的API方法列举如下:

方法描述
between计算两个日期之间的时间间隔。注意,这里只能计算出相差几年几个月几天
ofXxxof()或者以of开头的一系列static方法,用于基于传入的参数构造出一个新的Period对象
withXxxwith开头的方法,比如withYearswithMonthswithDays等方法,用于对现有的Period对象中对应的年、月、日等字段值进行修改(只修改对应的字段,比如withYears方法,只修改year,保留month和day不变),并生成一个新的Period对象
getXxx读取Period中对应的yearmonthday字段的值。注意下,这里是仅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));

????}

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();
????
????
????
????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());
}

2.4 计算时间差

1?? 使用Hutool工具进行计算

一款超厉害的国产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>

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);
????
????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;
?}

制作Date工具类计算

Java项目开发中常见的日期操作工具类封装——DateUtil[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);
????
????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中,为了方便各种格式转换,提供了基于时间模板进行转换的实现能力:

时间格式模板中的字幕含义说明如下:

字母使用说明
yyyy4位数的年份
yy显示2位数的年份,比如2022年,则显示为22年
MM显示2位数的月份,不满2位数的,前面补0,比如7月份显示07月
M月份,不满2位的月份不会补0
dd天, 如果1位数的天数,则补0
d天,不满2位数字的,不补0
HH24小时制的时间显示,小时数,两位数,不满2位数字的前面补0
H24小时制的时间显示,小时数,不满2位数字的不补0
hh12小时制的时间显示,小时数,两位数,不满2位数字的前面补0
ss秒数,不满2位的前面补0
s秒数,不满2位的不补0
SSS毫秒数
z时区名称,比如北京时间东八区,则显示CST
Z时区偏移信息,比如北京时间东八区,则显示+0800

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数据进行处理。

文章来源:https://blog.csdn.net/weixin_54542328/article/details/135336117
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。