功能需求
很简单的需求,项目定时任务,需要定时获取截止到现在的全年费用金额数据(第三方系统),然后分别统计本日费用总和、本月费用总和、本年费用总和。
处理思路
获取当年第一天开始时间Y、本月第一天开始时间M、当日开始时间D、现在截至时间N,发送接口查询请求,获取 Y - N之间的数据,分别统计汇总,得出结果。
代码处理
此处依赖为
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
// 获取本年开始时间
long yearStartTime = DateUtil.beginOfYear(calendar).getTimeInMillis();
// 获取本月开始时间
long monthStartTime = DateUtil.beginOfMonth(calendar).getTimeInMillis();
// 获取本日开始时间
long dayStartTime = DateUtil.beginOfDay(calendar).getTimeInMillis();
// 获取通用截至时间
long commonEndTime = DateUtil.date(calendar).getTime();
/**
* 处理金额
*
* @param list 列表
* @param monthStartTime 月开始时间
* @param dayStartTime 一天开始时间
* @param commonEndTime 通用结束日期
* @return {@link Map}<{@link String}, {@link BigDecimal}>
*/
private <T> Map<String, BigDecimal> processMoney(
List<T> list, Long monthStartTime, Long dayStartTime, Long commonEndTime
) {
Map<String, BigDecimal> finalMap = new HashMap<>();
BigDecimal yearAmountTemp = ZERO; // 年报销总额初始化
BigDecimal monthAmountTemp = ZERO; // 月报销总额初始化
BigDecimal dayAmountTemp = ZERO; // 日报销总额初始化
// 遍历报销列表,累计年、月、日的报销金额
for (T responseDTO : list) {
yearAmountTemp = yearAmountTemp.add(responseDTO.getPaymentAmount().getAmount());
if (isInPeriod(responseDTO.getProcInstEndTime(), monthStartTime, commonEndTime)) {
monthAmountTemp = monthAmountTemp.add(responseDTO.getPaymentAmount().getAmount());
if (isInPeriod(responseDTO.getProcInstEndTime(), dayStartTime, commonEndTime)) {
dayAmountTemp = dayAmountTemp.add(responseDTO.getPaymentAmount().getAmount());
}
}
}
finalMap.put(YEAR_TOTAL_EXPENSE_AMOUNT, yearAmountTemp);
finalMap.put(MONTH_TOTAL_EXPENSE_AMOUNT, monthAmountTemp);
finalMap.put(DAY_TOTAL_EXPENSE_AMOUNT, dayAmountTemp);
return finalMap;
}
异常出现
本日费用计算无论如何都是空的,本月和本年费用都存在正确的值,后面核实,确实存在当日费用汇总,但是为啥统计出问题了呢?processMoney方法不可能存在异常的吧?
代码debug
遇事不决,debug启动!
然后就发现,不对劲,不对劲,怎么都debug进不去这个方法
if (isInPeriod(responseDTO.getProcInstEndTime(), dayStartTime, commonEndTime)) {
dayAmountTemp=dayAmountTemp.add(responseDTO.getPaymentAmount().getAmount());
}
然后怀疑是时间获取上出问题了,准备拉一个test测试时间过去的这段代码
test测试
直接上图片解释
什么原因呢?接着往下看
什么!calendar.getTime() 时间变了!!!
好,那么好,这时候就可以猜测,hutool工具的处理Calendar类型的变量是对它存在修改的!
开始看源码
//处理年开始的方法
public static Calendar beginOfYear(Calendar calendar) {
// DateField.YEAR ,其中YEAR(1)
return truncate(calendar, DateField.YEAR);
}
// 这步其实已经看出来了是存在更新修改的
public static Calendar truncate(Calendar calendar, DateField dateField) {
return DateModifier.modify(calendar, dateField.getValue(), ModifyType.TRUNCATE);
}
public static Calendar modify(Calendar calendar, int dateField, ModifyType modifyType) {
return modify(calendar, dateField, modifyType, false);
}
// 更新的方法,modifyType 为更新类型,truncateMillisecond为是否更新到ms级别
public static Calendar modify(Calendar calendar, int dateField, ModifyType modifyType, boolean truncateMillisecond) {
int i;
if (9 == dateField) {
boolean isAM = DateUtil.isAM(calendar);
switch (modifyType) {
case TRUNCATE:
// 主要使用的方法,存在set的方法修改变量calendar的值的
calendar.set(11, isAM ? 0 : 12);
break;
case CEILING:
calendar.set(11, isAM ? 11 : 23);
break;
case ROUND:
i = isAM ? 0 : 12;
int max = isAM ? 11 : 23;
int href = (max - i) / 2 + 1;
int value = calendar.get(11);
calendar.set(11, value < href ? i : max);
}
return modify(calendar, dateField + 1, modifyType);
} else {
int endField = truncateMillisecond ? 13 : 14;
for(i = dateField + 1; i <= endField; ++i) {
if (!ArrayUtil.contains(IGNORE_FIELDS, i)) {
if (4 != dateField && 3 != dateField) {
if (7 == i) {
continue;
}
} else if (5 == i) {
continue;
}
modifyField(calendar, i, modifyType);
}
}
if (truncateMillisecond) {
calendar.set(14, 0);
}
return calendar;
}
}
关键代码
// 关键代码
private static void modifyField(Calendar calendar, int field, ModifyType modifyType) {
if (10 == field) {
field = 11;
}
switch (modifyType) {
case TRUNCATE:
// 更新了calendar的值
calendar.set(field, DateUtil.getBeginValue(calendar, field));
break;
case CEILING:
calendar.set(field, DateUtil.getEndValue(calendar, field));
break;
case ROUND:
int min = DateUtil.getBeginValue(calendar, field);
int max = DateUtil.getEndValue(calendar, field);
int href;
if (7 == field) {
href = (min + 3) % 7;
} else {
href = (max - min) / 2 + 1;
}
int value = calendar.get(field);
// 修改 calendar 属性字段,类似YEAR、MONTH、DAY
calendar.set(field, value < href ? min : max);
}
}
到这里,一切都清晰明了!
下面是小许这次工具类的经验总结:
- 遇事不决开始debug
- 快速定位bug范围,拉出代码进行单元测试
- 定位bug,查出原因,查看源码学习(避免踩雷)
hutool类使用DateUtil的总结
1、使用DateUtil.beginOfDay(calendar)和DateUtil.beginOfMonth(calendar) 等入参为Calendar对象的方法时,需要注意,此时此变量是否发生改变,看一下ai的回答吧
2、 为了解决使用calendar对象入参发生改变的问题
- 每次入参都使用全新的calendar实例(非常极端情况下会出现跨天,导致系统逻辑bug)
long dayStartTime = DateUtil.beginOfDay(Calendar.getInstance()).getTimeInMillis();
- 使用DateTime作为入参(推荐),其底层还是返回calendar对象实例,但是时间上不会存在偏差
long dayStartTime = DateUtil.beginOfDay(dateTime).getTime();
源码核心代码
public static DateTime beginOfDay(Date date) {
return new DateTime(beginOfDay(calendar(date)));
}
public static Calendar calendar(Date date) {
return date instanceof DateTime ? ((DateTime)date).toCalendar() : calendar(date.getTime());
}
public Calendar toCalendar() {
return this.toCalendar(Locale.getDefault(Category.FORMAT));
}
public Calendar toCalendar(TimeZone zone, Locale locale) {
if (null == locale) {
locale = Locale.getDefault(Category.FORMAT);
}
Calendar cal = null != zone ? Calendar.getInstance(zone, locale) : Calendar.getInstance(locale);
cal.setFirstDayOfWeek(this.firstDayOfWeek.getValue());
if (this.minimalDaysInFirstWeek > 0) {
cal.setMinimalDaysInFirstWeek(this.minimalDaysInFirstWeek);
}
cal.setTime(this);
return cal;
}
最后谢谢大家看到这里啦!!!