赞
踩
类名 | 具体描述 |
---|---|
Date | Date对象算是JAVA中历史比较悠久的用于处理日期、时间相关的类了,但是随着版本的迭代演进,其中的众多方法都已经被弃用,所以 Date更多的时候仅被用来做一个数据类型使用 ,用于记录对应的日期与时间信息 |
Calender | 为了弥补Date对象在日期时间处理方法上的一些缺陷,JAVA提供了Calender抽象类来 辅助实现Date相关的一些日历日期时间的处理与计算 。 |
TimeZone | Timezone类提供了一些有用的方法用于 获取时区的相关信息 |
@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 复制代码
总体来说,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的静态属性,不需要刻意记
常用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的特定字段的值。
具体的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()); } 复制代码
上面程序①②两处的代码完全相似,但它们运行的结果不一样:
关键在于程序中粗体字代码行,Calendar提供了一个setLenient()用于设置它的容错性, Calendar默认支持较好的容错性 ,通过 setLenient(false)可以关闭Calendar的容错性,让它进行严格的参数检查。
Calendar有两种解释日历字段的模式:lenient模式和non-lIenient模式:
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。
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(); // 也可以通过 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日 复制代码
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();//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 是期间持续时间的意思
示例代码
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"); // 获取当前日期,此处为了保证后续结果固定,注掉自动获取当前日期,指定固定日期 // 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)); } 复制代码
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)); } 复制代码
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(); // 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()); } 复制代码
一款超厉害的国产 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> 复制代码
制作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项目开发中常见的日期操作工具类封装——DateUtil
项目中,时间格式转换是一个非常典型的日期处理操作,可能会涉及到将一个字符串日期转换为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); // 字符串转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 复制代码
对于计算机而言,时间处理的时候按照基于时间原点的数字进行处理即可,但是转为人类方便识别的场景显示时,经常会需要转换为不同的日期时间显示格式,比如:
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数据进行处理。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。