Java工具-获取某月份天数、某月最后一天、某月工作日天数(支持自定义节假日)

Java工具-获取某月份天数、某月最后一天、某月工作日天数(支持自定义节假日)

因为之前在项目中有一个工作日志的功能,所以在网上找了一些相关的工具类,都是零零散散,我在这总结一下。废话不多说,上代码!

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @ClassName WorkDayUtil   计算日期、工作日相关的工具类
 * @Auther JunKai
 * @Date 2019/12/24
 */
public class WorkDayUtil {
    /** 预设工作日数据的开始年份 */
    private static final int START_YEAR = 2017;

    /** 预设工作日数据的结束年份 */
    private static final int END_YEAR = 5020;
    
    /** 工作日map,true为补休,false为放假 */
    public static final Map<Integer, Boolean> WORKDAY_MAP = new HashMap<>();

    private static final SegmentTree SEGMENT_TREE;

    private WorkDayUtil() {
    }

    /** 获取一个月天数 */
    public static int getDaysOfMonth(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
    }

    /** 获取一个月的最后一天 */
    public static String getLastDay(Date date) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int lastDays = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
        Date time = calendar.getTime();
        time.setDate(lastDays);
        return df.format(time);
    }

    /** 获取一个月的第一天 */
    public static String getFirstDay(Date date) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        //calendar.add(Calendar.MONTH, -1);
        calendar.set(Calendar.DAY_OF_MONTH, 1);
        String time = df.format(calendar.getTime());
        return time;
    }

    /**
     * 计算两个日期之间有多少个工作日<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);
    }

    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);   // 生成线段树
    }
    
    /**
     * 判断某一日期是否为工作日
     * @param date
     * @return
     */
    public static boolean isWorkDay(int date) {
        if(WORKDAY_MAP.get(date)== null) {
            Calendar calendar = Calendar.getInstance();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            try {
                calendar.setTime(sdf.parse(String.valueOf(date)));
            } catch (ParseException e) {
                e.printStackTrace();
            }
            //如果当天不是补休也不是放假,但是为周末,则为非工作日
            if(calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY){
                return false;
            }
        } else {
            //判断如何当天是法定假日,则为非工作日
            if(WORKDAY_MAP.get(date) == false) {
                return false;
            }
            //判断如何当天是补休,则为工作日
            if(WORKDAY_MAP.get(date)== true) {
                return true;
            }
        }
        return true;
    }

    /**
     * 是否为工作日
     * @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() {
    	 // ---------------2020------------------
        WORKDAY_MAP.put(20200101, false);
        WORKDAY_MAP.put(20200124, false);
        WORKDAY_MAP.put(20200125, false);
        WORKDAY_MAP.put(20200126, false);
        WORKDAY_MAP.put(20200127, false);
        WORKDAY_MAP.put(20200128, false);
        WORKDAY_MAP.put(20200129, false);
        WORKDAY_MAP.put(20200130, false);
        WORKDAY_MAP.put(20200201, true);
        // ---------------2019------------------
        WORKDAY_MAP.put(20190101, false);
        WORKDAY_MAP.put(20190202, true);
        WORKDAY_MAP.put(20190203, true);
        WORKDAY_MAP.put(20190204, false);
        WORKDAY_MAP.put(20190205, false);
        WORKDAY_MAP.put(20190206, false);
        WORKDAY_MAP.put(20190207, false);
        WORKDAY_MAP.put(20190208, false);
        WORKDAY_MAP.put(20190209, false);
        WORKDAY_MAP.put(20190210, false);
        WORKDAY_MAP.put(20190405, false);
        WORKDAY_MAP.put(20190501, false);
        WORKDAY_MAP.put(20190502, false);
        WORKDAY_MAP.put(20190503, false);
        WORKDAY_MAP.put(20190505, true);
        WORKDAY_MAP.put(20190607, false);
        WORKDAY_MAP.put(20190913, false);
        WORKDAY_MAP.put(20190929, true);
        WORKDAY_MAP.put(20191001, false);
        WORKDAY_MAP.put(20191002, false);
        WORKDAY_MAP.put(20191003, false);
        WORKDAY_MAP.put(20191004, false);
        WORKDAY_MAP.put(20191007, false);
        WORKDAY_MAP.put(20191012, true);
        // ------------------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/>
     * 因此特将此逻辑分离出来,各自按照不同需求实现该接口即可
     * @Auther JunKai
     * @Date 2019/12/24
     */
    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;
        }
    }
    
    /** 起始日期处理策略 */
    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天
    };

}

好了,这工具类就完事了!

每天进步一点点

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值