工作日计算工具类

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

思路:

预先设置好一定年份范围内的节假日、补休到map里。(这里暂时只设置了2017 - 2018年的)

将这个年份范围内的每一天是否为节假日存到数组里,以2017-2018为例,两年有365*2=730天,则开一个数组boolean workdays[730],用于存放这个年份范围内的每一天是否为工作日。判断方法为:遍历这个年份范围内的每一天,先从map里找是否为节假日或补休,找不到则以是否为周末来判断。

使用线段树或树状数组处理这个数组,从而在O(logN)内求出两个日期之间有多少个工作日。

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;

/**

* 工作日计算工具类

* 目前仅支持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 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);// 生成线段树

}

/**

* 计算两个日期之间有多少个工作日

* @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

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

* 日期格式必须为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);

}

/**

* 边界日期处理策略

* 在计算两个日期之间有多少个工作日时,有的特殊需求是如果开始/结束的日期在某个时间之前/后(如中午十二点前),则不把当天算作一天

* 因此特将此逻辑分离出来,各自按照不同需求实现该接口即可

* @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计算工作日_Java工作日计算工具类相关推荐

  1. java 静态类 安全_Java静态static工具类线程安全问题研究

    针对静态方法有以下一些前提: 静态方法和实例方法的区别是静态方法只能引用静态变量,静态方法通过类名来调用,实例方法通过对象实例来调用 每个线程都有自己的线程栈,栈与线程同时创建,每一个虚拟机线程都有自 ...

  2. java签名软件_Java实现签名工具类

    一 点睛 签名和验证签名常常用于网络安全,在此提供一个工具类. 二 代码 package com.imooc.demo.common.util; import org.apache.commons.c ...

  3. java时间随机数_java开发代码工具类(时间戳/随机数/日期等)

    简介 java开发代码工具类,提高开发效率,持续更新~ 实践 package com.springboot.sixmonth.common.util; import java.math.BigInte ...

  4. java word生成_JAVA生成WORD工具类

    该方法借助freemarker的JAR包即可. 参考: 所需工具: 步骤: 1.在word中编辑好模版样式,设置好占位符,注意图片最好先黏上去 2.在word中,文件-另存为-XML格式 3.使用工具 ...

  5. java校验文件格式_java验证文件格式工具类(获取文件真实格式)

    , String>();privateVerifyFileType(){} static{ getAllFileType();//初始化文件类型信息} /*** Discription:[get ...

  6. java 查找一行_Java培训之工具类通用的查询一行多列,非实体

    //通用的查询方法之四:查询多行多列,但每一行又不是一个JavaBean /* * SELECT did,AVG(salary),MAX(Salary) FROM t_employee GROUP B ...

  7. java描绘条形码_java生成条形码工具类

    /** * 静态内部类 * 自定义的 TextPainter, 允许定义字体,大小,文本等 * 参考底层实现:BaseLineTextPainter.getInstance() */ protecte ...

  8. java每秒限流_java限流工具类

    代码 import com.google.common.util.concurrent.RateLimiter; import java.util.concurrent.ConcurrentHashM ...

  9. Java实现Google的S2算法工具类

    WGS84坐标系 GCJ02坐标系 BD09坐标系的各种转换 WGS84坐标系 GCJ02坐标系 BD09坐标系的各种转换 Google S2 经纬度 转 CellId 经纬度 转 cellToken ...

  10. Java学习总结:58(Collections工具类)

    Collections工具类 Java提供了一个集合的工具类--Collections,这个工具类可以实现List.Set.Map集合的操作.Collections类的常用方法如下: No. 方法 类 ...

最新文章

  1. linux arpwatch 命令详解
  2. 服务器性能/压力测试工具http_load、webbench、ab、Siege使用教程
  3. ctdb main loop
  4. 【LeetCode】剑指 Offer 10- II. 青蛙跳台阶问题
  5. vue前端验证输入_Vue-Element之vue-element 输入框验证
  6. 【编译原理笔记19】代码优化: 支配结点和回边,自然循环及其识别,删除全局公共子表达式和复制语句,代码移动,作用于归纳变量的强度削弱,归纳变量的删除
  7. Python 笔试 —— 效率与优雅
  8. 动态规划-----(思路详解)
  9. 437.路径总和III (力扣leetcode) 博主可答疑该问题
  10. Garmin报警点完善计划
  11. Oracle密码过期
  12. ftp服务器复制文件命令,FTP服务器的Copy命令的使用
  13. Kylin多维分析引擎(四):Kylin Cude构建流程详解
  14. 网盘上传文件服务器失败原因,win10系统在百度网盘上传文件一直失败的恢复教程...
  15. 域名微信拦截html代码,多域名下获取微信openId,通过拦截器注解实现,减少代码量以及业务混淆...
  16. 软件 购 买 优 惠 券及使用说明
  17. 2298: [HAOI2011]problem a
  18. 2013-04《信息资源管理 02378》真卷解析,逐题解析+背诵技巧
  19. 企业研发人员配备比例_如何理解高新技术企业认定对研发人员比例的要求
  20. 栖与谁邻解析:百度网盟操作要筛选哪种无效投放网站, 哪些网站要屏蔽

热门文章

  1. Learning to Write Stylized Chinese Charactersby Reading a Handful of Examples
  2. PostgreSQL开发规范
  3. Python数据分析案例08——预测泰坦尼克号乘员的生存(机器学习全流程)
  4. 前端笔记-201808
  5. 安装google扩展
  6. google浏览器打包扩展程序
  7. Windows10优化系统,优化达到30多项,速度大幅提升,
  8. Mac 终端——常用命令语
  9. Scene(场景)的使用
  10. iOS调试_Couldn't load project