java工作日计算工具类(代码片段)

corvey corvey     2023-01-19     275

关键词:

工作日计算工具类

主要功能:传入两个日期,返回这两个日期之间有多少个工作日。

思路:先预设值好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——git(工具类)(代码片段)

git分布式控制系统 创建目录作为你的仓库,在目录中通过gitinit命令进行初始化;仓库可视为有3个区域:工作区、暂存区、版本区gitaddxxx.txt:将xxx.txt文件从工作区转到暂存区gitcommiit-m“备注”将所有存在暂存区的文件... 查看详情

java工具类六根据经纬度计算距离(代码片段)

Java实现根据经纬度计算距离在项目开发过程中,需要根据两地经纬度坐标计算两地间距离,所用的工具类如下:Demo1: publicstaticdoublegetDistatce(doublelat1,doublelat2,doublelon1,doublelon2)doubleR=6371;doubledistance=0.0;doubledLat=(lat2-lat1)*Math.PI/180;d... 查看详情

场景应用:java写一个根据生日日期计算距离生日还有多少天的工具类?(代码片段)

Java写一个根据生日日期计算距离生日还有多少天的工具类?packagecom.yyl.algorithm.tools;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Calendar;publicclassDateTool/***计算距离生日还有多少天*@paramaddtime:生日日期*/... 查看详情

场景应用:java写一个根据生日日期计算距离生日还有多少天的工具类?(代码片段)

Java写一个根据生日日期计算距离生日还有多少天的工具类?packagecom.yyl.algorithm.tools;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Calendar;publicclassDateTool/***计算距离生日还有多少天*@paramaddtime:生日日期*/... 查看详情

java必会的工具库,让你的代码量减少90%...(代码片段)

工作很多年后,才发现有很多工具类库,可以大大简化代码量,提升开发效率,初级开发者却不知道。而这些类库早就成为了业界标准类库,大公司的内部也都在使用,如果刚工作的时候就有人告诉我使用... 查看详情

好工具推荐——hutool工具类(代码片段)

1、简介Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。Hutool中的工具方法来自每个用户的精雕细... 查看详情

java中并发常用工具类及示例代码(代码片段)

...就不觉的无聊了今日记录:四个并发中可能会用到的工具类,分别是:CountDownLatchCyclicBarrierSemaphoreExchangerCountDownLatch是一组线程等待其他的线程完成工作以后在执行,加强版join区别在于:调用thread.join()方法必须等th... 查看详情

java几何计算类(代码片段)

查看详情

java吐司工具类(代码片段)

查看详情

java吐司工具类(代码片段)

查看详情

java吐司工具类(代码片段)

查看详情

java工具类——java将一串数据按照gzip方式压缩和解压缩(代码片段)

    我要整理在工作中用到的工具类分享出来,也方便自己以后查阅使用,这些工具类都是我自己实际工作中使用的 importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.File;importjava.io.IOException;importj... 查看详情

(工作效率提升杂记)——visualstudio效率提升类的的工具和设置(个人)(代码片段)

...章目录(工作效率提升杂记)——VisualStudio效率提升类的的工具和设置(个人)概论相关设置**VisualStudioIDE****VisualStudioCode**办公提升工具GIT使用类个人格言(工作效率提升杂记)——VisualStudio效率提升类的的工具和设置(个人)概论   ... 查看详情

java工具arthas的使用--结合实际工作(代码片段)

目录安装使用sc查看JVM已加载的类信息sm(查看已加载类的方法信息)thread命令dashboard命令trace多层调用trace特定调用参数trace多方法&多层调用stackwatchognl安装使用https://arthas.aliyun.com/zh-cn/help命令帮助cls命令清屏命令列表... 查看详情

计算时间工具类(代码片段)

packagecom.dosion.shop.common.core.util;importjava.text.ParseException;importjava.text.SimpleDateFormat;importjava.util.Calendar;importjava.util.Date;/****日期工具类**@authordamao**/publicclassDateAndTimeUtil/****日期月份减一个月**@paramdatetime*日期(2014-11)*@return2014-10*/publicsta... 查看详情

java重试工具类(代码片段)

查看详情

java文件操作工具类(代码片段)

查看详情

java时间处理工具类(代码片段)

查看详情