java获取国家法定节假日(不依赖API)
java获取国家法定节假日, 由此可获取每月第一个工作日和最后一个工作日
(不依赖API,主要是因为API接口不可靠或计费,此工具依赖国务院发布的节假日基础信息进行解析)
本工具仅供参考学习。各类数据获取需遵守法律法规
package com.exrate;
import cn.hutool.core.util.NumberUtil;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Q
*/
public class ChinaHolidaysUtils {
/**
* 国务院发布的节假日安排的通知 保存的文件路径
*/
private static final String HOLIDAY_NOTICES_FILE_PATH = ChinaHolidaysUtils.class.getResource("/").getPath()+"国务院发布的节假日安排的通知/";
/**
* 国务院文件搜索地址
*/
private static final String GOV_URL = "http://sousuo.gov.cn/s.htm?t=paper&advance=false&n=10&timetype=timeqb&mintime=&maxtime=&sort=pubtime&q=%E8%8A%82%E5%81%87%E6%97%A5%E5%AE%89%E6%8E%92%E7%9A%84%E9%80%9A%E7%9F%A5";
private static Set<String> publicHolidays = new ConcurrentSkipListSet<>();
private static Set<String> oxenHorseDays = new ConcurrentSkipListSet<>();
public static void main(String[] args) {
try {
System.out.println(isOxenHorseDays(LocalDate.now().toString()));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("publicHolidays: " + publicHolidays);
System.out.println("oxenHorseDays: " + oxenHorseDays);
}
/**
* 是否为调休补班日
* @param localDate
* @return
* @throws IOException
*/
public static boolean isOxenHorseDays(String localDate) throws IOException {
if(oxenHorseDays.isEmpty()){
getDays(localDate, publicHolidays, oxenHorseDays);
}
return oxenHorseDays.contains(localDate);
}
/**
* 是否为法定节假日
* @param localDate
* @return
* @throws IOException
*/
public static boolean isPublicHolidays(String localDate) throws IOException {
if(publicHolidays.isEmpty()){
getDays(localDate, publicHolidays, oxenHorseDays);
}
return publicHolidays.contains(localDate);
}
private static synchronized void getDays(String localDate, Set<String> publicHolidays, Set<String> oxenHorseDays) throws IOException {
//获取xxx年的节假日数据
String year = null;
if(StringUtils.isEmpty(localDate)){
year = String.valueOf(LocalDate.parse(localDate).getYear());
}else{
year = String.valueOf(LocalDate.now().getYear());
}
//先通过缓存文件,否则使用http获取
String html = getHtmlByCacheFiles(year);
if(html == null){
html = getHtmlByHttp(year);
}
Document doc = Jsoup.parse(html);
Element content = doc.select("div.b12c.pages_content").first();
Elements paragraphs = content.select("p");
for (Element p : paragraphs) {
String text = p.text();
if (text.contains("、") && text.contains(":") && text.contains("。") && text.contains("放假")) {
text = text.substring(text.indexOf(":")+1);
String[] sentences = text.split("。");
for (String sentence : sentences) {
if (sentence.contains("放假")) {
String t = sentence.split("放假")[0];
if (t.contains("至")) {
String start = t.split("至")[0];
String startDay=null, startMonth=null, startYear=null;
if(start.contains("日") || start.contains("月") || start.contains("年")){
startDay = getDigit(start, "日");
startMonth = getDigit(start, "月");
startYear = getDigit(start, "年");
}
LocalDate startDate = parseDate(startYear==null?year:startYear, startMonth, startDay);
String end = t.split("至")[1];
String endDay=null, endMonth=null, endYear=null;
if(end.contains("日") || end.contains("月") || end.contains("年")){
endDay = getDigit(end, "日");
endMonth = getDigit(end, "月");
endYear = getDigit(end, "年");
}
LocalDate endDate = parseDate(endYear==null?(startYear==null?year:startYear):endYear, endMonth==null?startMonth:endMonth, endDay);
for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
publicHolidays.add(date.toString());
}
} else {
String tDay=null, tMonth=null, tYear=null;
if(t.contains("日") || t.contains("月") || t.contains("年")){
tDay = getDigit(t, "日");
tMonth = getDigit(t, "月");
tYear = getDigit(t, "年");
}
LocalDate date = parseDate(tYear==null? year:tYear, tMonth, tDay);
publicHolidays.add(date.toString());
}
}
if (sentence.contains("上班")) {
String t = sentence.split("上班")[0];
if (sentence.contains("、")) {
String[] dates = sentence.split("、");
for (String dateStr : dates) {
String tDay=null, tMonth=null, tYear=null;
if(dateStr.contains("日") || dateStr.contains("月") || dateStr.contains("年")){
tDay = getDigit(dateStr, "日");
tMonth = getDigit(dateStr, "月");
tYear = getDigit(dateStr, "年");
}
LocalDate date = parseDate(tYear==null? year:tYear, tMonth, tDay);
oxenHorseDays.add(date.toString());
}
}else{
String tDay=null, tMonth=null, tYear=null;
if(t.contains("日") || t.contains("月") || t.contains("年")){
tDay = getDigit(t, "日");
tMonth = getDigit(t, "月");
tYear = getDigit(t, "年");
}
LocalDate date = parseDate(tYear==null? year:tYear, tMonth, tDay);
oxenHorseDays.add(date.toString());
}
}
}
}
}
}
/**
* 模拟人为操作的参数
* @param url
*/
private static Request getRequestSetUnifiedHead(String url){
Request.Builder builder = new Request.Builder();
builder.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36")
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7")
.header("Host", "www.gov.cn")
.header("Referer", "http://sousuo.gov.cn/")
// .header("Accept-Encoding", "gzip, deflate, br") 造成乱码问题
.header("Accept-Language", "zh-CN,zh;q=0.9")
.header("Cache-Control", "max-age=0")
.header("Connection", "keep-alive")
.header("Sec-Fetch-Dest", "document")
.header("Sec-Fetch-Mode", "navigate")
.header("Sec-Fetch-Site", "cross-site")
.header("Sec-Fetch-User", "?1")
.header("Upgrade-Insecure-Requests", "1")
.header("sec-ch-ua", ""Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"")
.header("sec-ch-ua-mobile", "?0")
.header("sec-ch-ua-platform", "Windows");
return builder.url(url).build();
}
/**
* http get请求
* @param client
* @param url
* @return
* @throws IOException
*/
private static String httpGet(OkHttpClient client, String url) throws IOException {
Request request = getRequestSetUnifiedHead(url);
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("获取数据失败:" + url);
}
String html = response.body().string();
System.out.println("进行了一次http get请求:" + url);
return html;
}
/**
* 通过http获取国务院发布xxxx年的节假日安排的通知
* @return
* @throws IOException
*/
private static String getHtmlByHttp(String year) throws IOException {
OkHttpClient client = new OkHttpClient();
String html = httpGet(client, GOV_URL);
Document doc = Jsoup.parse(html);
Elements resList = doc.select("li.res-list");
if (!resList.isEmpty()) {
Optional<Element> optional = resList.stream().filter(res -> res.text().contains("国务院办公厅关于"+year+"年")).findFirst();
if (!optional.isPresent()) {
throw new IOException("未获取到"+ year +"年节假日安排的通知:" + GOV_URL);
}
Element element = optional.get();
String linkUrl = element.select("a[href]").attr("abs:href");
html = httpGet(client, linkUrl);
str2File(html, HOLIDAY_NOTICES_FILE_PATH, year+"节假日安排的通知-源数据", ".html");
Document resDoc = Jsoup.parse(html);
str2File(html, HOLIDAY_NOTICES_FILE_PATH, resDoc.title(), ".html");
return html;
}
return null;
}
/**
* 先通过缓存节假日通知的指定目录中获取 当年的 节假日通知文件
* @param year
* @return
*/
private static String getHtmlByCacheFiles(String year) {
String[] paths = new File(HOLIDAY_NOTICES_FILE_PATH).list();
if(paths != null && paths.length > 0){
Optional<String> yearPath = Arrays.stream(paths).filter(p -> p.contains(year)).findFirst();
if (yearPath.isPresent()){
return file2Str(HOLIDAY_NOTICES_FILE_PATH + yearPath.get());
}
}
return null;
}
/**
* 根据年月日字符转为 yyyy-M-d 格式的LocalDate
* @param year
* @param month
* @param day
* @return
*/
private static LocalDate parseDate(String year, String month, String day) {
String dateStr = year + "-" + month + "-" + day;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d");
return LocalDate.parse(dateStr, formatter);
}
/**
* 将字符串内容转为文件存到指定路径下
* @param str
* @param filePath
*/
public static void str2File(String str, String filePath, String fileName, String fileSuffix) throws IOException {
File file = new File(filePath + fileName + fileSuffix);
if (!file.getParentFile().exists()) {
boolean created = file.getParentFile().mkdirs();
if (!created) {
throw new IOException("文件路径创建失败");
}
}
try (FileWriter writer = new FileWriter(file)) {
writer.write(str);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将指定路径的文件转为字符串
* @param filePath
* @return
*/
private static String file2Str(String filePath) {
File file = new File(filePath);
try (FileReader reader = new FileReader(file)) {
char[] buffer = new char[(int) file.length()];
reader.read(buffer);
return new String(buffer);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取指定字符串前的数字
*/
private static final Pattern PATTERN = Pattern.compile("[^\d]");
private static String getDigit(String content, String targetStr) {
if(!content.contains(targetStr)){
return null;
}
content = content.substring(0, content.indexOf(targetStr));
StringBuffer sb = new StringBuffer(content);
content = sb.reverse().toString();
//使用正则表达式匹配第一个非数字
Matcher matcher = PATTERN.matcher(content);
if (matcher.find()) {
content = content.substring(0, matcher.start());
sb = new StringBuffer(content);
return sb.reverse().toString();
}
//是否为数字
if(NumberUtil.isNumber(content)){
return new StringBuffer(content).reverse().toString();
}
return null;
}
}