给定一个时间,根据配置的节假日和休息时间,跳过节假日与休息时间,计算推进指定时间之后的时间。转载请附带原文链接
解释一下思路:首先锚定开始时间,由于给定时间可能在节假日或者休息时间(当然也可以在调用方直接拦截,这里没有拦截),所以需要判断并重新计算开始时间,这里的请参看下面代码注释。锚定开始时间后,思路就比较清晰了,总时间(我这里用的是小时单位,如果需要变成分钟,需要自己转)转分钟,除以每日工作时长得到需跨越天数,取余得剩余时长,最后添加即可
下面给出工具类源码:
package cn.li.project.util;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.*;
import org.springframework.data.annotation.Id;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* @author li
*/
public class DateUtil {
//休息时间
private static final String bidRestTime = "18:00-8:00";
/**
* 计算竞价截止时间,自动跳过节假日,此方法会抹除秒级时间
* ####核心代码####
* @param date 当前时间
* @param time 报价时效
* @return 竞价截止时间
*/
public Date computeBidBlockingTimeWithoutHoliday(Date date, Integer time) {
Date targetDate;
LocalDateTime beginDate = LocalDateTimeUtil.of(date);
List<CalendarInfo> calendarDays = getNextMonthsCalendarDays(beginDate.toLocalDate(), 2L);
boolean notBeginningDay = false;
int originHour = beginDate.getHour();
int originMinute = beginDate.getMinute();
//如果当前日期所处时间是节假日,则将开始日期推进到下一个工作日,并设置开始时间为00:00:00,以便没有配置休息时间时使用
if (isHoliday(beginDate)) {
LocalDateTime nextWorkDay = getNextWorkDay(beginDate);
notBeginningDay = true;
beginDate = LocalDateTime.of(
nextWorkDay.getYear(),
nextWorkDay.getMonthValue(),
nextWorkDay.getDayOfMonth(),
0, 0, 0);
}
if (!StrUtil.isBlank(bidRestTime)) {
//如果有休息时间配置
String[] split = bidRestTime.split("-");
String workingStart = split[1];
String workingEnd = split[0];
String[] workingStartSplit = workingStart.split(":");
String[] workingEndSplit = workingEnd.split(":");
//分割时间,得到工作开始、结束时间的小时和分钟位
int workingStartHour = Integer.parseInt(workingStartSplit[0]);
int workingStartMinute = Integer.parseInt(workingStartSplit[1]);
int workingEndHour = Integer.parseInt(workingEndSplit[0]);
int workingEndMinute = Integer.parseInt(workingEndSplit[1]);
//不考虑夜班,即工作跨日
if (workingEndHour < 0 || workingEndHour > 23 || workingStartHour > workingEndHour ||
(workingStartHour == workingEndHour && workingStartMinute < workingEndMinute) ||
workingStartMinute < 0 || workingStartMinute > 59 ||
workingEndMinute < 0 || workingEndMinute > 59) {
throw new RuntimeException("休息时间配置不正确");
}
//判断上下班时间,规整日期开始计算的时间,正常工作日上班时间内不处理
if (notBeginningDay || originHour < workingStartHour ||
(originHour == workingStartHour && originMinute < workingStartMinute)) {
//如果是在节假日或者当前时间在上班开始时间之前,需要把开始时间调整到当天的上班时间
beginDate = beginDate.withHour(workingStartHour).withMinute(workingStartMinute).withSecond(0);
} else if ((originHour == workingEndHour && originMinute > workingEndMinute) ||
originHour > workingEndHour) {
//如果当前时间在下班时间之后,把时间推进到第二天的上班时间
beginDate = getNextWorkDayAndResetWorkTime(beginDate, calendarDays, workingStartHour, workingStartMinute);
}
//设置一个固定日期,以便计算每天的工作时间
String fixDate = "2000-01-01 ";
String formatStr = "yyyy-MM-dd HH:mm";
int workMinutes = (int) cn.hutool.core.date.DateUtil.between(
cn.hutool.core.date.DateUtil.parse(fixDate + workingStart, formatStr),
cn.hutool.core.date.DateUtil.parse(fixDate + workingEnd, formatStr),
DateUnit.MINUTE
);
//将计算时长转换为分钟,计算总共需要跨几日以及跨日期后剩余的分钟数
int minutesTime = time * 60;
int toPlusDay = minutesTime / workMinutes;
int restMinutes = minutesTime % workMinutes;
//推进天数
for (int i = 0; i < toPlusDay; i++) {
beginDate = getNextWorkDay(beginDate, calendarDays);
}
//保存推进后的日期
LocalDateTime workingEndTime = LocalDateTime.of(
beginDate.getYear(), beginDate.getMonthValue(),
beginDate.getDayOfMonth(), workingEndHour, workingEndMinute);
//推进指定的分钟数
LocalDateTime afterDateTime = beginDate.plusMinutes(restMinutes);
if (afterDateTime.isAfter(workingEndTime)) {
//若推进后时间在当天下班时间之后,则计算超出下班时间后的时间,并添加到第二天上班时间之后
int beyondMinutes = (int) LocalDateTimeUtil.between(workingEndTime, afterDateTime, ChronoUnit.MINUTES);
beginDate = getNextWorkDayAndResetWorkTime(beginDate, calendarDays, workingStartHour, workingStartMinute)
.plusMinutes(beyondMinutes);
} else {
beginDate = afterDateTime;
}
} else {
//没有休息时间配置
int day = time / 24;
int hour = time % 24;
//推进天数
for (int i = 0; i < day; i++) {
beginDate = getNextWorkDay(beginDate, calendarDays);
}
int currentHour = beginDate.getHour();
int totalHours = currentHour + hour;
//若推进后
if (totalHours > 23) {
beginDate = getNextWorkDay(beginDate, calendarDays).withHour(totalHours - 23);
} else {
beginDate = beginDate.plusHours(hour);
}
}
String format = "yyyy-MM-dd HH:ss:mm";
targetDate = cn.hutool.core.date.DateUtil.parse(beginDate.format(DateTimeFormatter.ofPattern(format)), format);
return targetDate;
}
/**
* 获取接下来指定月数的内假期配置,代码省略
*/
private List<CalendarInfo> getNextMonthsCalendarDays(LocalDate toLocalDate, Long month) {
return Collections.emptyList();
}
/**
* 从给定节假日配置中向后推进一个工作日,从缓存的节假日列表中推进
* @param beginDate 开始日期
* @param calendarInfoList 给定节假日配置
* @return 计算后的日期
*/
private LocalDateTime getNextWorkDay(LocalDateTime beginDate, List<CalendarInfo> calendarInfoList) {
beginDate = beginDate.plusDays(1);
while (isHoliday(beginDate, calendarInfoList)) {
beginDate = beginDate.plusDays(1);
}
return beginDate;
}
/**
* 从给定节假日配置中向后推进一个工作日,并将时间设置为上班时间
* @param beginDate 开始日期
* @param calendarInfoList 给定节假日配置
* @return 计算后的日期
*/
private LocalDateTime getNextWorkDayAndResetWorkTime(LocalDateTime beginDate,
List<CalendarInfo> calendarInfoList,
int workingStartHour,
int workingStartMinute) {
beginDate = beginDate.plusDays(1);
while (isHoliday(beginDate, calendarInfoList)) {
beginDate = beginDate.plusDays(1);
}
beginDate = beginDate.withHour(workingStartHour).withMinute(workingStartMinute).withSecond(0);
return beginDate;
}
/**
* 从给定节假日配置中向后推进一个工作日,实时查询数据库的节假日列表推进
* @param beginDate 开始日期
* @return 计算后的日期
*/
private LocalDateTime getNextWorkDay(LocalDateTime beginDate) {
LocalDateTime newDate = beginDate.plusDays(1);
while (isHoliday(newDate)) {
newDate = newDate.plusDays(1);
}
return newDate;
}
/**
* 查询数据库配置当天是否是节假日,需要根据date的年月日精准匹配
* 代码省略
*/
private boolean isHoliday(LocalDateTime date) {
return true;
}
/**
* 从给定节假日配置缓存中看当天是否是节假日,需要根据date的年月日精准匹配
* 代码省略
*/
private boolean isHoliday(LocalDateTime newDate, List<CalendarInfo> calendarInfoList) {
return true;
}
}
/**
* 代码中所用实体类,建议配置时,全部日期都默认为工作日,只保存节假日,周六周日调休的不保存,不然需要在isHoliday方法中进行比较细致的判断
*/
@Data
@Builder
@ToString
@TableName("calendar_info")
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
class CalendarInfo {
private static final long serialVersionUID = -1640722497108507541L;
/**
* ID
*/
@Id
private BigDecimal id;
/**
* 创建人
*/
private String createdBy;
/**
* 创建时间
*/
@JSONField(format = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@TableField(value = "created_time", fill = FieldFill.INSERT)
private Date createdTime;
/**
* 更新人
*/
private String updatedBy;
/**
* 更新时间
*/
@JSONField(format = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
private Date updatedTime;
/**
* 是否有效(1:是,0:否)
*/
@TableField(value = "is_valid", fill = FieldFill.INSERT)
private String isValid;
/**
* 实体类保存日期
*/
@TableField("calendar_current_date")
private LocalDate currentDate;
/**
* 日期所在年
*/
@TableField("calendar_year")
private Integer year;
/**
* 日期所在月
*/
@TableField("calendar_month")
private Integer month;
/**
* 日期所在日
*/
@TableField("calendar_day")
private Integer day;
/**
* 日期所在星期几:1,2,3,4,5,6,7之一
*/
@TableField("calendar_week_day")
private Integer weekDay;
}