package com.mossle.workcal.support; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; /** * 工作日历. */ public class WorkCalendar { /** 1分钟60秒. */ public static final long MILLIS_OF_MINUTE = 1000L * 60; /** 1小时60分钟. */ public static final long MILLIS_OF_HOUR = 60 * MILLIS_OF_MINUTE; /** 1天8小时. */ public static final long HOUR_OF_DAY = 8; private List<WorkDay> days = new ArrayList<WorkDay>(); private List<Holiday> holidays = new ArrayList<Holiday>(); private List<WorkDay> workDays = new ArrayList<WorkDay>(); private DatatypeFactory datatypeFactory; private boolean accurateToDay; /** * construtor. */ public WorkCalendar() throws Exception { datatypeFactory = DatatypeFactory.newInstance(); } /** * 计算结束时间. */ public Date add(Date date, String period) throws Exception { return this.add(date, this.parsePeriod(period)); } /** * 计算结束时间. */ public Date add(Date startDate, Duration duration) { // 得到对应的时间 Calendar calendar = Calendar.getInstance(); calendar.setTime(startDate); // 添加年数和月数,工作日方面年和月的概念不会改变 calendar.add(Calendar.YEAR, duration.getYears()); calendar.add(Calendar.MONTH, duration.getMonths()); // 天数,小时,分钟可能因为工作日有概念,所以特殊处理 int day = duration.getDays(); int hour = duration.getHours(); int minute = duration.getMinutes(); if (accurateToDay) { // 有时需要自动把一天换算成8个小时,以实际计算工时 hour += (day * HOUR_OF_DAY); day = 0; } else { Date workDate = this.findWorkDate(calendar.getTime()); calendar.setTime(workDate); // 目前还没有更好的算法,所以对天数累加,再判断是否工作日 for (int i = 0; i < day; i++) { calendar.add(Calendar.DATE, 1); int originHour = calendar.get(Calendar.HOUR_OF_DAY); int originMinute = calendar.get(Calendar.MINUTE); // 如果当前就是工作日,就返回当前时间 // 如果当前的时间已经不是工作日了就返回最近的工作日 workDate = this.findWorkDate(calendar.getTime()); calendar.setTime(workDate); calendar.set(Calendar.HOUR_OF_DAY, originHour); calendar.set(Calendar.MINUTE, originMinute); } } Date targetDate = calendar.getTime(); long millis = (hour * MILLIS_OF_HOUR) + (minute * MILLIS_OF_MINUTE); DayPart dayPart = this.findDayPart(targetDate); boolean isInbusinessHours = (dayPart != null); if (!isInbusinessHours) { DayPartResult dayPartResult = this.findTargetWorkDay(targetDate) .findNextDayPartStart(0, targetDate); targetDate = dayPartResult.getDate(); dayPart = dayPartResult.getDayPart(); } Date end = dayPart.add(targetDate, millis); return end; } /** * 把开始时间转换成工作时间,比如当前时间是假期,就要从最近的工作日开始计算. */ public Date findWorkDate(Date date) { // 先找当时所处的时间段,如果找到,可以直接返回当前时间了 DayPart dayPart = this.findDayPart(date); if (dayPart != null) { return date; } // 如果找不到,从当天的第一个时间段开始搜索 DayPartResult dayPartResult = this.findTargetWorkDay(date) .findNextDayPartStart(0, date); // Object[] result = new Object[2]; // this.findDay(date).findNextDayPartStart(0, date, result); // date = (Date) result[0]; // return date; return dayPartResult.getDate(); } /** * 返回第二天. */ public Date findStartOfNextDay(Date date) { Calendar calendar = this.cleanTime(date); return calendar.getTime(); } /** * 找到当天的时间段设置. */ public WorkDay findDay(Date date) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); int weekDayIndex = calendar.get(Calendar.DAY_OF_WEEK); return this.days.get(weekDayIndex); } /** * 日期增加一天,清空时间属性. */ public Calendar cleanTime(Date date) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.add(Calendar.DATE, 1); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return calendar; } /** * 获取时间段配置. */ public DayPart findDayPart(Date date) { if (this.isWorkDay(date)) { DayPart dayPart = this.findWorkDayPart(date); if (dayPart != null) { return dayPart; } } if (this.isHoliday(date)) { return null; } WorkDay day = this.findDay(date); List<DayPart> dayParts = day.getDayParts(); if (dayParts == null) { return null; } for (int i = 0; i < dayParts.size(); i++) { DayPart dayPart = dayParts.get(i); if (dayPart.includes(date)) { return dayPart; } } return null; } // ~ ================================================== public boolean isHoliday(Calendar calendar) { return this.isHoliday(calendar.getTime()); } public boolean isHoliday(Date date) { if (holidays != null) { for (WorkDay holiday : holidays) { if (holiday.isSameDay(date)) { return true; } } } return false; } // ~ ================================================== public boolean isWorkDay(Calendar calendar) { return this.isWorkDay(calendar.getTime()); } public boolean isWorkDay(Date date) { return findWorkDayPart(date) != null; } public WorkDay findWorkDay(Date date) { for (WorkDay workDay : workDays) { if (workDay.isSameDay(date)) { return workDay; } } return null; } public DayPart findWorkDayPart(Date date) { WorkDay workDay = findWorkDay(date); if (workDay == null) { return null; } for (DayPart dayPart : workDay.getDayParts()) { if (dayPart.includes(date)) { return dayPart; } } return null; } public WorkDay findTargetWorkDay(Date date) { WorkDay workDay = this.findWorkDay(date); if (workDay != null) { return workDay; } if (this.isHoliday(date)) { Holiday holiday = new Holiday(this); holiday.setDate(date); return holiday; } return this.findDay(date); } // ~ ================================================== private Duration parsePeriod(String period) throws Exception { return datatypeFactory.newDuration(period); } // ~ ================================================== public void setDays(List<WorkDay> days) { this.days = days; } public void setHolidays(List<Holiday> holidays) { this.holidays = holidays; } public void setWorkDays(List<WorkDay> workDays) { this.workDays = workDays; } public void addHoliday(Holiday holiday) { this.holidays.add(holiday); } public void addWorkDay(WorkDay workDay) { this.workDays.add(workDay); } }