如:
// 当前时间 Date date = new Date(); LocalDateTime now = LocalDateTime.now(); // 当前日期 LocalDate today = LocalDate.now();
日期比较,不要转成String进行比较
反例:
void foo(Date myDate) { String format1 = DateFormatUtils.format(myDate, "yyyy-MM-dd"); String format2 = DateFormatUtils.format(new Date(), "yyyy-MM-dd"); if (format1.equals(format2)){ ... } }
正例:
使用 org.apache.commons.lang3.time.DateUtils#isSameDay(java.util.Date, java.util.Date),该util方法通过Calendar来比较两个Date。
void foo(Date myDate) { if (DateUtils.isSameDay(myDate, new Date())){ ... } }
Date
类也提供了 before()
和 after()
方法来比较两个Date的先后关系。
//source code in class java.util.Date /* Tests if this date is before the specified date. */ public boolean before(Date when) { return getMillisOf(this) < getMillisOf(when); } /* Tests if this date is after the specified date. */ public boolean after(Date when) { return getMillisOf(this) > getMillisOf(when); }
void foo(Date myDate) { Date today = DateUtils.truncate(new Date(), Calendar.DATE); // 获取当前日期 if (myDate.before(today)){ ... } }
强大的org.apache.commons.lang3.time.DateUtils
org.apache.commons.lang3.time.DateUtils 是 Apache Commons Lang 库中的一个类,提供了一系列便捷的Date操作,可以避免手动编写复杂的日期计算代码。使用这些方法可以简化日期操作,提高代码的可读性和维护性。以下是一些 DateUtils
类中常用的方法:
1. 日期计算
addYears(Date date, int amount)
:在给定的日期上增加指定的年数。addMonths(Date date, int amount)
:在给定的日期上增加指定的月数。addWeeks(Date date, int amount)
:在给定的日期上增加指定的周数。addDays(Date date, int amount)
:在给定的日期上增加指定的天数。addHours(Date date, int amount)
:在给定的日期上增加指定的小时数。addMinutes(Date date, int amount)
:在给定的日期上增加指定的分钟数。addSeconds(Date date, int amount)
:在给定的日期上增加指定的秒数。
2. 日期比较
isSameDay(Date date1, Date date2)
:检查两个日期是否是同一天。isSameInstant(Date date1, Date date2)
:检查两个日期是否是同一瞬时。isSameLocalTime(Date date1, Date date2)
:检查两个日期是否是同一本地时间。
3. 日期截断
truncate(Date date, int field)
:截断给定日期到指定粒度,如年、月、日等。
4. 解析和格式化
parseDate(String str, String... parsePatterns)
:解析字符串为日期对象。format(Date date, String pattern)
:格式化日期对象为指定格式的字符串。
5. 其他
round(Date date, int field)
:将日期舍入到指定粒度。
除了org.apache.commons.lang3.time.DateUtils以外,其他组件例如hutool日期工具,也都在Date操作方面提供了优秀的API。因此,就别往String上靠了,转来转去的挺麻烦,程序也不易读,甚至可能出bug。
Mybatisplus中,对Date属性做eq/between/gt等操作,参数值别傻傻用String了
如下代码中,YqfCertConsumeNotice.createTime是Date类型。
String startDate = DateUtil.format(DateUtil.offsetDay(date, offset), "yyyy-MM-dd HH:mm:ss"); String endDate = DateUtil.format(DateUtil.offsetMinute(date, -3), "yyyy-MM-dd HH:mm:ss"); return list(new LambdaQueryWrapper<YqfCertConsumeNotice>() ... .between(YqfCertConsumeNotice::getCreateTime, startDate, endDate));
如上代码,请改成
如何获取某一天的期初时间? ---- 还在通过字符串拼接"00:00:00"来获取某一天的期初时间?
'2024-08-19' 与 '2024-08-19 00:00:00' 是同一时间。
反例:
一个Query类中有个field是 String createTimeBegin。页面或调用者为 createTimeBegin 赋的值是一个日期,如"2024-08-19"。
然后,处理程序里,对其进行了“画蛇添足”式的处理。
query.setCreateTimeBegin(query.getCreateTimeBegin() + "00:00:00");
又或者,在使用Mybatisplus的Wrapper对象时,经常看到这种“画蛇添足”式的代码
queryWrapper.ge(RefundRecord::getOrderEndTime, query.getCreateTimeBegin() + "00:00:00")
正例:
'yyyy-MM-dd' 与 'yyyy-MM-dd 00:00:00' 是同一时间。别再“画蛇添足”了。
还在通过字符串拼接"23:59:59"来指定时间区间最大值?
来看看下面几个代码片段,注意其中所拼接的 23:59:59
-- 代码片段1 LambdaQueryWrapper<EnterpriseProfit> queryWrapper = new QueryWrapper<EnterpriseProfit>().lambda() ... .between(StringUtils.isNoneBlank(dto.getProfitCreateTimeBegin(),dto.getProfitCreateTimeEnd()), EnterpriseProfit::getProfitCreateTime, dto.getProfitCreateTimeBegin()+ " 00:00:00", dto.getProfitCreateTimeEnd()+ " 23:59:59"); -- 代码片段2 if(StringUtils.isNotBlank(query.getWithdrawStart()) && StringUtils.isNotBlank(query.getWithdrawEnd())) { request.setWithdrawalTimeBegin(LocalDateTime.parse(query.getWithdrawStart().trim() + " 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); request.setWithdrawalTimeEnd(LocalDateTime.parse(query.getWithdrawEnd().trim() + " 23:59:59", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } -- 代码片段3 return new QueryWrapper<MerRefundRecord>().lambda() ... .between(StringUtils.isNoneBlank(vo.getOrderEndTimeBegin(), vo.getOrderEndTimeEnd()), MerRefundRecord::getOrderEndTime, vo.getOrderEndTimeBegin() + "00:00:00", vo.getOrderEndTimeEnd()+ "23:59:59");
请分析,代码片段3中是"23:59:59"而非" 23:59:59", 这是否bug?
查询特定日期范围内的数据,应使用半开半闭区间。即,要查询 2024-8-1到2024-8-7的数据,时间区间应该是[2024-8-1,2024-8-8) ,注意这里的最大值是 结束日期+1d。
敲黑板!-->如果用字符串拼接"23:59:59"的方式表示为闭区间[2024-8-1,2024-8-7 23:59:59],其实是有潜在问题的————其一,当时间数据的精度到millisecond(毫秒)时,会漏查数据;其二,就是要求开发者格外留意"23:59:59"串前面的空格字符,这显然增加了开发者的负担。
对于SpringMVC接口,时间数据,可以定义为String。不过,需要说明的是↓
仅仅是接口将这个参数定义为String。控制器方法内的程序里,要首先对该数据进行校验,并转换为Date,后面的程序都用这个Date来进行数据操作。绝不是从头到尾都用这个String的日期。
代码示例1:
@RestController public class DateDemoController { @GetMapping("queryByDate") Result<List<Order>> queryByDate(String createDate) { if (StringUtils.isBlank(createDate)) { return Result.error("日期不能为空"); } // ** 将入参createDate变量转换为Date类型的myDate,后面程序都用这个myDate。不再关注createDate。 Date myDate = null; try { myDate = DateUtils.parseDate(createDate, "yyyy-MM-dd"); } catch (ParseException e) { return Result.error("日期格式错误,应为 yyyy-MM-dd"); } log.debug("经过转换后的日期参数={}", myDate); List<Order> orderList = getOrderList(myDate); return Result.success(orderList); } private List<Order> getOrderList(Date myDate) { LambdaQueryWrapper<Order> queryWrapper = Wrappers.lambdaQuery(new Order()) .ge(Order::getCreateTime, myDate) .lt(Order::getCreateTime, DateUtils.addDays(myDate, 1)); return myMapper.selectList(queryWrapper); } }
反例:
下面代码将入参贯穿到最底层的sql语句里。方法入口只做了判空,但并没有校验 createDate的数据合法性。当 createDate 的值不是有效日期时,例如是"2024年8月7日",或甚至是一个字符串"abc",则会直接影响最终sql的执行。
@RestController public class DateDemoController { @GetMapping("queryByDate") Result<List<Order>> queryByDate(String createDate) { if (StringUtils.isBlank(createDate)) { return Result.error("日期不能为空"); } List<Order> orderList = getOrderList(createDate); return Result.success(orderList); } private List<Order> getOrderList(String myDate) { LambdaQueryWrapper<Order> queryWrapper = Wrappers.lambdaQuery(new Order()) .ge(Order::getCreateTime, myDate) .le(Order::getCreateTime, myDate + "23:59:59"); return myMapper.selectList(queryWrapper); } }
代码示例2:
@RestController public class DateDemoController { /** * http://localhost:8080/list?createDate=2024-08-19 */ @GetMapping({"/list"}) public List<Order> list(OrderQuery orderQuery) { String string = orderQuery.toString(); log.info("请求参数:{}", string); LambdaQueryWrapper<Order> queryWrapper = Wrappers.lambdaQuery(new Order()) .ge(orderQuery.getCreateDate() != null, Order::getCreateTime, orderQuery.getCreateDate()) .lt(orderQuery.getCreateDate() != null, Order::getCreateTime, DateUtils.addDays(orderQuery.getCreateDate(), 1)); List<Order> orderList = myMapper.selectList(queryWrapper); return orderList; } } @Data public class OrderQuery { @JsonSerialize(using = ToStringSerializer.class) private Long enterpriseId; private String enterpriseName; private String createDate; //** override getter public Date getCreateDate() { if (StringUtils.isBlank(createDate)) return null; try { return DateUtils.parseDate(createDate, "yyyy-MM-dd"); } catch (ParseException e) { throw new RuntimeException(e); } } }
添加评论