Java 实现自动获取法定节假日

一、背景

在实现业务需求的过程中,遇到了需要计算 x 个工作日后的日期需求。由于工作日是每年国务院发布的,调休和休假都没有规律,所以无法使用算法进行计算。

一般的实现方案是自己维护一个工作日和调休的表,或者去爬取国务院发布的数据。但前者实现起来麻烦,每年都得搞一遍;后者可能涉及法律风险,爬虫的识别策略也不太可靠。

所以还是考虑使用由专人维护的接口,找到了天行数据的接口,个人用户有10个免费接口的额度,每个接口每天限制调用100次。

因为节假日一旦定下来就不会轻易改变,所以可以把获取到的数据存在本地,这样每天100次的接口额度完全够用,不需要进行付费。

二、技术实现方案

整体流程:
  1. **读取节假日配置:**从本地文件中读取节假日,如果本地没有文件,则调用天行接口获取。
  2. **解析数据:**从天行返回的数据里,获取该年份里需要调休的日期和补班的日期。
  3. **计算日期:**循环获取日期,判断是否为工作日,计算x个工作日后的日期。
实现细节:
  1. **文件名:**保存下来的文件,名字里要包含特定的年份。
  2. **计算逻辑:**计算日期的时候,需要考虑到跨年的情况,跨年需要重新获取下一年的数据,再继续进行计算日期。
  3. **日期判断:**工作日=不休假的周一至周五+补班的周六周末。

三、详细代码

Java 代码

主要有五个类,HolidayResponse 是封装天行API的返回结果;TianApiProperties 是获取天行API的key;TianApiHolidayService 是接口;TianApiHolidayServiceImpl 里是具体实现;HttpConnector 是接口请求,这个换成任何一个能发起http请求的库都行。

目前代码是基于SpringBoot写的,但纯粹只是为了方便,实际是可以转成纯Java工具代码,不依赖于SpringBoot。

HolidayResponse.java

java 复制代码
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import java.util.List;

/**
 * @author jing
 * @version 1.0
 * @desc 返回结果
 * @date 2023/12/19 11:40
 **/
@Data
public class HolidayResponse {

    @JsonProperty("code")
    private int code; // 公共参数 - 状态码

    @JsonProperty("msg")
    private String msg; // 公共参数 - 错误信息

    @JsonProperty("result")
    private Result result; // 公共参数 - 返回结果集

    // Getters and setters

    /**
     * Represents the result section of the response.
     */
    @Data
    public static class Result {

        @JsonProperty("update")
        private boolean update; // 公共参数 - 是否更新法定节假日

        @JsonProperty("list")
        private List<HolidayItem> list; // 应用参数 - 节假日列表

        // Getters and setters
    }

    /**
     * Represents an item in the list of holidays.
     */
    @Data
    public static class HolidayItem {
        @JsonProperty("holiday")
        private String holiday; // 应用参数 - 节日日期

        @JsonProperty("name")
        private String name; // 应用参数 - 节假日名称(中文)

        @JsonProperty("vacation")
        private String vacation; // 应用参数 - 节假日数组

        @JsonProperty("remark")
        private String remark; // 应用参数 - 调休日数组

        @JsonProperty("wage")
        private String wage; // 应用参数 - 薪资法定倍数/按年查询时为具体日期

        @JsonProperty("start")
        private int start; // 应用参数 - 假期起点计数

        @JsonProperty("now")
        private int now; // 应用参数 - 假期当前计数

        @JsonProperty("end")
        private int end; // 应用参数 - 假期终点计数

        @JsonProperty("tip")
        private String tip; // 应用参数 - 放假提示

        @JsonProperty("rest")
        private String rest; // 应用参数 - 拼假建议

    }

}

TianApiProperties.java

java 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author jing
 * @version 1.0
 * @desc 配置
 * @date 2023/12/19 11:25
 **/
@Component
@Data
@ConfigurationProperties(prefix = "tianapi")
public class TianApiProperties {

    /**
     * 天行数据凭证key
     */
    private String key;
}

TianApiHolidayService.java

java 复制代码
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.time.LocalDateTime;


/**
 * @author jing
 * @version 1.0
 * @desc 天行接口获取节假日,目前使用的是免费接口,每天只能调用100次,后续如果需要调用更多次数,可以考虑购买付费接口。只能获取到今年和明年的节假日,明年的节假日需要在今年11月份左右才能获取到
 * @date 2023/12/19 11:25
 **/
@Service
public interface TianApiHolidayService {

    /**
     * 计算x个工作日后的日期,跳过节假日
     *
     * @param startTime     开始日期
     * @param workdaysToAdd 需要跳过的工作日天数
     */
    LocalDateTime jumpWorkDay(LocalDateTime startTime, int workdaysToAdd) throws IOException;

}

TianApiHolidayServiceImpl.java (核心逻辑)

java 复制代码
import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.xxx.app.common.library.holiday.domain.HolidayResponse;
import com.xxx.app.common.library.holiday.properties.TianApiProperties;
import com.xxx.app.common.utils.http.HttpConnector;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @author jing
 * @version 1.0
 * @desc 天行接口获取节假日,目前使用的是免费接口,每天只能调用100次,后续如果需要调用更多次数,可以考虑购买付费接口。只能获取到今年和明年的节假日,明年的节假日需要在今年11月份左右才能获取到
 * @date 2023/12/19 11:25
 **/
@Slf4j
@Service
public class TianApiHolidayServiceImpl implements TianApiHolidayService {

    @Resource
    private TianApiProperties tianApiProperties;
    @Resource
    private HttpConnector httpConnector;

    private final String FILE_FORMAT = "./.holiday/%s_holiday.json";

    /**
     * 计算x个工作日后的日期,跳过节假日
     *
     * @param startTime     开始日期
     * @param workdaysToAdd 需要跳过的工作日天数
     */
    public LocalDateTime jumpWorkDay(LocalDateTime startTime, int workdaysToAdd) throws IOException {
        // 从文件中读取节假日
        HolidayResponse response = getHolidayConfig(startTime);
        if (response == null) {
            return null;
        }
        // 节假日,只算周一到周五的
        List<LocalDate> vacationList = new ArrayList<>();
        // 补班列表,表示周末补班的
        List<LocalDate> workDayList = new ArrayList<>();
        extracted(response, vacationList, workDayList);
        return workDayAdd(startTime, workdaysToAdd, vacationList, workDayList);
    }

    /**
     * 提取返回数据里的节假日和调休列表
     *
     * @param response     返回数据
     * @param vacationList 节假日列表
     * @param workDayList  补班列表
     */
    private static void extracted(HolidayResponse response, List<LocalDate> vacationList, List<LocalDate> workDayList) {
        // 节假日列表
        response.getResult().getList().forEach(item -> {
            if (StringUtils.isNotEmpty(item.getWage())) {
                String[] vList = item.getVacation().split("\\|");
                for (String wage : vList) {
                    // 不需要上班的工作日
                    vacationList.add(LocalDate.parse(wage, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
                }
            }
            if (StringUtils.isNotEmpty(item.getRemark())) {
                String[] workList = item.getRemark().split("\\|");
                for (String work : workList) {
                    // 需要上班的周末
                    workDayList.add(LocalDate.parse(work, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
                }
            }
        });
    }

    /**
     * 添加工作日
     *
     * @param startTime     开始时间
     * @param workdaysToAdd 需要添加的工作日天数
     * @param vacationList  节假日列表
     * @param workDayList   补班列表
     * @return LocalDateTime 返回添加工作日后的时间
     * @throws IOException 异常
     */
    private LocalDateTime workDayAdd(LocalDateTime startTime, int workdaysToAdd, List<LocalDate> vacationList, List<LocalDate> workDayList) throws IOException {
        LocalDateTime result = startTime;
        // 今年最后一天
        LocalDateTime lastDayOfYear = LocalDateTime.of(LocalDate.of(startTime.getYear(), 12, 31), LocalTime.MAX);
        // 循环计算,直到工作日天数为0,或者到了今年最后一天
        while (workdaysToAdd > 0 && result.isBefore(lastDayOfYear)) {
            // 判断周一到周五,是否会放假,周六周日是否会补班
            result = result.plusDays(1);
            if (workDayNeedToWork(vacationList, result) && holidayNeedToWork(workDayList, result)) {
                workdaysToAdd--;
            }
        }
        // 如果还有剩余的工作日,就继续往后推
        if (workdaysToAdd > 0) {
            // 如果还有剩余的工作日,就继续往后推
            return jumpWorkDay(result, workdaysToAdd);
        }
        return result;
    }

    /**
     * 工作日需要去上班
     *
     * @param vacationList 节假日列表
     * @param date         日期
     * @return boolean    工作日是否需要上班
     */
    private boolean workDayNeedToWork(List<LocalDate> vacationList, LocalDateTime date) {
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        boolean isWork = dayOfWeek != DayOfWeek.SATURDAY && dayOfWeek != DayOfWeek.SUNDAY;
        if (isWork) {
            // 如果是工作日,还需要判断是否会放假
            LocalDate localDate = LocalDate.from(date);
            return !vacationList.contains(localDate);
        }
        return true;
    }

    /**
     * 周六末需要去补班
     *
     * @param workDayList 补班列表
     * @param date        日期
     * @return boolean    是否为休息日
     */
    private boolean holidayNeedToWork(List<LocalDate> workDayList, LocalDateTime date) {
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        boolean isHoliday = dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY;
        if (isHoliday) {
            // 如果是节假日,还需要判断是否是补班
            LocalDate localDate = LocalDate.from(date);
            return workDayList.contains(localDate);
        }
        return true;
    }


    /**
     * 获取某个日期所在年份的节假日配置数据,注:节假日指来自官方发布的有假节日,每年底政府公布后同步更新
     *
     * @param date 日期
     * @return boolean
     */
    public HolidayResponse getHolidayConfig(LocalDateTime date) throws IOException {
        // 节假日文件路径
        String savePath = String.format(FILE_FORMAT, DateUtil.format(date, "yyyy"));
        try {
            // 从文件中读取节假日
            HolidayResponse response = readJsonFromFile(savePath);
            if (response == null) {
                String url = "https://apis.tianapi.com/jiejiari/index";
                // 获取当年的节假日列表
                Map<String, String> params = new HashMap<>();
                params.put("key", tianApiProperties.getKey());
                params.put("date", DateUtil.format(date, "yyyy-MM-dd"));
                params.put("type", "1");
                String formData = httpConnector.fromData(params);
                String rspBody = httpConnector.doFormPost(url, formData);
                // 将json转换为对象
                Gson gson = new Gson();
                response = gson.fromJson(rspBody, HolidayResponse.class);
                // 将对象写入文件
                writeJsonToFile(response, savePath);
            }
            return response;
        } catch (IOException e) {
            log.error("Error during holiday configuration retrieval : {} , {}", savePath, e.getMessage());
        }
        return null;
    }

    /**
     * 将json写入文件
     *
     * @param jsonObject jsonObject
     * @param filePath   文件路径
     */
    private void writeJsonToFile(HolidayResponse jsonObject, String filePath) {
        try {
            // Create directory if it doesn't exist
            Path parentDirectory = FileSystems.getDefault().getPath(filePath).getParent();
            if (parentDirectory != null && !Files.exists(parentDirectory)) {
                Files.createDirectories(parentDirectory);
            }

            try (FileOutputStream fos = new FileOutputStream(filePath)) {
                ObjectMapper objectMapper = new ObjectMapper();
                objectMapper.writeValue(fos, jsonObject);
                log.info("节假日文件写入成功 : {} , {}", filePath, jsonObject);
            } catch (Exception e) {
                log.error("节假日文件写入失败 : {} , {}", filePath, e.getMessage());
            }
        } catch (IOException e) {
            log.error("Error creating directory structure: {}", e.getMessage());
        }
    }

    /**
     * 从文件中读取节假日
     *
     * @param filePath 文件路径
     * @return 节假日
     */
    private HolidayResponse readJsonFromFile(String filePath) {
        try {
            // Create directory if it doesn't exist
            Path parentDirectory = FileSystems.getDefault().getPath(filePath).getParent();
            if (parentDirectory != null && !Files.exists(parentDirectory)) {
                Files.createDirectories(parentDirectory);
            }

            try (FileInputStream fis = new FileInputStream(filePath)) {
                ObjectMapper objectMapper = new ObjectMapper();
                return objectMapper.readValue(fis, HolidayResponse.class);
            } catch (Exception e) {
                log.error("节假日文件读取失败 : {} , {}", filePath, e.getMessage());
            }
        } catch (IOException e) {
            log.error("Error creating directory structure: {}", e.getMessage());
        }
        return null;
    }
}

HttpConnector.java (不重要,只是发起请求,可替换)

java 复制代码
package com.xxx.app.common.utils.http;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;

/**
 * @author Jing
 * @desc http 连接工具类
 * https json post 请求用 doSSlPostRsp接口
 */
@Setter
@Slf4j
@Service
public class HttpConnector {

    @Resource
    private HttpClient httpClient;

    /**
     * 转拼接参数
     *
     * @param url    接口地址
     * @param params 参数
     * @return 拼接后的参数
     */
    public String addUrlParam(String url, Map<String, String> params) {
        if (MapUtils.isNotEmpty(params)) {
            StringBuilder sb = new StringBuilder(url);
            sb.append("?");
            for (String key : params.keySet()) {
                sb.append(key).append("=").append(params.get(key)).append("&");
            }
            url = sb.substring(0, sb.length() - 1);
        }
        return url;
    }

    /**
     * 发送带参数post请求 表单请求头
     *
     * @param url      接口地址
     * @param postJson 传参
     * @return 响应数据
     */
    public String doFormPost(String url, String postJson) throws IOException {
        HttpPost httpPost = new HttpPost(url);
        if (postJson != null) {
            httpPost.setHeader("Accept", "application/json");
            httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
            httpPost.setEntity(new StringEntity(postJson, Consts.UTF_8));
        }
        Map<String, String> headers = new HashMap<>();
        return doRequest(headers, httpPost);
    }

    /**
     * 发送带参数post请求 表单请求头
     *
     * @param url      接口地址
     * @param formData 传参
     * @return 响应数据
     */
    public String doFormPost(String url, Map<String, String> headers, String formData) throws IOException {
        HttpPost httpPost = new HttpPost(url);
        if (formData != null) {
            httpPost.setHeader("Accept", "application/json");
            httpPost.setHeader("Content-type", "application/x-www-form-urlencoded");
            httpPost.setEntity(new StringEntity(formData, Consts.UTF_8));
        }
        return doRequest(headers, httpPost);
    }

    /**
     * 发送带文件和请求头的post请求
     *
     * @param url      接口地址
     * @param headers  请求头
     * @param file     文件
     * @param fileName 文件名
     * @return 响应数据
     */
    public String doPost(String url, Map<String, String> headers, File file, String fileName) throws IOException {
        HttpPost httpPost = new HttpPost(url);
        if (file != null) {
            httpPost.setEntity(assemblyFileEntity(file, fileName));
        }
        return doRequest(headers, httpPost);
    }

    /**
     * 生成文件请求包
     *
     * @param file 文件
     * @return http 请求体
     */
    protected HttpEntity assemblyFileEntity(File file, String fileName) {
        MultipartEntityBuilder build = MultipartEntityBuilder.create();
        build.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
        build.addBinaryBody("file", file);
        build.addTextBody("filename", StringUtils.isBlank(fileName) ? file.getName() : fileName);
        HttpEntity entity = build.build();
        return entity;
    }

    /**
     * 发送请求
     *
     * @param headers     请求头
     * @param httpRequest request
     * @return 响应数据
     */
    private String doRequest(Map<String, String> headers, HttpRequestBase httpRequest) throws IOException {
        HttpRspBO rspBO = doRequestRsp(headers, httpRequest);
        return rspBO.getBodyStr();
    }


    /**
     * 发送request 请求
     *
     * @param headers
     * @param httpRequest 注意这个方法需要自己关闭
     *                    finally {
     *                    httpRequest.releaseConnection();
     *                    }
     * @return 带请求头的响应数据封装
     */
    private HttpRspBO doRequestRsp(Map<String, String> headers, HttpRequestBase httpRequest) throws IOException {
        if (headers != null) {
            for (String key : headers.keySet()) {
                httpRequest.addHeader(key, headers.get(key));
            }
        }
        try {
            HttpResponse response = httpClient.execute(httpRequest);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == HttpStatus.SC_OK) {
                HttpRspBO bo = new HttpRspBO();
                bo.setBodyStr(getEntity(response));
                HashMap<String, String> map = new HashMap<>();
                Header[] rspHeaders = response.getAllHeaders();
                int i = 0;
                while (i < rspHeaders.length) {
                    map.put(rspHeaders[i].getName(), rspHeaders[i].getValue());
                    i++;
                }
                bo.setHeaders(map);
                return bo;
            } else {
                String entity = getEntity(response);
                log.info("result msg " + entity);
                throw new FinMgwException("");
            }
        } finally {
            httpRequest.releaseConnection();
        }
    }


    /**
     * 解析返回请求体
     *
     * @param response 返回参数
     * @return 字符串类型的请求体
     */
    public String getEntity(HttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        if (entity == null) {
            throw new FinMgwException(ResultCodeEnum.HTTP_EXECUTE_EX.getCode(),
                    ResultCodeEnum.HTTP_EXECUTE_EX.getDesc() + ", http response entity is null.");
        }
        String result;
        // 去掉首尾的 ""
        result = EntityUtils.toString(entity, Consts.UTF_8);
        String delStr = "\"";
        if (result.indexOf(delStr) == 0) {
            result = result.substring(1);
        }
        if (result.lastIndexOf(delStr) == result.length() - 1) {
            result = result.substring(0, result.length() - 1);
        }
        return result;
    }

    /**
     * 将参数转换为form data传参
     *
     * @param param map集合
     * @return form data
     */
    public String fromData(Map<String, String> param) {
        StringJoiner joiner = new StringJoiner("&");
        for (Map.Entry<String, String> map : param.entrySet()) {
            String data = String.format("%s=%s", map.getKey(), map.getValue());
            joiner.add(data);
        }
        return joiner.toString();
    }
}
使用示例:

application.yml 配置

复制代码
# 天行api
tianapi:
  # API密钥
  key: xxxxxxxxxxxxxxxxxxxxxxxxxxx

demo 代码:

java 复制代码
@Slf4j
@Service
public class Demo {

    @Resource
    private TianApiHolidayService tianApiHolidayService;

    public void get(){
      LocalDateTime publishTime = LocalDateTime.now()
      // 计算五个工作日后的日期
      LocalDateTime deadlineTime = tianApiHolidayService.jumpWorkDay(publishTime, 5);
    }

}

理论上可以扩展很多方法,比如判断当前是否工作日/节假日,减去x个工作日之类的。但不太想写了,暂时用不着。

四、相关依赖

天行API申请:

节假日API接口 - 天行数据TianAPI

代码依赖:

Java 版本 = 1.8

Maven 版本 = 3.9.2

涉及maven依赖版本:

XML 复制代码
        <!--SpringBoot 启动插件-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.3.4.RELEASE</version>
            <exclusions>
                <exclusion>
                    <artifactId>log4j-api</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.4.RELEASE<</version>
        </dependency>

        <!--简化开发的jar 包-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <version>1.18.16</version>
        </dependency>

        <!--日期转化-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.10</version>
        </dependency>

        <!--json解析-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.10.2</version>
        </dependency>

        <!--json解析-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.9</version>
        </dependency>

五、补充

附带上2024年的节假日数据,如果只是要判断2024年的,那直接把文件放在项目目录/.holiday 目录下即可,不需要再申请天行的API接口权限。

或者拿这个去调试代码也可以,但需要保证日期范围在2024年内,否则会自动调用天行API接口获取其他年份的数据。

文件名:2024_holiday.json

复制代码
{
  "code": 200,
  "msg": "success",
  "result": {
    "update": true,
    "list": [
      {
        "holiday": "1月1号",
        "name": "元旦节",
        "vacation": "2023-12-30|2023-12-31|2024-01-01",
        "remark": "",
        "wage": "2024-01-01",
        "start": 0,
        "now": 0,
        "end": 2,
        "tip": "1月1日放假,与周末连休,共三天。",
        "rest": "2023年12月28日至12月29日请假2天,与周末连休可拼5天小长假。"
      },
      {
        "holiday": "2月10号",
        "name": "春节",
        "vacation": "2024-02-10|2024-02-11|2024-02-12|2024-02-13|2024-02-14|2024-02-15|2024-02-16|2024-02-17",
        "remark": "2024-02-04|2024-02-18",
        "wage": "2024-02-10|2024-02-11|2024-02-12",
        "start": 0,
        "now": 0,
        "end": 7,
        "tip": "2月10日至17日放假调休,共8天。2月4日(星期日)、2月18日(星期日)上班。鼓励各单位结合带薪年休假等制度落实,安排职工在除夕(2月9日)休息。",
        "rest": "2月8日至2月9日请假2天,与春节连休可拼10天长假。"
      },
      {
        "holiday": "4月4号",
        "name": "清明节",
        "vacation": "2024-04-04|2024-04-05|2024-04-06",
        "remark": "2024-04-07",
        "wage": "2024-04-04",
        "start": 0,
        "now": 0,
        "end": 2,
        "tip": "4月4日至6日放假调休,共3天。4月7日(星期日)上班。",
        "rest": "4月3日和4月7日请假2天,与清明节连休可拼5天小长假。"
      },
      {
        "holiday": "5月1号",
        "name": "劳动节",
        "vacation": "2024-05-01|2024-05-02|2024-05-03|2024-05-04|2024-05-05",
        "remark": "2024-04-28|2024-05-11",
        "wage": "2024-05-01",
        "start": 0,
        "now": 0,
        "end": 4,
        "tip": "5月1日至5日放假调休,共5天。4月28日(星期日)、5月11日(星期六)上班。",
        "rest": "4月28日至4月30日请假3天,周六与劳动节连休可拼9天长假。"
      },
      {
        "holiday": "6月10号",
        "name": "端午节",
        "vacation": "2024-06-08|2024-06-09|2024-06-10",
        "remark": "",
        "wage": "2024-06-10",
        "start": 0,
        "now": 0,
        "end": 2,
        "tip": "6月10日放假,与周末连休,共3天。",
        "rest": "6月6日至6月7日请假2天,与端午节连休可拼5天小长假。"
      },
      {
        "holiday": "9月15号",
        "name": "中秋节",
        "vacation": "2024-09-15|2024-09-16|2024-09-17",
        "remark": "2024-09-14",
        "wage": "2024-09-17",
        "start": 0,
        "now": 0,
        "end": 2,
        "tip": "9月15日至17日放假调休,共3天。9月14日(星期六)上班。",
        "rest": "9月13日至9月14日请假2天,与周日连休可拼5天小长假。"
      },
      {
        "holiday": "10月1号",
        "name": "国庆节",
        "vacation": "2024-10-01|2024-10-02|2024-10-03|2024-10-04|2024-10-05|2024-10-06|2024-10-07",
        "remark": "2024-09-29|2024-10-12",
        "wage": "2024-10-01|2024-10-02|2024-10-03",
        "start": 0,
        "now": 0,
        "end": 6,
        "tip": "10月1日至7日放假调休,共7天。9月29日(星期日)、10月12日(星期六)上班。",
        "rest": "9月29日至9月30号请假2天,周六与国庆节连休可拼10天长假。"
      }
    ]
  }
}
相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔7 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说12 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json