|
@ -0,0 +1,324 @@
|
|
|
package com.yihu.jw.dailyReport.service;
|
|
|
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStreamReader;
|
|
|
import java.io.Reader;
|
|
|
import java.net.URL;
|
|
|
import java.net.URLConnection;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.text.SimpleDateFormat;
|
|
|
import java.util.Calendar;
|
|
|
import java.util.Date;
|
|
|
import java.util.HashSet;
|
|
|
import java.util.Map;
|
|
|
import java.util.Set;
|
|
|
import java.util.TimeZone;
|
|
|
import java.util.concurrent.locks.ReadWriteLock;
|
|
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
|
|
|
|
|
/**
|
|
|
* 原生java调用(除开JSON)
|
|
|
* 线程不安全
|
|
|
*
|
|
|
* @author holate
|
|
|
*/
|
|
|
public class Holiday {
|
|
|
/**
|
|
|
* 网址前缀
|
|
|
*/
|
|
|
private static final String BASE_PREFIX = "https://gitee.com/holate/public-holiday/raw/master/holidays/year/";
|
|
|
/**
|
|
|
* 网址后缀
|
|
|
*/
|
|
|
private static final String BASE_SUFFIX = ".json";
|
|
|
/**
|
|
|
* 使用读写锁(单线程忽略)
|
|
|
*/
|
|
|
private static final ReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock();
|
|
|
/**
|
|
|
* 法律规定的放假日期,30+366*2/7向上取整
|
|
|
*/
|
|
|
private static final Set<String> LAW_HOLIDAYS = new HashSet<>(135);
|
|
|
|
|
|
/**
|
|
|
* 由于放假需要额外工作的周末,366*5/7向上取整
|
|
|
*/
|
|
|
private static final Set<String> EXTRA_WORKDAYS = new HashSet<>(262);
|
|
|
/**
|
|
|
* 调用接口获取过的年份,大小设置为4年
|
|
|
*/
|
|
|
private static final Set<Integer> ALREADY_OBTAIN = new HashSet<>(4);
|
|
|
/**
|
|
|
* 格式化日期到天
|
|
|
*/
|
|
|
private static final String TO_DAY = "yyyy-MM-dd";
|
|
|
/**
|
|
|
* 调用接口每天调用一次,记录上一次调用日期
|
|
|
*/
|
|
|
private static String initDate = "";
|
|
|
|
|
|
/**
|
|
|
* 返回指定日期后{@param days}个工作日的日期
|
|
|
*
|
|
|
* @param startDate 开始日期
|
|
|
* @param days 工作日数
|
|
|
* @return {@link Date}
|
|
|
* @date 2020/10/26
|
|
|
* @author holate
|
|
|
*/
|
|
|
public static Date calculateWorkDay(Date startDate, int days) {
|
|
|
// 每日清理节假日重新从接口获取
|
|
|
cleanDate();
|
|
|
if (startDate == null) {
|
|
|
return null;
|
|
|
}
|
|
|
READ_WRITE_LOCK.readLock().lock();
|
|
|
try {
|
|
|
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
|
|
|
cal.setTime(startDate);
|
|
|
//累加自然日
|
|
|
for (int i = 1; i <= days; i++) {
|
|
|
cal.add(Calendar.DATE, 1);
|
|
|
Integer year = cal.get(Calendar.YEAR);
|
|
|
//如果那一年的数据没有获取过,则获取数据
|
|
|
if (!ALREADY_OBTAIN.contains(year)) {
|
|
|
getHolidayForMonth(year);
|
|
|
}
|
|
|
//如果是假日,延后一天
|
|
|
if (isHoliday(cal)) {
|
|
|
days++;
|
|
|
}
|
|
|
}
|
|
|
return cal.getTime();
|
|
|
} finally {
|
|
|
READ_WRITE_LOCK.readLock().unlock();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 返回指定日期后{@param days}个自然日的日期
|
|
|
*
|
|
|
* @param startDate 开始日期
|
|
|
* @param days 自然日数
|
|
|
* @return {@link Date}
|
|
|
* @date 2020/10/26
|
|
|
* @author holate
|
|
|
*/
|
|
|
public static Date calculateNormalDay(Date startDate, int days) {
|
|
|
if (startDate == null) {
|
|
|
return null;
|
|
|
}
|
|
|
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
|
|
|
cal.setTime(startDate);
|
|
|
cal.add(Calendar.DATE, days);
|
|
|
return cal.getTime();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 返回指定日期后{@param days}个节假日的日期
|
|
|
*
|
|
|
* @param startDate 开始日期
|
|
|
* @param days 自然日数
|
|
|
* @return {@link Date}
|
|
|
* @date 2020/10/26
|
|
|
* @author holate
|
|
|
*/
|
|
|
public static Date calculateNextHoliday(Date startDate, int days) {
|
|
|
// 每日清理节假日重新从接口获取
|
|
|
cleanDate();
|
|
|
if (startDate == null) {
|
|
|
return null;
|
|
|
}
|
|
|
READ_WRITE_LOCK.readLock().lock();
|
|
|
try {
|
|
|
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
|
|
|
cal.setTime(startDate);
|
|
|
//累加自然日
|
|
|
for (int i = 1; i <= days; i++) {
|
|
|
cal.add(Calendar.DATE, 1);
|
|
|
Integer year = cal.get(Calendar.YEAR);
|
|
|
//如果那一年的数据没有获取过,则获取数据
|
|
|
if (!ALREADY_OBTAIN.contains(year)) {
|
|
|
getHolidayForMonth(year);
|
|
|
}
|
|
|
//如果是假日,延后一天
|
|
|
if (!isHoliday(cal)) {
|
|
|
days++;
|
|
|
}
|
|
|
}
|
|
|
return cal.getTime();
|
|
|
} finally {
|
|
|
READ_WRITE_LOCK.readLock().unlock();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 判断一天是不是工作日
|
|
|
*
|
|
|
* @param date 日期
|
|
|
* @return true:休息日,false:工作日
|
|
|
* @date 2020/10/26
|
|
|
*/
|
|
|
public static boolean isHoliday(Date date) {
|
|
|
cleanDate();
|
|
|
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT+8"));
|
|
|
cal.setTime(date);
|
|
|
Integer year = cal.get(Calendar.YEAR);
|
|
|
if (!ALREADY_OBTAIN.contains(year)) {
|
|
|
getHolidayForMonth(year);
|
|
|
}
|
|
|
return isHoliday(cal);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 判断一天是不是休息日
|
|
|
*
|
|
|
* @param cal 日期
|
|
|
* @return true:休息日,false:工作日
|
|
|
* @date 2020/10/26
|
|
|
*/
|
|
|
private static boolean isHoliday(Calendar cal) {
|
|
|
SimpleDateFormat simpleFormat = new SimpleDateFormat(TO_DAY);
|
|
|
String day = simpleFormat.format(cal.getTime());
|
|
|
// 法定节假日必定是休息日
|
|
|
if (LAW_HOLIDAYS.contains(day)) {
|
|
|
return true;
|
|
|
}
|
|
|
// 排除法定节假日外的非周末必定是工作日
|
|
|
if (!isWeekend(cal)) {
|
|
|
return false;
|
|
|
}
|
|
|
// 所有周末中只有非补班的才是休息日
|
|
|
return !EXTRA_WORKDAYS.contains(day);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 判断是否为周末
|
|
|
*
|
|
|
* @param cal 日期
|
|
|
* @return true:是周末,false:不是周末
|
|
|
* @date 2020/10/26
|
|
|
* @author holate
|
|
|
*/
|
|
|
private static boolean isWeekend(Calendar cal) {
|
|
|
return cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 每天清空日期
|
|
|
*
|
|
|
* @date 2020/10/26
|
|
|
* @author holate
|
|
|
*/
|
|
|
private static void cleanDate() {
|
|
|
SimpleDateFormat simpleFormat = new SimpleDateFormat(TO_DAY);
|
|
|
String day = simpleFormat.format(new Date());
|
|
|
//如果初始化时间不为当天
|
|
|
if (!day.equals(initDate)) {
|
|
|
READ_WRITE_LOCK.writeLock().lock();
|
|
|
try {
|
|
|
if (!day.equals(initDate)) {
|
|
|
ALREADY_OBTAIN.clear();
|
|
|
EXTRA_WORKDAYS.clear();
|
|
|
LAW_HOLIDAYS.clear();
|
|
|
initDate = day;
|
|
|
}
|
|
|
} finally {
|
|
|
READ_WRITE_LOCK.writeLock().unlock();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 通过年月查询第三方节假日,获取节日和调休的工作日
|
|
|
*
|
|
|
* @param year 年份
|
|
|
* @return {@link Set<Calendar>}
|
|
|
* @date 2020/10/26
|
|
|
* @author holate
|
|
|
*/
|
|
|
@SuppressWarnings("unchecked")
|
|
|
private static void getHolidayForMonth(Integer year) {
|
|
|
//json对象,获取值使用的键
|
|
|
String holiday = "holiday";
|
|
|
//访问url
|
|
|
String url = BASE_PREFIX + year + BASE_SUFFIX;
|
|
|
String json = getUrl(url);
|
|
|
if (json.length() == 0) {
|
|
|
return;
|
|
|
}
|
|
|
//构建json对象
|
|
|
JSONObject body = JSON.parseObject(json);
|
|
|
Map<String, JSONObject> days = (Map<String, JSONObject>) body.get(holiday);
|
|
|
for (Map.Entry<String, JSONObject> entry : days.entrySet()) {
|
|
|
Boolean isHoliday = (Boolean) entry.getValue().get(holiday);
|
|
|
if (isHoliday) {
|
|
|
LAW_HOLIDAYS.add(year + "-" + entry.getKey());
|
|
|
} else {
|
|
|
EXTRA_WORKDAYS.add(year + "-" + entry.getKey());
|
|
|
}
|
|
|
}
|
|
|
ALREADY_OBTAIN.add(year);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 使用get请求访问url
|
|
|
*
|
|
|
* @param url 网址
|
|
|
* @return {@link String} 返回响应内容
|
|
|
* @date 2020/10/26
|
|
|
* @author holate
|
|
|
*/
|
|
|
private static String getUrl(String url) {
|
|
|
//访问返回结果
|
|
|
StringBuilder result = new StringBuilder();
|
|
|
//读取访问结果
|
|
|
BufferedReader read = null;
|
|
|
try {
|
|
|
//创建url
|
|
|
URL realUrl = new URL(url);
|
|
|
//打开连接
|
|
|
URLConnection connection = realUrl.openConnection();
|
|
|
// 设置通用的请求属性
|
|
|
connection.setRequestProperty("accept", "*/*");
|
|
|
connection.setRequestProperty("connection", "Keep-Alive");
|
|
|
connection.setRequestProperty("user-agent", "Mozilla/5.0");
|
|
|
//建立连接
|
|
|
connection.connect();
|
|
|
// 定义 BufferedReader输入流来读取URL的响应
|
|
|
read = new BufferedReader(new InputStreamReader(
|
|
|
connection.getInputStream(), StandardCharsets.UTF_8));
|
|
|
String line;//循环读取
|
|
|
while ((line = read.readLine()) != null) {
|
|
|
result.append(line).append("\n");
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
e.printStackTrace();
|
|
|
} finally {
|
|
|
closeReader(read);
|
|
|
}
|
|
|
return result.toString();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 关闭读取字符流
|
|
|
*
|
|
|
* @param reader 读取字符流
|
|
|
* @date 2020/10/26
|
|
|
* @author holate
|
|
|
*/
|
|
|
private static void closeReader(Reader reader) {
|
|
|
if (reader != null) {
|
|
|
try {
|
|
|
reader.close();
|
|
|
} catch (IOException e) {
|
|
|
e.printStackTrace();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|