java基于交易日表计算自然日、工作日、交易日

 一、日期概念

  • 自然日:一年365天都属于自然日
  • 工作日:简单理解为上班就是工作日,不上班就是非工作日
  • 交易日:证券、期货交易的日期

 二、交易日表结构

CREATE TABLE "BUS_DATE" 
   (	"ID" VARCHAR2(32) NOT NULL, 
	"SK_DATE" NUMBER(8,0) NOT NULL, 
	"BK_DATE" DATE NOT NULL, 
	"GRE_DAY" VARCHAR2(10) NOT NULL, 
	"GRE_WEEK" VARCHAR2(10) NOT NULL, 
	"GRE_MONTH" VARCHAR2(10) NOT NULL, 
	"GRE_QUARTER" VARCHAR2(10) NOT NULL, 
	"GRE_YEAR" VARCHAR2(10) NOT NULL, 
	"SEMI_YEAR" VARCHAR2(10) NOT NULL, 
	"IS_WORK_DAY" CHAR(1) NOT NULL, 
	"IS_YHJ_DAY" CHAR(1) NOT NULL, 
	"CREATE_TIME" DATE NOT NULL, 
	"UPDATE_TIME" DATE NOT NULL, 
	"CREATE_USER" VARCHAR2(20), 
	"UPDATE_USER" VARCHAR2(20), 
	PRIMARY KEY ("ID")
   );

COMMENT ON TABLE BUS_DATE IS '交易日表';
COMMENT ON COLUMN BUS_DATE.ID IS '主键';
COMMENT ON COLUMN BUS_DATE.SK_DATE IS '日期';
COMMENT ON COLUMN BUS_DATE.BK_DATE IS '日期date类型';
COMMENT ON COLUMN BUS_DATE.GRE_DAY IS '公历日';
COMMENT ON COLUMN BUS_DATE.GRE_WEEK IS '公历周';
COMMENT ON COLUMN BUS_DATE.GRE_MONTH IS '公历月';
COMMENT ON COLUMN BUS_DATE.GRE_QUARTER IS '公历季';
COMMENT ON COLUMN BUS_DATE.GRE_YEAR IS '公历年';
COMMENT ON COLUMN BUS_DATE.SEMI_YEAR IS '半年度';
COMMENT ON COLUMN BUS_DATE.IS_WORK_DAY IS '是否交易日';
COMMENT ON COLUMN BUS_DATE.IS_YHJ_DAY IS '是否工作日/银行间交易日';
COMMENT ON COLUMN BUS_DATE.CREATE_TIME IS '创建时间';
COMMENT ON COLUMN BUS_DATE.UPDATE_TIME IS '修改时间';
COMMENT ON COLUMN BUS_DATE.CREATE_USER IS '创建人';
COMMENT ON COLUMN BUS_DATE.UPDATE_USER IS '修改人';

数据预览:

 此交易日表需要预先插入下一年的数据,以防计算日期超出范围。

三、代码

1.枚举类

IEnum.java

java枚举类封装一个基础枚举类-CSDN博客

 DimTypeEnum.java

package com.zhou.pub.dim.common;

import com.zhou.util.enums.IEnum;
import lombok.Getter;

/**
 * 交易日表计算维度枚举
 * @author lang.zhou
 * @since 2024/5/24 14:02
 */
public enum DimTypeEnum implements IEnum<String> {

    DAY("gre_day","日"),
    WEEK("gre_week","周"),
    MONTH("gre_month","月"),
    QUARTER("gre_quarter","季"),
    SEMIYEAR("semi_year","半年"),
    YEAR("gre_year","年");

    @Getter
    private final String value;
    @Getter
    private final String name;

    DimTypeEnum(String value, String name) {
        this.value = value;
        this.name = name;
    }
}

DateTypeEnum.java

package com.zhou.pub.dim.common;


import com.zhou.util.enums.IEnum;
import lombok.Getter;

/**
 * 日期类型枚举
 * @author lang.zhou
 * @since 2024/5/24 13:10
 */
public enum DateTypeEnum implements IEnum<Integer> {

    NATURE(0,"自然日"),
    TRADE(1,"交易日"),
    WORK(2,"工作日");
    @Getter
    private final Integer value;
    @Getter
    private final String name;

    DateTypeEnum(Integer value, String name) {
        this.value = value;
        this.name = name;
    }
}

2.实体类

DimTime.java

package com.zhou.pub.dim.dao;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.zhou.common.model.CommonBean;
import com.zhou.pub.dim.dto.DimTimeDTO;
import com.zhou.util.DateParse;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 交易日表
 * </p>
 */
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("pub_dim_time")
@ApiModel(value="DimTime对象", description="交易日表")
public class DimTime extends DimTimeDTO implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "自然日")
    @TableField("sk_date")
    private Integer skDate;

    @ApiModelProperty(value = "日期类型")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @TableField("bk_date")
    private Date bkDate;

    @ApiModelProperty(value = "公历日")
    @TableField("gre_day")
    private String greDay;

    @ApiModelProperty(value = "公历周")
    @TableField("gre_week")
    private String greWeek;

    @ApiModelProperty(value = "公历月")
    @TableField("gre_month")
    private String greMonth;

    @ApiModelProperty(value = "公历季")
    @TableField("gre_quarter")
    private String greQuarter;

    @ApiModelProperty(value = "公历年")
    @TableField("gre_year")
    private String greYear;

    @ApiModelProperty(value = "半年度")
    @TableField("semi_year")
    private String semiYear;

    @ApiModelProperty(value = "是否交易日")
    @TableField("is_work_day")
    @NotBlank(message = "交易日类型不能为空")
    private String isWorkDay;

    @ApiModelProperty(value = "是否银行间交易日")
    @TableField("is_yhj_day")
    @NotBlank(message = "银行间工作日类型不能为空")
    private String isYhjDay;


}

3.mapper层

DimTimeMapper.java

package com.zhou.pub.dim.mapper;

import com.zhou.pub.dim.dao.DimTime;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * <p>
 * 交易日表 Mapper 接口
 * </p>
 */
public interface DimTimeMapper extends BaseMapper<DimTime> {

}

 DimTimeMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhou.pub.dim.mapper.DimTimeMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.zhou.pub.dim.dao.DimTime">
        <id column="id" property="id" />
        <result column="sk_date" property="skDate" />
        <result column="bk_date" property="bkDate" />
        <result column="gre_day" property="greDay" />
        <result column="gre_week" property="greWeek" />
        <result column="gre_month" property="greMonth" />
        <result column="gre_quarter" property="greQuarter" />
        <result column="gre_year" property="greYear" />
        <result column="semi_year" property="semiYear" />
        <result column="is_work_day" property="isWorkDay" />
        <result column="is_yhj_day" property="isYhjDay" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
        <result column="create_user" property="createUser" />
        <result column="update_user" property="updateUser" />
    </resultMap>



</mapper>

 4.service层

DimTimeService.java

package com.zhou.pub.dim.service;

import com.zhou.pub.dim.dao.DimTime;
import com.baomidou.mybatisplus.extension.service.IService;

import java.util.List;

/**
 * <p>
 * 交易日表 服务类
 * </p>
 */
public interface DimTimeService extends IService<DimTime> {
    /**
     * 升序查询交易日表全部数据(此处为了方便,也可以传入一个日期范围,提高查询效率)
     */
    List<DimTime> listAll();

}

 DimTimeServiceImpl.java

package com.zhou.pub.dim.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhou.pub.dim.dao.DimTime;
import com.zhou.pub.dim.mapper.DimTimeMapper;
import com.zhou.pub.dim.service.DimTimeService;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * <p>
 * 交易日表 服务实现类
 * </p>
 */
@Service
public class DimTimeServiceImpl extends ServiceImpl<DimTimeMapper, DimTime> implements DimTimeService {

    

    @Override
    public List<DimTime> listAll() {
        LambdaQueryWrapper<DimTime> w = new LambdaQueryWrapper<>();
        w.orderByAsc(DimTime::getSkDate);
        return list(w);
    }

}

 5.封装工具类DimTimeUtil.java

package com.zhou.pub.dim.util;

import com.zhou.common.enums.RateTypeEnum;
import com.zhou.pub.dim.common.DimTypeEnum;
import com.zhou.pub.dim.common.DateTypeEnum;
import com.zhou.pub.dim.dao.DimTime;
import com.zhou.util.DateParse;
import com.zhou.util.enums.IEnum;
import lombok.extern.slf4j.Slf4j;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Objects;

/**
 * 交易日计算工具类
 * @author lang.zhou
 * @since 2023/9/15 15:39
 */
@SuppressWarnings("all")
@Slf4j
public class DimTimeUtil {

    /**
     * 找到日期所在索引
     */
    private static int findIndex(Integer date, List<DimTime> dimTimeList){
        if(date != null){
            for (int i = 0; i < dimTimeList.size(); i++) {
                DimTime dimTime = dimTimeList.get(i);
                if (Objects.equals(dimTime.getSkDate(), date)) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 找到日期DimTime对象
     */
    public static DimTime findDimTime(Integer date, List<DimTime> dimTimeList){
        if(date != null){
            for (DimTime dimTime : dimTimeList) {
                if (Objects.equals(dimTime.getSkDate(), date)) {
                    return dimTime;
                }
            }
        }
        return null;
    }

    /**
     * 判断两个日期是否属于相同日/周/月/季/半年/年
     */
    private static boolean isSameName(DimTime time1, DimTime time2, DimTypeEnum name){
        if(name == DimTypeEnum.DAY){
            return Objects.equals(time1.getGreDay(),time2.getGreDay());
        }else if(name == DimTypeEnum.WEEK){
            return Objects.equals(time1.getGreWeek(),time2.getGreWeek());
        }else if(name == DimTypeEnum.MONTH){
            return Objects.equals(time1.getGreMonth(),time2.getGreMonth());
        }else if(name == DimTypeEnum.QUARTER){
            return Objects.equals(time1.getGreQuarter(),time2.getGreQuarter());
        }else if(name == DimTypeEnum.SEMIYEAR){
            return Objects.equals(time1.getSemiYear(),time2.getSemiYear());
        }else if(name == DimTypeEnum.YEAR){
            return Objects.equals(time1.getGreYear(),time2.getGreYear());
        }
        return false;
    }

    /**
     * 得到date所在周/月/季/年/半年/的第一天
     */
    private static Integer getFirstDate(Integer date, DimTypeEnum name){
        String res = null;
        if(date != null){
            DateParse parse = DateParse.ct();
            if(name == DimTypeEnum.DAY){
                res = String.valueOf(date);
            }else if(name == DimTypeEnum.WEEK){
                res = parse.getFirstDayOfWeek(date,0);
            }else if(name == DimTypeEnum.MONTH){
                res = parse.getFirstDayOfMonth(date,0);
            }else if(name == DimTypeEnum.QUARTER){
                res = parse.getFirstDayOfQuarter(date,0);
            }else if(name == DimTypeEnum.SEMIYEAR){
                res = parse.getFirstDayOfHalfYear(date,0);
            }else if(name == DimTypeEnum.YEAR){
                res = parse.getFirstDayOfYear(date,0);
            }
        }
        return res == null ? null : Integer.valueOf(res);
    }

    /**
     * 判断日期是否是自然日/交易日/银行间交易日
     */
    private static boolean judgeWorkDay(DimTime dimTime, DateTypeEnum dimType){
        boolean res = dimType == null ? true : false;
        if(DateTypeEnum.NATURE == dimType){
            return true;
        }
        if(DateTypeEnum.TRADE == dimType && Objects.equals("Y",dimTime.getIsWorkDay())){
            res = true;
        }
        if(DateTypeEnum.WORK == dimType && Objects.equals("Y",dimTime.getIsYhjDay())){
            res = true;
        }
        return res;
    }


    /**
     * 得到date所在(年/月/周)第n个工作日
     * @param date          当前日期
     * @param n             正数或负数(n<0倒数第n个)
     * @param dimTimeList   交易日表数据(按sk_date升序)
     * @param dimType       交易日类型DimTypeEnum
     */
    public static String getWorkDayByMap(Integer date, int n, List<DimTime> dimTimeList, DimTypeEnum name, DateTypeEnum dimType){
        int realCnt = 0;
        Integer res = null;

        Integer firstDate = getFirstDate(date,name);
        DimTime dimTime = findDimTime(firstDate, dimTimeList);
        int index = findIndex(firstDate, dimTimeList);

        if(index > -1 && dimTime != null){
            if(n > 0){
                for (int i = index; i < dimTimeList.size(); i++) {
                    DimTime time = dimTimeList.get(i);
                    if(isSameName(dimTime,time,name)){
                        if(judgeWorkDay(time, dimType)){
                            realCnt ++;
                            res = time.getSkDate();
                        }
                    }
                    if(realCnt >= n){
                        break;
                    }
                }
            }else if(n < 0){
                n = -n;
                for (int i = index; i >= 0; i--) {
                    DimTime time = dimTimeList.get(i);
                    if(isSameName(dimTime,time,name)){
                        if(judgeWorkDay(time, dimType)){
                            realCnt ++;
                            res = time.getSkDate();
                        }
                    }
                    if(realCnt >= n){
                        break;
                    }
                }
            }else{
                res = date;
            }
        }
        return res == null ? null res.toString();
    }

    /**
     * 得到上周/上月/上季度/去年最后一个工作日
     * @param date          当前日期
     * @param dimTimeList   交易日表数据(按sk_date升序)
     * @param dimType       交易日类型DimTypeEnum
     */
    public static String getLastWorkDayByMap(Integer date, List<DimTime> dimTimeList, DimTypeEnum name, DateTypeEnum dimType){
        int realCnt = 0;
        String res = null;
        //得到本月/周/季/年的第一天
        Integer firstDate = getFirstDate(date,name);
        DimTime dimTime = findDimTime(firstDate, dimTimeList);

        if(dimTime != null){
            res = getLastWorkDay(dimTime.getSkDate(), 1, dimTimeList, dimType);
        }
        return res;
    }

    /**
     * 得到date上一个工作日
     * @param date          当前日期
     * @param n             前n个交易日(正数或负数)
     * @param dimTimeList   交易日表数据(按sk_date升序)
     * @param dimType       交易日类型DimTypeEnum
     */
    public static String getWorkDay(Integer date, int n, List<DimTime> dimTimeList, DateTypeEnum dimType){
        if(n < 0){
            return getNextWorkDay(date, -n, dimTimeList, dimType);
        }else{
            return getLastWorkDay(date, n, dimTimeList, dimType);
        }
    }

    /**
     * 得到date前n个交易日/银行间交易日
     * @param date          当前日期
     * @param n             前n个交易日(正数是前n个或负数是后n个)
     * @param dimTimeList   交易日表数据(按sk_date升序)
     * @param dimType       交易日类型DimTypeEnum
     */
    public static String getLastWorkDay(Integer date, int n, List<DimTime> dimTimeList, DateTypeEnum dimType){
        int realCnt = 0;
        Integer res = null;
        int index = findIndex(date, dimTimeList);
        if(index > -1 && (index -1) < dimTimeList.size() - 1){
            int d = n > 0 ? 1 : 0;
            for (int i = index - d; i >= 0; i--) {
                DimTime dimTime = dimTimeList.get(i);
                if(judgeWorkDay(dimTime, dimType)){
                    realCnt ++;
                    res = dimTime.getSkDate();
                }
                if(realCnt >= n){
                    break;
                }
            }

        }
        return res == null ? null res.toString();
    }

    /**
     * 得到date下一个工作日
     * @param date          当前日期
     * @param n             后n个交易日(正数或负数)
     * @param dimTimeList   交易日表数据(按sk_date升序)
     * @param dimType       交易日类型DimTypeEnum
     */
    public static String getNextWorkDay(Integer date, int n, List<DimTime> dimTimeList, DateTypeEnum dimType){
        int realCnt = 0;
        Integer res = null;
        int index = findIndex(date, dimTimeList);
        if(index > -1 && (index + 1) < dimTimeList.size() - 1){
            if(n > 0){
                for (int i = index + 1; i < dimTimeList.size(); i++) {
                    DimTime dimTime = dimTimeList.get(i);
                    if(judgeWorkDay(dimTime, dimType)){
                        realCnt ++;
                        res = dimTime.getSkDate();
                    }
                    if(realCnt >= n){
                        break;
                    }
                }
            }else{
                res = date;
            }

        }
        return res == null ? null res.toString();
    }

    /**
     * @param t         当前时间
     * @param rate3     小时
     * @param rate4     分钟
     */
    public static boolean isTimeEqual(String t,String rate3,String rate4){
        //String time=t.substring(0,5);
        int hour = Integer.parseInt(t.substring(0,2));
        int min = Integer.parseInt(t.substring(3,5))+60*hour;

        int ol = Integer.parseInt(rate4) + 60*Integer.parseInt(rate3);
        return ol == min;
    }

}

DateParse.java

通用自然日计算工具-CSDN博客

四、使用示例

场景1:计算日期为20250730之后的第5个交易日:

List<DimTimeDTO> list = dimTimeService.listAll();
String date = "20250730";
String day = DimTimeUtil.getNextWorkDay(date, 5, list, DateTypeEnum.TRADE);
System.out.println(day);

 场景2:计算日期为20250730之前的第5个交易日:

List<DimTimeDTO> list = dimTimeService.listAll();
String date = "20250730";
String day = DimTimeUtil.getLastWorkDay(date, 5, list, DateTypeEnum.TRADE);
System.out.println(day);

场景3:计算20250730所在月份的第10个工作日:

List<DimTimeDTO> list = dimTimeService.listAll();
String date = "20250730";
String day = DimTimeUtil.getWorkDayByMap(date, 10, list, DimTypeEnum.MONTH, DateTypeEnum.WORK);
System.out.println(day);

 场景4:计算20250730所在月份的倒数第10个工作日:

List<DimTimeDTO> list = dimTimeService.listAll();
String date = "20250730";
String day = DimTimeUtil.getWorkDayByMap(date, -10, list, DimTypeEnum.MONTH, DateTypeEnum.WORK);
System.out.println(day);

拓展场景:统计一个日期区间中有多少个交易日 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值