/** * Copyright 2007-2008 非也 * All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation。 * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses. * */ package org.fireflow.engine.modules.calendar.impl; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import org.fireflow.engine.context.AbsEngineModule; import org.fireflow.engine.context.RuntimeContext; import org.fireflow.engine.modules.calendar.CalendarService; import org.fireflow.model.misc.Duration; /** * 缺省的日历服务实现类,请在业务系统中扩展该类。 * * 一般情况下,覆盖isBusinessDay,getBusinessTime,getAverageWorkingHours三个方法即可。 * * @author 非也,nychen2000@163.com */ public class CalendarServiceDefaultImpl extends AbsEngineModule implements CalendarService { /** * 一天的工作时段,例如8:30-12:00 & 13:30-17:30 表示早上8点半到中午12点和下午1点半到5点半 * */ public static final String business_time = "business_time"; /** * 保留 */ public static final String business_time_monday = "business_time.monday"; /** * 保留 */ public static final String business_time_tuesday = "business_time.tuesday"; /** * 保留 */ public static final String business_time_wednesday = "business_time.wednesday"; /** * 保留 */ public static final String business_time_thursday = "business_time.thursday"; /** * 保留 */ public static final String business_time_friday = "business_time.friday"; /** * 保留 */ public static final String business_time_saturday = "business_time.saturday"; /** * 保留 */ public static final String business_time_sunday = "business_time.sunday"; /** * 用于日历换算的属性。 * 缺省实现类在构造函数中设置了日历的相关属性,你可以扩展这种实现方式,将日历属性放在配置文件中 */ private Properties businessCalendarProperties = new Properties(); private float averageWorkingHours = 7.5f; protected RuntimeContext rtCtx = null; public CalendarServiceDefaultImpl() { //初始化businessCalendarProperties businessCalendarProperties.setProperty(business_time, "8:30-12:00 & 13:30-17:30");//工作时段 } /* * (non-Javadoc) * @see org.fireflow.engine.modules.calendar.CalendarService#dateAfter(java.util.Date, org.fireflow.model.misc.Duration) */ public Date dateAfter(Date fromDate, Duration duration) { if (duration.getUnit().equals(Duration.SECOND) || duration.getUnit().equals(Duration.MINUTE) || duration.getUnit().equals(Duration.WEEK) || duration.getUnit().equals(Duration.MONTH) || duration.getUnit().equals(Duration.YEAR) || (duration.getUnit().equals(Duration.DAY) && !duration.isBusinessTime()) || (duration.getUnit().equals(Duration.HOUR) && !duration.isBusinessTime())) { Calendar cal = Calendar.getInstance(); cal.setTime(fromDate); if (duration.getUnit().equals(Duration.MONTH)) { cal.add(Calendar.MONTH, duration.getValue()); } else if (duration.getUnit().equals(Duration.YEAR)) { cal.add(Calendar.YEAR, duration.getValue()); } else if (duration.getUnit().equals(Duration.DAY)){ cal.add(Calendar.DATE, duration.getValue()); }else if (duration.getUnit().equals(Duration.HOUR)){ cal.add(Calendar.HOUR, duration.getValue()); }else if (duration.getUnit().equals(Duration.MINUTE)){ cal.add(Calendar.MINUTE, duration.getValue()); }else if (duration.getUnit().equals(Duration.SECOND)){ cal.add(Calendar.SECOND, duration.getValue()); }else if (duration.getUnit().equals(Duration.WEEK)){ cal.add(Calendar.DATE, duration.getValue()*7); } return cal.getTime(); } //计算工作日间隔 else if (duration.getUnit().equals(Duration.DAY) && duration.isBusinessTime()) { float hoursPerDay = this.getAverageWorkingHours(); if(hoursPerDay>0){ int totalDurationInMillseconds = (int) (duration.getValue() * hoursPerDay * 60 * 60 * 1000); return businessDateAfter(fromDate, totalDurationInMillseconds); }else{ businessDateAfter2(fromDate,duration.getValue());//如果没有规定每日工时,则按照方案2计算 } } //计算工作时间隔 else if (duration.getUnit().equals(Duration.HOUR) && duration.isBusinessTime()) { int totalDurationInMillseconds = (int) (duration.getValue() * 60 * 60 * 1000); return businessDateAfter(fromDate, totalDurationInMillseconds); } return null; } /** * * @param fromDate * @param businessDays * @return */ protected Date businessDateAfter2(Date fromDate,int businessDays){ if (businessDays<=0){ return fromDate; } Calendar cal = Calendar.getInstance(); cal.setTime(fromDate); for (int i=0;i<businessDays;i++){ cal.add(Calendar.DATE, 1); while (!this.isBusinessDay(cal.getTime())) { cal.add(Calendar.DATE, 1); } } return cal.getTime(); } /** * 从fromDate开始,计算totalDurationInMillseconds毫秒后的日期,排除工作日和非工作时间。 * @param fromDate * @param totalDurationInMillseconds * @return */ protected Date businessDateAfter(Date fromDate, int totalDurationInMillseconds) { int remaining = totalDurationInMillseconds; int workingTimeOfDay = 0; Date theDate = fromDate; SimpleDateFormat dateFormat = new SimpleDateFormat(HOUR_FORMAT); boolean spanDay = false; while (remaining > 0) { //首先减去当天的工作时间 workingTimeOfDay = this.getTotalWorkingTime(theDate); remaining = remaining - workingTimeOfDay; if (remaining > 0) { //theDate = theDate+(1 biz day); Calendar cal = Calendar.getInstance(); cal.setTime(theDate); cal.add(Calendar.DATE, 1); while (!this.isBusinessDay(cal.getTime())) { cal.add(Calendar.DATE, 1); }//剔除非工作日 //定位到新的一天的开始工作时间点 String businessTime = this.getBusinessTime(cal.getTime());//剔除非工作日后应该不会有businessTime==null的情况 spanDay = true; int idx = businessTime.indexOf("-"); Date dateTmp = null; try { dateTmp = dateFormat.parse(businessTime.substring(0, idx)); } catch (ParseException ex) { Logger.getLogger(CalendarServiceDefaultImpl.class.getName()).log(Level.SEVERE, null, ex); } Calendar cal2 = Calendar.getInstance(); cal2.setTime(dateTmp); cal.set(Calendar.HOUR_OF_DAY, cal2.get(Calendar.HOUR_OF_DAY)); cal.set(Calendar.MINUTE, cal2.get(Calendar.MINUTE)); cal.set(Calendar.SECOND, cal2.get(Calendar.SECOND)); cal.set(Calendar.MILLISECOND, cal2.get(Calendar.MILLISECOND)); theDate = cal.getTime(); } } if (remaining <= 0) { //精确计算结束时间点 remaining = remaining + workingTimeOfDay; String businessTime = getBusinessTime(theDate); if (businessTime != null) { StringTokenizer stringTokenizer = new StringTokenizer(businessTime, "&"); int totalTime = 0; while (stringTokenizer.hasMoreTokens()) { String bizTimeSpan = stringTokenizer.nextToken().trim(); Calendar cal = Calendar.getInstance(); cal.setTime(theDate); //如果跨了日期或者当前时间在工作时间段之前,则定位到下一时间段的起始点 int inTheSpan = testTimeInTheTimeSpan(theDate, bizTimeSpan); if (spanDay || inTheSpan == -1) { int index1 = bizTimeSpan.indexOf(":"); int index2 = bizTimeSpan.indexOf("-"); int hour = Integer.parseInt(bizTimeSpan.substring(0, index1)); int minute = Integer.parseInt(bizTimeSpan.substring(index1 + 1, index2)); cal.set(Calendar.HOUR_OF_DAY, hour); cal.set(Calendar.MINUTE, minute); theDate = cal.getTime(); } else if (inTheSpan == 1) { continue; } else if (inTheSpan == 0) { String timeStart = dateFormat.format(theDate); int indexTmp = bizTimeSpan.indexOf("-"); bizTimeSpan = timeStart + bizTimeSpan.substring(indexTmp); } totalTime = getTotalWorkingTime(bizTimeSpan); if (totalTime < remaining) { remaining = remaining - totalTime; } else { cal.add(Calendar.MILLISECOND, remaining); theDate = cal.getTime(); break; } } } } return theDate; } /* * (non-Javadoc) * @see org.fireflow.engine.modules.calendar.CalendarService#getAverageWorkingHours() */ public float getAverageWorkingHours(){ return this.averageWorkingHours; } public void setAverageWorkingHours(float f){ this.averageWorkingHours = f; } /** * 获得当天的(剩余)工时,以毫秒为单位 * @param date * @return */ protected int getTotalWorkingTime(Date date) { if (!this.isBusinessDay(date)) { return 0;//非工作日的工时为0 } String businessTime = getBusinessTime(date); if (businessTime == null) { return 0; } //判断date属于哪个时间段 SimpleDateFormat dFormat = new SimpleDateFormat(HOUR_FORMAT); String paramTimeStr = dFormat.format(date); StringTokenizer stringTokenizer = new StringTokenizer(businessTime, "&"); List<String> timeSpanList = new ArrayList<String>(); while (stringTokenizer.hasMoreTokens()) { String timeSpan = stringTokenizer.nextToken(); int isInTheSpan = testTimeInTheTimeSpan(date, timeSpan); if (isInTheSpan == -1) { timeSpanList.add(timeSpan); } else if (isInTheSpan == 0) { String tmp = paramTimeStr + timeSpan.substring(timeSpan.indexOf("-")); timeSpanList.add(tmp); } } int totalTime = 0; for (int i = 0; i < timeSpanList.size(); i++) { String timeSpan = (String) timeSpanList.get(i); totalTime = totalTime + getTotalWorkingTime(timeSpan); } return totalTime; } /** * 检查时间点是否在给定的区间内 * @param timeStr * @param timeSpan * @return 0-在给定的区间内,-1在给定的区间前,1在给定的区间后 */ protected int testTimeInTheTimeSpan(Date d, String timeSpan) { SimpleDateFormat dFormat = new SimpleDateFormat(DAY_FORMAT); String dayStr = dFormat.format(d); dFormat.applyPattern(DAY_FORMAT + " " + HOUR_FORMAT); int idx = timeSpan.indexOf("-"); String date1Str = dayStr + " " + timeSpan.substring(0, idx); String date2Str = dayStr + " " + timeSpan.substring(idx + 1); Date date1 = null; Date date2 = null; try { date2 = dFormat.parse(date2Str); } catch (ParseException ex) { Logger.getLogger(CalendarServiceDefaultImpl.class.getName()).log(Level.SEVERE, null, ex); } try { date1 = dFormat.parse(date1Str); } catch (ParseException ex) { Logger.getLogger(CalendarServiceDefaultImpl.class.getName()).log(Level.SEVERE, null, ex); } if (d.before(date1)) { return -1; } else if (d.after(date2)) { return 1; } return 0; } /** * 获得时间段的工作时间,以毫秒表示 * @param timeSpan 例如:8:30-12:00 * @return */ protected int getTotalWorkingTime(String timeSpan) { if (timeSpan == null) { return 0; } StringTokenizer stringTokenizer = new StringTokenizer(timeSpan, "-"); String dStr1 = null; String dStr2 = null; if (stringTokenizer.hasMoreTokens()) { dStr1 = stringTokenizer.nextToken(); } if (stringTokenizer.hasMoreTokens()) { dStr2 = stringTokenizer.nextToken(); } if (dStr1 == null || dStr2 == null) { return 0; } SimpleDateFormat dFormat = new SimpleDateFormat(HOUR_FORMAT); Date d1 = null; try { d1 = dFormat.parse(dStr1); } catch (ParseException ex) { Logger.getLogger(CalendarServiceDefaultImpl.class.getName()).log(Level.SEVERE, null, ex); } Date d2 = null; try { d2 = dFormat.parse(dStr2); } catch (ParseException ex) { Logger.getLogger(CalendarServiceDefaultImpl.class.getName()).log(Level.SEVERE, null, ex); } if (d1 == null || d2 == null) { return 0; } return (int) (d2.getTime() - d1.getTime()); } /* * (non-Javadoc) * @see org.fireflow.engine.modules.calendar.CalendarService#getBusinessTime(java.util.Date) */ public String getBusinessTime(Date date) { if (!this.isBusinessDay(date)){ return null; } String businessTime = this.businessCalendarProperties.getProperty(business_time); Calendar cal = Calendar.getInstance(); cal.setTime(date); String businessTime2 = null; int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); switch (dayOfWeek) { case 1: businessTime2 = this.businessCalendarProperties.getProperty(business_time_sunday);//周日 break; case 2: businessTime2 = this.businessCalendarProperties.getProperty(business_time_monday);//周1 break; case 3: businessTime2 = this.businessCalendarProperties.getProperty(business_time_tuesday);//周2 break; case 4: businessTime2 = this.businessCalendarProperties.getProperty(business_time_wednesday);//周3 break; case 5: businessTime2 = this.businessCalendarProperties.getProperty(business_time_thursday);//周4 break; case 6: businessTime2 = this.businessCalendarProperties.getProperty(business_time_friday);//周5 break; case 7: businessTime2 = this.businessCalendarProperties.getProperty(business_time_saturday);//周六 break; } if (businessTime2 != null) { businessTime = businessTime2; } return businessTime; } /** * 缺省实现只将周六周日当做非工作日。 */ public boolean isBusinessDay(Date d) { if (d == null) { return false; } Calendar cal = Calendar.getInstance(); cal.setTime(d); int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); if (dayOfWeek == 1 || dayOfWeek == 7) { return false; } return true; } /** * 返回日历属性 * @return */ public Properties getBusinessCalendarProperties() { return businessCalendarProperties; } /** * 设置日历属性。该设置不是一个替换操作,而是一个覆盖操作,代码如下<br> * this.businessCalendarProperties.putAll(props);<br> * 即方法参数中提供的属性被合并到缺省定义中。如果参数中提供的属性和缺省属性同名,则缺省属性被覆盖,否则缺省属性被保留。 * @param props */ public void setBusinessCalendarProperties(Properties props) { this.businessCalendarProperties.putAll(props); } /** * 缺省实现返回当前JVM所在机器的系统时间。即return new Date(); */ public Date getSysDate() { return new Date(); } public void setRuntimeContext(RuntimeContext ctx) { this.rtCtx = ctx; } public RuntimeContext getRuntimeContext(){ return this.rtCtx; } }