算出截止到指定年份的所有天数中,那些是节假日,那些是工作日

本文介绍了一种用于银行金融系统中判断日期是工作日还是节假日的算法,通过递归循环计算截止到指定年份的所有天数中,哪些是节假日,哪些是工作日。

还是直接上代码吧,这个比较简单,主要是银行金融系统里面需要判断相应的日期是否为工作日还是节假日:

package com.sort;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;

class Test {
	static SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");

	public static void main(String args[]) {
		//获取当前时间
		Date date = new Date();
		//转化为String类型
		String strDate = sf.format(date);
		System.out.println(strDate);
		//计算当前日期是否为节假日,并利用递归循环算法,算出截止到指定年份的所有天数中,那些是节假日,那些是工作日
		String a = calculateDay(strDate);

	}
	
	//具体的算法
	public static String calculateDay(String strDate) {
		Date date2 = null;
		try {
			date2 = add(sf.parse(strDate), Calendar.DATE, 1);
		} catch (ParseException e) {
			System.out.println("计算失败");
		}
		Calendar cal = GregorianCalendar.getInstance(Locale.CHINESE);
		cal.setTime(date2);
		int i = cal.get(Calendar.DAY_OF_WEEK);
		String strDate2 = sf.format(date2);
		System.out.print(strDate2);
		if (i == 1 || i == 7) {
			System.out.print("/节假日");
		} else {
			System.out.print("/工作日");
		}
		System.out.println();
		int year = Integer.parseInt(strDate2.substring(0, 4));
		if (year <= 2015) {
			strDate = calculateDay(strDate2);// 递归算法
		}
		return strDate;
	}

	/**
	 * 日期计算:指定年/月/日的加法计算
	 * 
	 * @param date
	 *            - 处理日期
	 * @param field
	 *            - 指定的年/月/日
	 * @param amount
	 *            - 时间参数
	 * @return Date 所得日期
	 */
	private static Date add(Date date, int field, int amount) {
		if (date == null) {
			return null;
		}
		Calendar calendar = GregorianCalendar.getInstance(Locale.CHINESE);
		calendar.setTime(date);
		calendar.add(field, amount);

		return calendar.getTime();
	}

}


<think>我们已经在之前的回答中提供了一个解决方案,但为了更清晰地适应当前问题,我们将重新组织并确保符合MaxCompute的语法。需求:计算流程的实际工作日耗时(开始时间到结束时间之间的天数,排除周末和节假日)已知:-流程表(假设名为`process_table`),包含字段:`process_id`(流程ID),`start_date`(开始日期),`end_date`(结束日期)-节假日表(`holiday`),包含字段:`holiday_date`(日期),可能还有其他字段如日期类型(用于区分节假日和调休等,但根据问题,我们只需要日期即可)思路:1.生成开始日期到结束日期之间的所有日期序列。2.对每个日期判断是否为工作日(非周末且非节假日)。3.统计每个流程在工作日序列中的天数。注意:MaxCompute中生成日期序列的常用方法是使用`lateralviewposexplode(split(space(datediff(end_date,start_date)),''))`,这样可以生成0到天数差的整数序列。另外,MaxCompute的WEEKDAY函数返回的是星期几,其中0表示周一,1表示周二,...,5表示周六,6表示周日。如果节假日表中包含的日期都是需要排除的(即非工作日),那么我们可以直接使用。但是,有时候节假日表可能还包含调休的工作日(即本来是周末但需要上班),但问题中并未提及,所以我们先按照基本需求实现:排除周末和节假日。如果后续需要支持调休,则可以在holiday表中用日期类型字段来区分(例如:1表示节假日,2表示调休工作日),然后在判断时,如果是调休工作日,即使它是周末也要算作工作日。但当前问题没有要求,所以我们先实现基本功能。步骤:1.扩展流程表的每一行,生成从开始日期到结束日期的每一天。2.将生成的日期与节假日表左连接,如果连接上,则说明该日期是节假日。3.判断每个日期的星期几,如果是周六或周日,则排除;如果是节假日,也排除。4.按流程ID分组,计算工作日的数量。实现:假设流程表为`process_table`,节假日表为`holiday`。注意:日期字段的类型是日期类型(DATE)或字符串类型(STRING)?在MaxCompute中,我们通常使用日期类型,但如果存储为字符串,需要转换为日期类型。这里假设都是日期类型。如果开始日期和结束日期是字符串,可以使用`to_date`转换。我们使用以下SQL:```sqlWITHdate_seriesAS(SELECTp.process_id,p.start_date,p.end_date,--生成从0到日期差(包括)的整数序列,然后加上开始日期得到每一天date_add(p.start_date,t.pos)ASevery_dateFROMprocess_tablepLATERALVIEWposexplode(split(space(datediff(p.end_date,p.start_date)),''))tASpos,val--注意:space(n)生成n个空格,split后得到n+1个元素(因为空格分隔符有n个,分割出n+1个部分),但实际我们只需要0到n(包括)的整数,所以用pos从0到n),workday_flagAS(SELECTds.process_id,ds.every_date,--判断是否为工作日:不是周六、周日,且不是节假日CASEWHENWEEKDAY(ds.every_date)IN(5,6)THEN0--5是周六,6是周日WHENh.holiday_dateISNOTNULLTHEN0--节假日ELSE1ENDASis_workdayFROMdate_seriesdsLEFTJOINholidayhONds.every_date=h.holiday_date--关联节假日表)SELECTprocess_id,COUNT(*)ASwork_days--统计工作日的数量FROMworkday_flagWHEREis_workday=1GROUPBYprocess_id;```但是,请注意:`datediff`函数在MaxCompute中计算的是两个日期之间相差的天数(结束日期-开始日期),然后我们用`space(datediff(end_date,start_date))`生成一个字符串,空格数量等于天数差,然后用`split`分割,会得到`datediff(end_date,start_date)+1`个空字符串(因为n个空格分割出n+1部分)。然后`posexplode`会生成两列:位置(从0开始)和值(空字符串)。我们使用位置`pos`,然后`date_add(start_date,pos)`就得到了从`start_date`到`end_date`的每一天(包括开始日期和结束日期)。这个方法是MaxCompute中生成连续日期的常用技巧。但是,如果日期跨度非常大(比如几年),可能会因为生成的数据量过大而影响性能。因此,在实际应用中,如果流程的日期跨度普遍较大,可以考虑使用预先生成的日历表(包含所有日期和是否工作日的标记)来优化。优化方案(使用日历表):我们可以预先创建一个日历表`dim_calendar`,包含字段`calendar_date`(日期)和`is_workday`(是否工作日,1表示工作日,0表示非工作日)。这样,我们只需要将流程表与日历表关联,过滤出在开始日期和结束日期之间的日期,并且`is_workday=1`,然后按流程ID分组计数。创建日历表的过程可以一次性完成,然后每天更新(如果有新的节假日安排)。这样在查询时就不需要动态生成日期序列和计算工作日,性能会更好。优化后的SQL:```sqlSELECTp.process_id,COUNT(*)ASwork_daysFROMprocess_tablepJOINdim_calendarcONc.calendar_dateBETWEENp.start_dateANDp.end_dateWHEREc.is_workday=1GROUPBYp.process_id;```但是,这需要提前维护好`dim_calendar`表。如果还没有日历表,我们可以通过以下步骤创建:步骤1:生成一个足够覆盖所有流程日期的日期序列(例如从1970-01-01到2100-12-31)。步骤2:标记每一天是否为工作日(先根据星期排除周末,再根据节假日表排除节假日,并考虑调休等)。由于问题要求使用现有的holiday表,我们可以先创建日历表,然后每次计算时使用。但考虑到问题要求直接使用holiday表,我们提供第一种动态生成的方法。但是,如果用户希望使用日历表的方法,我们可以提供创建日历表的SQL。不过,根据问题,我们只需要一个SQL,所以提供第一种动态方法即可。注意:第一种方法在日期跨度大时可能会比较慢,所以如果流程数量多且跨度大,建议使用日历表。回到问题,我们提供第一种动态生成日期序列的方法。另外,如果开始日期和结束日期是同一天,那么`datediff`结果为0,生成一个日期(即开始日期)。这是符合预期的。测试用例:流程:开始日期2023-10-01(周日),结束日期2023-10-07(周六)节假日:2023-10-02,2023-10-03,2023-10-04(假设这三天是节假日)那么实际工作日应该是:2023-10-05(周四)、2023-10-06(周五)->共2天。根据上面的SQL,生成日期序列:2023-10-01到2023-10-07(共7天)2023-10-01:周日->排除2023-10-02:节假日->排除2023-10-03:节假日->排除2023-10-04:节假日->排除2023-10-05:周四->工作日2023-10-06:周五->工作日2023-10-07:周六->排除所以计数为2,正确。因此,我们使用第一种方法即可。但是,如果节假日表中包含调休工作日(即周末上班),那么我们需要调整。由于问题没有提到,我们暂不考虑。如果需要,我们可以扩展holiday表,增加一个字段`is_off_day`(是否放假,1表示放假,0表示上班)。然后我们的判断逻辑改为:CASE--如果该日期在holiday表中,且标记为放假(is_off_day=1),则排除--如果该日期在holiday表中,且标记为调休上班(is_off_day=0),则算工作日--如果不在holiday表中,则按正常星期判断:周一到周五为工作日,周末为非工作日END由于问题没有要求,我们按基本需求实现。最后,给出完整的SQL(基本需求):</think>### 在 MaxCompute 中计算实际工作日的完整解决方案 #### 核心思路 1. **生成日期序列**:基于流程的开始和结束日期,生成连续日期列表 2. **标记工作日**: - 排除周末(周六、周日) - 排除节假日(通过 `holiday` 表判断) 3. **统计有效工作日**:对标记为工作日的日期进行计数 #### 完整 SQL 实现 ```sql WITH -- 步骤1: 生成日期序列 date_series AS ( SELECT p.process_id, p.start_date, p.end_date, date_add(p.start_date, pos) AS work_date FROM process_table p LATERAL VIEW posexplode( split(space(datediff(end_date, start_date)), ' ') ) t AS pos, _ ), -- 步骤2: 标记工作日(排除周末和节假日) workday_flag AS ( SELECT ds.process_id, ds.work_date, CASE WHEN WEEKDAY(ds.work_date) IN (5, 6) THEN 0 -- 排除周末(5=周六,6=周日) WHEN h.holiday_date IS NOT NULL THEN 0 -- 排除节假日 ELSE 1 END AS is_workday FROM date_series ds LEFT JOIN holiday h ON ds.work_date = h.holiday_date ) -- 步骤3: 计算各流程实际工作日 SELECT process_id, start_date, end_date, COUNT(CASE WHEN is_workday = 1 THEN 1 END) AS work_days FROM workday_flag GROUP BY process_id, start_date, end_date; ``` #### 关键组件说明 1. **日期序列生成**: - `datediff(end_date, start_date)`:计算日期跨度 - `space(n)` + `split()`:生成 n+1 个空字符串 - `posexplode()`:将序列展开为位置索引(从 0 开始) - `date_add(start_date, pos)`:生成连续日期序列 2. **工作日判断逻辑**: - `WEEKDAY(date)`:返回日期对应的星期索引(0=周一,6=周日) - `LEFT JOIN holiday`:关联节假日表,排除法定假日 - 双重过滤:先排除周末,再排除节假日 3. **节假日表结构要求**: ```sql CREATE TABLE holiday ( holiday_date DATE COMMENT '节假日日期', holiday_type STRING COMMENT '节假日类型' -- 可选字段 ); ``` #### 示例测试 **流程表数据 (process_table)**: | process_id | start_date | end_date | |------------|------------|------------| | P001 | 2023-10-01 | 2023-10-07 | **节假日表数据 (holiday)**: | holiday_date | holiday_type | |--------------|--------------| | 2023-10-02 | 法定假日 | | 2023-10-03 | 法定假日 | | 2023-10-04 | 法定假日 | **执行结果**: | process_id | start_date | end_date | work_days | |------------|------------|------------|-----------| | P001 | 2023-10-01 | 2023-10-07 | 2 | > 解释:实际工作日为 10月5日(周四)和 10月6日(周五),排除国庆假期和周末[^1]。 #### 性能优化建议 1. **使用日历维度表**:预生成包含日期、星期、节假日标记的日历表 ```sql CREATE TABLE dim_calendar AS SELECT calendar_date, WEEKDAY(calendar_date) AS weekday, CASE WHEN h.holiday_date IS NOT NULL THEN 1 ELSE 0 END AS is_holiday FROM (...日期范围...) d LEFT JOIN holiday h ON d.calendar_date = h.holiday_date; ``` 2. **优化后查询**: ```sql SELECT p.process_id, COUNT(*) AS work_days FROM process_table p JOIN dim_calendar c ON c.calendar_date BETWEEN p.start_date AND p.end_date WHERE c.weekday NOT IN (5, 6) -- 排除周末 AND c.is_holiday = 0 -- 排除节假日 GROUP BY p.process_id; ``` #### 扩展场景处理 1. **包含调休工作日**: ```sql CASE WHEN o.workday_date IS NOT NULL THEN 1 -- 调休上班日 WHEN WEEKDAY(ds.work_date) IN (5,6) THEN 0 WHEN h.holiday_date IS NOT NULL THEN 0 ELSE 1 END ``` 需额外维护 `workday_override` 表记录调休工作日[^2]。 2. **处理日期边界**: - 包含开始/结束日期:当前方案已包含 - 排除结束日期:使用 `date_add(start_date, pos) < end_date`
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值