相信在座的各位都有在公司的审批系统上请过假,因为我国的四大发明之一:调休,所以这个请假时间的计算并不是能够直接算出来的,正巧这几天写了这个代码,在此想与各位分享一下。
这几日正在备考软件设计师,稍稍学习了一下设计模式,因此我左思右想,我认为应该是可以用到一个设计模式的,于是我满怀壮志,打下第一句代码"abstract",vscode开始提示仅TypeScript支持,无奈,只能老老实实写了。
首先设计这个请假时间,我们要想想为什么需要专门来写这个代码,啊!因为调休!调休就造成了以下四种场景:
- 工作日需要上班(正常情况)
- 工作日不需要上班(那就是放假)
- 周六日不需要上班(正常情况,也不能排除有的勇士是996或007,该代码也能兼容好吧,无惧996007)
- 周六日需要上班(常规情况下我们称为调休,自己申请加班不算哈)
正因为调休,我们不能直接硬生生把星期六日排除掉,因此我本人第一个不支持调休!
那么要解决这四种情况,身为小腊鸡的我只能想出来if else了,如果有更好的方法,希望大家可以评论告诉我,那么if else也得写的高端,肯定不能一个个来对比,那就会写的很长,那么废话不多说虽然已经说了很多,我们继续往下说。
我先声明两个数组,一个是special_workingDate,一个是special_holidayDate,为什么special,因为调休,它才会变得special,举个例子
//中秋假期与国庆假期
const special_holidayDate= ["2023-09-29","2023-09-30","2023-10-01","2023-10-02","2023-10-03","2023-10-04","2023-10-05","2023-10-06"]
//国庆后的调休
const special_workingDate = ["2023-10-07","2023-10-08"]
然后,我们按照正常的逻辑,写个if else
if(){//今天上班
}else{//今天周末
}
这里考虑到有些喜欢工作的勇士一周需要上多一天班,我们可以这么写这个上班的if
workingDay = [1,2,3,4,5,6]
if(workingDay.includes( startTime.getDay() ) ){
//如果我传入的日期,在自己规定的workingDay里,那今天就是得上班
}else{
//如果我传入的日期,不在workingDay里,那今天就休息
}
有了这个逻辑,我们再结合上面的调休,再深入的写个if else
//中秋假期与国庆假期
const special_holidayDate= ["2023-09-29","2023-09-30","2023-10-01","2023-10-02","2023-10-03","2023-10-04","2023-10-05","2023-10-06"]
//国庆后的调休
const special_workingDate = ["2023-10-07","2023-10-08"]
workingDay = [1,2,3,4,5,6]
oneDayMS = 86400000//一天的毫秒数
function fn(){
do{
if(workingDay.includes( currentTime.getDay() ) ){
//如果我传入的日期,在自己规定的workingDay里,那今天就是得上班
if(calculate_WorkingDay_to_Holiday(currentTime)){
//但如果今天是放假日
currentTime = currentTime + oneDayMS//currentTime实际上我是用时间戳写的
continue//因为我这写了个循环,平时请假肯定不止请一天嘛
//如果是放假,就跳过这天,进入下一个循环
}
}else{
//如果我传入的日期,不在workingDay里,那今天就休息
if( !this.calculate_Holiday_to_WorkingDay( currentTime) ){
//并且这个休息日,不是调休日,那就直接躺平好吧,休息!
currentTime = currentTime + oneDayMS//currentTime实际上我是用时间戳写的
continue//因为我这写了个循环,平时请假肯定不止请一天嘛
}
}
//主要逻辑已经写完,接下来就是自由发挥时间
//统计请假多少天,多少小时,多少分钟,多少秒,自行发挥
}while(currentTime < endTime)
}
//是否在工作日放假
calculate_WorkingDay_to_Holiday(currentDate){
//法定假日是否包含当前日期
return special_holidayDate.includes( formatTime(currentDate) )
}
//是否在节假日工作
calculate_Holiday_to_WorkingDay(currentDate){
//法定工作日里是否包含当前日期
return special_workingDate.includes( formatTime(currentDate) )
}
formatTime(date,j='-'){
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
function formatNumber(n){
n = n.toString()
return n[1] ? n : '0' + n
}
return [year, month, day].map(formatNumber).join(j)
}
大体的思路就是这样,此外我还另设了一些参数,可供各位勇士在各种上班日上下班时间,能够准确的计算出准确的数据,我就直接贴代码了,讲太多了
class AskForLeave{
//工作日,星期一二三四五
workingDay = [1,2,3,4,5]
//上午上下班时间
workingTime_morning = '09:00'
colsingTime_morning = '12:00'
//下午上下班时间
workingTime_afternoon = '13:45'
closingTime_afternoon = '17:45'
oneDayWorkTime
oneDayMS = 86400000
//特殊上班日
special_workingDate
//特殊放假日
special_holidayDate
timeStamp = new Date()
days
minutes
hours
totalMinutesCount = 0//总分钟数
constructor(opt){
this.initData(opt)
}
//初始化数据
initData(opt){
this.workingDay = opt.workingDay == undefined ? this.workingDay : opt.workingDay
this.workingTime_morning = opt.workingTime_morning == undefined ? this.workingTime_morning : opt.workingTime_morning
this.colsingTime_morning = opt.colsingTime_morning == undefined ? this.colsingTime_morning : opt.colsingTime_morning
this.workingTime_afternoon = opt.workingTime_afternoon == undefined ? this.workingTime_afternoon : opt.workingTime_afternoon
this.closingTime_afternoon = opt.closingTime_afternoon == undefined ? this.closingTime_afternoon : opt.closingTime_afternoon
this.special_workingDate = opt.special_workingDate
this.special_holidayDate = opt.special_holidayDate
this.oneDayWorkTime = ( Number(this.colsingTime_morning.split(':')[0]) - Number(this.workingTime_morning.split(':')[0]) ) + ( Number(this.closingTime_afternoon.split(':')[0]) - Number(this.workingTime_afternoon.split(':')[0]) )
}
formatTime(date,j='-'){
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
function formatNumber(n){
n = n.toString()
return n[1] ? n : '0' + n
}
return [year, month, day].map(formatNumber).join(j)
}
//是否在工作日放假
calculate_WorkingDay_to_Holiday(currentDate){
//法定假日是否包含当前日期
return this.special_holidayDate.includes( this.formatTime(currentDate) )
}
//是否在节假日工作
calculate_Holiday_to_WorkingDay(currentDate){
//法定工作日里是否包含当前日期
return this.special_workingDate.includes( this.formatTime(currentDate) )
}
getDateObj(val){
this.timeStamp.setTime(val)
return this.timeStamp
}
DateDiffNoWeekDay(startTime, endTime) {
if (startTime >= endTime) return 0;
startTime = startTime.getTime()
endTime = endTime.getTime()
do {
如果是上班日
// console.log(`今天是${this.formatTime(this.getDateObj(startTime))},星期${this.getDateObj(startTime).getDay()}`)
if(this.workingDay.includes(this.getDateObj(startTime).getDay())){
//且是法定调休假日
if( this.calculate_WorkingDay_to_Holiday(this.getDateObj(startTime)) ){
// console.log('今天是在工作日的放假日')
startTime = startTime + this.oneDayMS
startTime = this.getDateObj(startTime).setHours(Number(this.workingTime_morning.split(':')[0]))
startTime = this.getDateObj(startTime).setMinutes(Number(this.workingTime_morning.split(':')[1]))
continue
}
}else{//如果是周六日
//且不是在周六日调休
if( !this.calculate_Holiday_to_WorkingDay(this.getDateObj(startTime)) ){
//则认定为是一个普通周六日,直接跳过,初始化为早晨9点
// console.log('今天是个普通的周六日')
startTime = startTime + this.oneDayMS
startTime = this.getDateObj(startTime).setHours(Number(this.workingTime_morning.split(':')[0]))
startTime = this.getDateObj(startTime).setMinutes(Number(this.workingTime_morning.split(':')[1]))
continue
}
}
// console.log('今天必须上班')
//一切不需要上班的日期在上面均被跳过,接下来开始算需要上班的工时
let tempMinutes = this.getDateObj(startTime).getHours() * 60 + this.getDateObj(startTime).getMinutes();
//上午9点到12点半,算工时
let tempMorningWorkingTime = Number(this.workingTime_morning.split(':')[0]) * 60 + Number(this.workingTime_morning.split(':')[1])
let tempMorningClosingTime = Number(this.colsingTime_morning.split(':')[0]) * 60 + Number(this.colsingTime_morning.split(':')[1])
let tempAfternoonWorkingTime = Number(this.workingTime_afternoon.split(':')[0]) * 60 + Number(this.workingTime_afternoon.split(':')[1])
let tempAfternoonClosingTime = Number(this.closingTime_afternoon.split(':')[0]) * 60 + Number(this.closingTime_afternoon.split(':')[1])
if (tempMinutes >= tempMorningWorkingTime && tempMinutes < tempMorningClosingTime ) {
this.totalMinutesCount += 1;
}
if(tempMinutes >= tempMorningClosingTime && tempMinutes < tempAfternoonWorkingTime ){
let tempObj = this.getDateObj(startTime)
let currentTimeExcludeHours = new Date(`${tempObj.getFullYear()}-${tempObj.getMonth() + 1 > 10 ? tempObj.getMonth() + 1 : '0'+ (tempObj.getMonth() + 1) }-${tempObj.getDate() > 10 ? tempObj.getDate() : '0' + tempObj.getDate()} ${Number(this.workingTime_afternoon.split(':')[0])}:${Number(this.workingTime_afternoon.split(':')[1])}:00`)
startTime = currentTimeExcludeHours.getTime()
continue
}
//下午13:45到17:45,算工时
if (tempMinutes >= tempAfternoonWorkingTime && tempMinutes < tempAfternoonClosingTime ) {
this.totalMinutesCount += 1;
}
startTime = startTime + 60 * 1000//每次增加一分钟
} while ( startTime < endTime)
this.days = Math.floor(this.totalMinutesCount / (this.oneDayWorkTime * 60))
let hoursAndMinutes = this.totalMinutesCount % (this.oneDayWorkTime * 60)
this.hours = Math.floor(hoursAndMinutes / 60)
this.minutes = Math.floor(hoursAndMinutes % 60)
let result = {
totalMinutes:this.totalMinutesCount,
days:this.days,
hours:this.hours,
minutes:this.minutes
}
return result;
}
}
//调用方法如下
const askForLeave = new AskForLeave({
special_workingDate: [],//法定调休日
special_holidayDate: []//法定放假日
})
//这里我就不传了,数据上面有
//调用内部计算请假方法
let res = askForLeave.DateDiffNoWeekDay(new Date("2023-9-25 09:00"), new Date("2023-10-10 17:30"))
//输出结果
console.log(res)
如果没设置调休日放假日,结果是这样的
如果设置了,结果是这样的
我封装成了class,大家可以自行配置一些参数
- 如上几天的班,这里按星期一二三四五六七来配置
- 上下班时间
这些配置就能基本满足所有公司的请假时间计算了,我的while循环是基于时间戳的分钟计算的,因此每执行一次都会增加一分钟,直到与请假截止时间重合。
可以看到我频繁的调用了一个this.getDateObj()方法,因为while循环里用的是时间戳,时间戳并不是一个标准的时间对象,那么他就不具备时间对象的一些方法,getDate(),getFullYear(),getMonth(),getDay(),getMinutes(),getTime(),setHours(),setMinutes()等等,这里就不一一列举了(虽然已经列举了基本上会用到的),在看到这个方法里的内容
timeStamp = new Date()
getDateObj(val){
this.timeStamp.setTime(val)
return this.timeStamp
}
可以看到timeStamp实际上是一个时间对象,那么他就会具备这些方法,他会根据传入的时间戳参数,重新设定时间对象的值,然后返还时间对象,就可以使用对应时间戳的一些方法了,那么为什么不直接new Date()。
这样不就简单一点。//过渡句
这里就要首尾呼应一下了,这几天一直在学设计模式,看到一个享元模式,因为一个对象,他的属性,作用基本相同,并且我们需要new大量的这种对象,那既然它属性方法一致,是不是可以共享这个对象呢,借鉴了这个思想,我决定写下这个方法,如果这里不适用,欢迎大家拍醒我!