有时候我们会遇到这类问题,比如在OA系统提交年假申请时,系统如何校验当前提交的休假时间,有没有在之前的单据中提交并审批通过的(通过所有流程的年假单据通常会存在一张年假信息表里,记录了年假的开始结束日期)时间发生重叠呢? 下面我们讨论一下这个时间校验的问题:一个Date类型时间或一段Date类型时间在不在某段Date时间范围之中。
举一个栗子:某员工在9月初提交了一条年假申请单(申请时间为9月6日上午至9月7日上午,1天半)并被终审通过了,而该员工后来又提交了一条年假申请单,可能由于粗心,忘记了9月7日上午已经请过年假了,又想把9月7日全天申请了,这个时候系统该如何进行校验呢?(系统不能让本单提交成功),以下为例子的真实应用场景。
实现步骤分析:
一、要实现本功能的前提条件是,数据库中存放审批通过的年假信息的表中的数据存储格式,是否满足业务需求
经过调查发现 存储 请假开始时间与结束时间的字段为 oracle 的 Timestamp 类型字段,而由于受数据库时间格式制约(需要进行设置),这种存储的时间数据在查询语句中判断不出上午或下午,看上图所示,虽然显示数据查询出上午下午,但是在查询语句条件中很难加入对上午下午的判断。故造成的影响是,当天的信息不得不取到后台中进行判断了(取出之后转成Date类型)。
后台执行的查询语句发现,之前的1天半年假,系统将其转化成两条记录记录存入年假信息表中(后台对年假以天为维度进行拆分的)
程序开始进行年假校验,参考代码如下
//校验年假
//fdChanged 申请人, mark1 >= , mark2 <= , fdStartDate 用户录入开始日期, fdEndDate 用户录入结束日期, fdStartNoon 用户录入 开始日期上下午标志, fdEndNoon 用户录入结束日期上下午标志
Map<String,String> fdReviewNoMap = checkAnnualLeave(fdChanged, mark1, mark2, fdStartDate, fdEndDate, fdStartNoon, fdEndNoon);
StringBuffer info = new StringBuffer();
if(fdReviewNoMap.size() > 0){
Iterator<Entry<String,String>> iter = fdReviewNoMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object val = entry.getValue();
info.append(val).append(",");
}
System.out.println("本次年假申请时间与以前的年假申请单年假时间发生重叠,重叠的年假单据号为:"+info);
map.put("info", "本次年假申请时间与以前的年假申请单年假时间发生重叠,重叠的年假单据号为:"+info);
list.add(map);
}
/**
* 检查年假
* @param fdChanged 申请人
* @param mark1
* @param mark2
* @param fdStartDate 用户录入开始日期
* @param fdEndDate 用户录入结束日期
* @param fdStartNoon 用户录入 开始日期上下午标志
* @param fdEndNoon 用户录入结束日期上下午标志
* @return Map<String,String> fdReviewNoMap
* @throws Exception
*/
@SuppressWarnings("unchecked")
public Map<String,String> checkAnnualLeave(String fdChanged,String mark1,String mark2,String fdStartDate,String fdEndDate,
String fdStartNoon,String fdEndNoon) throws Exception{
//检查本次年假是否已经请过了(走完流程的)
StringBuffer sql = new StringBuffer();
sql.append(" select t.fd_review_no,t.fd_start,t.fd_end,t.fd_count from km_attendanceinfol_leave t where 1=1 ");
sql.append(" and t.fd_holiday_type = '0' "); //假期类型为年假
sql.append(" and t.fd_type = '1' "); //流程类型 1: 请假 2: 外出 3: 销假 4:调休
sql.append(" and t.fd_attendance_id = '").append(fdChanged).append("'"); //申请人
sql.append(" and to_char(t.fd_start,'yyyy-mm-dd hh12:mi:ss') ").append(mark1).append("'").append(fdStartDate).append(" 12:00:00'");
sql.append(" and to_char(t.fd_end,'yyyy-mm-dd hh12:mi:ss') ").append(mark2).append("'").append(fdEndDate).append(" 12:00:00'");
Session session = this.sysOrgPersonService.getBaseDao().getHibernateSession();
List<Object[]> querylist = session.createSQLQuery(sql.toString()).list();
System.out.println("查询年假sql:"+sql.toString());
String fdReviewNo = ""; //年假单据号
Map<String,String> fdReviewNoMap = new HashMap<String,String>();
Timestamp tstart = null,tend = null;//开始时间,结束时间
Date dstart = null, dend = null;
int checkNoon = 0; //判断数据是上午: 1 ,下午: 2 , 全天: 3
if(querylist.size() != 0){
for(Object[] obj : querylist){
fdReviewNo = (String)obj[0];
System.out.println(obj[0]+" start:"+obj[1]+" end:"+obj[2]+" count:"+obj[3]);
/**受Oracle 字段类型Timestamp的条件限制(判断不出上午或下午)只能将当天数据都查出来 , 所以并不是查出来单据就一定是重叠的,需要进行判断
1.1 用户输入数据如果是同一天,则将录入数据与后台数据进行比较,看时间是否有重叠部分
1.2 用户输入数据跨天 ,要看是否有重叠的部分
请假开始日期 fdStartDate:2017-09-07 fdStartNoon:1
请假结束日期 fdEndDate:2017-09-07 fdEndNoon:1
当前时间:2017-09-07 00:00:00
当前时间:2017-09-07 12:00:00
*/
tstart = (Timestamp)obj[1]; //得到 Timestamp
tend = (Timestamp)obj[2];
dstart = tstart; //得到 date
dend = tend;
if(fdStartDate.equals(fdEndDate)){//用户输入数据如果是同一天
//将后台数据与当天中午时间进行校验,判断数据是上午: 1 ,下午: 2 , 全天: 3
//判断后台数据是上午,下午 ,全天
checkNoon = checkForenoon(dstart,dend);
System.out.println("checkNoon:"+checkNoon);
switch(checkNoon){
case 1: //后台数据是上午 ,开始校验用户输入数据
if(fdStartNoon.equals("0") && fdEndNoon.equals("0") ){
//用户输入数据也是上午,时间发生重叠
fdReviewNoMap.put(fdReviewNo, fdReviewNo); //将单据号放入Map中(进行去重操作)
}else if(fdStartNoon.equals("0") && fdEndNoon.equals("1") ){
//用户输入数据是全天,时间发生重叠
fdReviewNoMap.put(fdReviewNo, fdReviewNo); //将单据号放入Map中(进行去重操作)
}
break;
case 2: //后台数据是下午
if(fdStartNoon.equals("1") && fdEndNoon.equals("1") ){
//用户输入数据也是下午,时间发生重叠
fdReviewNoMap.put(fdReviewNo, fdReviewNo); //将单据号放入Map中(进行去重操作)
}else if(fdStartNoon.equals("0") && fdEndNoon.equals("1") ){
//用户输入数据是全天,时间发生重叠
fdReviewNoMap.put(fdReviewNo, fdReviewNo); //将单据号放入Map中(进行去重操作)
}
break;
case 3: //后台数据是全天
//用户输入数据不管是上午或下午都会发生重叠,因为当天年假已经被用掉了
fdReviewNoMap.put(fdReviewNo, fdReviewNo); //将单据号放入Map中(进行去重操作)
break;
default: //后台数据有错误
System.out.println("default 后台数据有错误");
break;
}
}else{//用户输入数据跨天
//跨天的情况,跨天并不代表一定是重叠的,需要进行判断是否有重叠部分,结合后台数据 与 用户输入数据进行校验
//用户输入时间数据比后台数据范围要大 因此需要 判断 后台时间在不在用户输入时间范围内即可
if(checkInTime(dstart,dend,fdStartDate, fdStartNoon, fdEndDate, fdEndNoon)){
//后台数据时间在用户输入时间范围之内,有重叠
fdReviewNoMap.put(fdReviewNo, fdReviewNo); //将单据号放入Map中(进行去重操作)
}
}
}
System.out.println("fdReviewNoMap.size():"+fdReviewNoMap.size());
}
return fdReviewNoMap;
}
校验时间范围checkInTime()方法
/**
* 校验时间范围
* @param start 后台数据开始时间
* @param end 后台数据结束时间
* @param fdStartDate 用户录入开始日期
* @param fdStartNoon 用户录入 开始日期上下午标志
* @param fdEndDate 用户录入结束日期
* @param fdEndNoon 用户录入结束日期上下午标志
* @return true 后台数据时间与用户录入时间有重叠 false 没有重叠
* @throws ParseException
*/
public boolean checkInTime(Date start,Date end,String fdStartDate,String fdStartNoon,String fdEndDate,String fdEndNoon) throws ParseException{
//将用户录入开始日期与结束日期 转成 Date类型
String startTime = fdStartNoon.equals("1") ? fdStartDate+" 12:00:00" : fdStartDate+" 00:00:00";
String endTimes = fdEndNoon.equals("1") ? fdEndDate+" 23:59:59" : fdEndDate+" 12:00:00";
Date beginTime = strFormatDate(startTime);
Date endTime = strFormatDate(endTimes);
//判断后台数据时间在不在用户录入时间范围之中
boolean falg1 = belongCalendar(start,beginTime,endTime);
boolean falg2 = belongCalendar(end,beginTime,endTime);
if(falg1 && falg2){
return true;
}
return false;
}
判断时间是否在某段时间段范围内belongCalendar()
/**
* 判断时间是否在时间段内
* @param nowTime
* @param beginTime
* @param endTime
* @return
*/
public boolean belongCalendar(Date nowTime, Date beginTime, Date endTime) {
Calendar date = Calendar.getInstance();
date.setTime(nowTime);
Calendar begin = Calendar.getInstance();
begin.setTime(beginTime);
Calendar end = Calendar.getInstance();
end.setTime(endTime);
if (date.after(begin) && date.before(end)) {
return true;
}else if(nowTime.compareTo(beginTime)==0 || nowTime.compareTo(endTime) == 0 ){
return true;
}else {
return false;
}
}
判断某时间段属于上午下午还是全天 checkForenoon()
/**
* 判断某时间段属于上午下午还是全天
* @param start 开始时间
* @param end 结束时间
* @return 1 是上午 2 是下午 3 全天 0 错误
* @throws ParseException
*/
private int checkForenoon(Date start,Date end) throws ParseException{
String sday = dateFormatStr(start);
Date middle = strFormatDate(sday+" 12:00:00"); //得到当天中午数据
if(start.compareTo(middle) < 0 && end.compareTo(middle) == 0){ //上午 start:2017-09-07 00:00:00.0 end:2017-09-07 12:00:00.0
return 1;
}
if(start.compareTo(middle) == 0 && end.compareTo(middle) > 0){ //下午 2016-08-05 12:00:00.0 end:2016-08-05 23:59:59.0
return 2;
}
if(start.compareTo(middle) < 0 && end.compareTo(middle) > 0){//全天 start:2017-09-06 00:00:00.0 end:2017-09-06 23:59:59.0
return 3;
}
return 0 ;
}
日期格式化方法 dateFormatStr()
/**
* 日期格式化
* @param Date
* return 格式化字符串如 2017-09-07
* @param date
*/
private String dateFormatStr(Date date) {
DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
String time = format.format(date);
// System.out.println("时间:" + time);
return time;
}
字符串日期转date
/**
* 字符串日期转为Date
* @param ld
* @throws ParseException
*/
private Date strFormatDate(String ld) throws ParseException {
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date lendDate = format.parse(ld);
// System.out.println(lendDate);
return lendDate;
}
时间格式化
/**
* 时间格式化
* @param Date
* return 格式化字符串如 2017-09-07 12:00:00
* @param date
*/
private String timeFormatStr(Date date) {
if(date == null)
return null;
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format.format(date);
// System.out.println("时间:" + time);
return time;
}
通过以上方法,可以在特定场合下解决了年假校验的问题了。