Java工作日計算工具類
阿新 • • 發佈:2018-11-15
日期 stat 構造 out any 必須 @param fwe sun
工作日計算工具類
主要功能:傳入兩個日期,返回這兩個日期之間有多少個工作日。
思路:先預設值好START_YEAR - END_YEAR年份範圍內的節假日、補休保存到map;然後遍歷這個年份範圍內的每一天,如果在map裏找到相應數據,則以map裏的數據判斷是否為工作日,否則以是否為周末來判斷;最後構造一棵線段樹,便於每次查詢。
import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import java.util.Scanner; /** * 工作日計算工具類<br/> * 目前僅支持2017,2018年 * * @author Corvey * @Date 2018年11月9日16:53:52 */ public class WorkdayUtils { /** 預設工作日數據的開始年份 */ private static final int START_YEAR = 2017; /** 預設工作日數據的結束年份 */ private static final int END_YEAR = 2018; /** 起始日期處理策略 */ private static final BoundaryDateHandlingStrategy START_DATE_HANDLING_STRATEGY = date -> { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); return calendar.get(Calendar.HOUR_OF_DAY) < 12; // 如果開始時間在中午12點前,則當天也算作一天,否則不算 }; /** 結束日期處理策略 */ private static final BoundaryDateHandlingStrategy END_DATE_HANDLING_STRATEGY = date -> { return true; // 結束時間無論幾點,都算作1天 }; /** 工作日map,true為補休,false為放假 */ private static final Map<Integer, Boolean> WORKDAY_MAP = new HashMap<>(); private static final SegmentTree SEGMENT_TREE; static { initWorkday(); // 初始化工作日map // 計算從START_YEAR到END_YEAR一共有多少天 int totalDays = 0; for (int year = START_YEAR; year <= END_YEAR; ++year) { totalDays += getDaysOfYear(year); } int[] workdayArray = new int[totalDays]; // 將工作日的數據存入到數組 Calendar calendar = new GregorianCalendar(START_YEAR, 0, 1); for (int i = 0; i < totalDays; ++i) { // 將日期轉為yyyyMMdd格式的int int datestamp = calendar.get(Calendar.YEAR) * 10000 + (calendar.get(Calendar.MONTH) + 1) * 100 + calendar.get(Calendar.DAY_OF_MONTH); Boolean isWorkDay = WORKDAY_MAP.get(datestamp); if (isWorkDay != null) { // 如果在工作日map裏有記錄,則按此判斷工作日 workdayArray[i] = isWorkDay ? 1 : 0; } else { // 如果在工作日map裏沒記錄,則按是否為周末判斷工作日 int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK); workdayArray[i] = (dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY) ? 1 : 0; } calendar.add(Calendar.DAY_OF_YEAR, 1); } SEGMENT_TREE = new SegmentTree(workdayArray); // 生成線段樹 } /** * 計算兩個日期之間有多少個工作日<br/> * @param startDate * @param endDate * @return */ public static int howManyWorkday(Date startDate, Date endDate) { if (startDate.after(endDate)) { return howManyWorkday(endDate, startDate); } Calendar startCalendar = Calendar.getInstance(); startCalendar.setTime(startDate); int startDays = getDaysAfterStartYear(startCalendar) - 1; // 第一天從0開始 Calendar endCalendar = Calendar.getInstance(); endCalendar.setTime(endDate); int endDays = getDaysAfterStartYear(endCalendar) - 1; // 第一天從0開始 if (startDays == endDays) { // 如果開始日期和結束日期在同一天的話 return isWorkDay(startDate) ? 1 : 0; // 當天為工作日則返回1天,否則0天 } if (!START_DATE_HANDLING_STRATEGY.ifCountAsOneDay(startDate)) { // 根據處理策略,如果開始日期不算一天的話 ++startDays; // 起始日期向後移一天 } if (!END_DATE_HANDLING_STRATEGY.ifCountAsOneDay(endDate)) { // 根據處理策略,如果結束日期不算一天的話 --endDays; // 結束日期向前移一天 } return SEGMENT_TREE.query(startDays, endDays); } /** * 是否為工作日 * @param date * @return */ public static boolean isWorkDay(Date date) { Calendar calendar = Calendar.getInstance(); calendar.setTime(date); int days = getDaysAfterStartYear(calendar) - 1; return SEGMENT_TREE.query(days, days) == 1; } /** * 計算從開始年份到這個日期有多少天 * @param calendar * @return */ private static int getDaysAfterStartYear(Calendar calendar) { int year = calendar.get(Calendar.YEAR); if (year < START_YEAR || year > END_YEAR) { throw new IllegalArgumentException(String.format("系統目前僅支持計算%d年至%d年之間的工作日,無法計算%d年!", START_YEAR, END_YEAR, year)); } int days = 0; for (int i=START_YEAR; i<year; ++i) { days += getDaysOfYear(i); } days += calendar.get(Calendar.DAY_OF_YEAR); return days; } /** * 計算該年有幾天,閏年返回366,平年返回365 * @param year * @return */ private static int getDaysOfYear(int year) { return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 ? 366 : 365; } /** * 初始化工作日Map<br/> * 日期格式必須為yyyyMMdd,true為補休,false為放假,如果本來就是周末的節假日則不需再設置 */ private static void initWorkday() { // ---------------2017------------------ WORKDAY_MAP.put(20170102, false); WORKDAY_MAP.put(20170122, true); WORKDAY_MAP.put(20170127, false); WORKDAY_MAP.put(20170130, false); WORKDAY_MAP.put(20170131, false); WORKDAY_MAP.put(20170201, false); WORKDAY_MAP.put(20170202, false); WORKDAY_MAP.put(20170204, true); WORKDAY_MAP.put(20170401, true); WORKDAY_MAP.put(20170403, false); WORKDAY_MAP.put(20170404, false); WORKDAY_MAP.put(20170501, false); WORKDAY_MAP.put(20170527, true); WORKDAY_MAP.put(20170529, false); WORKDAY_MAP.put(20170530, false); WORKDAY_MAP.put(20170930, true); WORKDAY_MAP.put(20171002, false); // ------------------2018---------------- WORKDAY_MAP.put(20180101, false); WORKDAY_MAP.put(20180211, true); WORKDAY_MAP.put(20180215, false); WORKDAY_MAP.put(20180216, false); WORKDAY_MAP.put(20180219, false); WORKDAY_MAP.put(20180220, false); WORKDAY_MAP.put(20180221, false); WORKDAY_MAP.put(20180224, true); WORKDAY_MAP.put(20180405, false); WORKDAY_MAP.put(20180406, false); WORKDAY_MAP.put(20180408, true); WORKDAY_MAP.put(20180428, true); WORKDAY_MAP.put(20180430, false); WORKDAY_MAP.put(20180501, false); WORKDAY_MAP.put(20180618, false); WORKDAY_MAP.put(20180924, false); WORKDAY_MAP.put(20180929, true); WORKDAY_MAP.put(20180930, true); WORKDAY_MAP.put(20181001, false); WORKDAY_MAP.put(20181002, false); WORKDAY_MAP.put(20181003, false); WORKDAY_MAP.put(20181004, false); WORKDAY_MAP.put(20181005, false); } /** * 邊界日期處理策略<br/> * 在計算兩個日期之間有多少個工作日時,有的特殊需求是如果開始/結束的日期在某個時間之前/後(如中午十二點前),則不把當天算作一天<br/> * 因此特將此邏輯分離出來,各自按照不同需求實現該接口即可 * @author Corvey * @Date 2018年11月12日15:38:16 */ private interface BoundaryDateHandlingStrategy { /** 是否把這個日期算作一天 */ boolean ifCountAsOneDay(Date date); } /** * zkw線段樹 * @author Corvey */ private static class SegmentTree { private int[] data; // 線段樹數據 private int numOfLeaf; // 葉子結點個數 public SegmentTree(int[] srcData) { for (numOfLeaf = 1; numOfLeaf < srcData.length; numOfLeaf <<= 1); data = new int[numOfLeaf << 1]; for (int i = 0; i < srcData.length; ++i) { data[i + numOfLeaf] = srcData[i]; } for (int i = numOfLeaf - 1; i > 0; --i) { data[i] = data[i << 1] + data[i << 1 | 1]; } } /** [left, right]區間求和,left從0開始 */ public int query(int left, int right) { if (left > right) { return query(right, left); } left = left + numOfLeaf - 1; right = right + numOfLeaf + 1; int sum = 0; for (; (left ^ right ^ 1) != 0; left >>= 1, right >>= 1) { if ((~left & 1) == 1) sum += data[left ^ 1]; if ((right & 1) == 1) sum += data[right ^ 1]; } return sum; } } public static void main(String[] args) throws ParseException { System.out.println("測試開始:-------------------"); DateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH"); Scanner cin = new Scanner(System.in); while (cin.hasNext()) { String l = cin.next(); Date start = df.parse(l); String r = cin.next(); Date end = df.parse(r); System.out.println(String.format("%s 到 %s, 有%d個工作日!", df.format(start), df.format(end), howManyWorkday(start, end))); } cin.close(); } }
Java工作日計算工具類