工作上遇到一個問題,想請問各位大大的如何解決
Q: 日期加總天數,要扣除假日(六&日),計算"早於" or "晚於"的日期。
後端會給加總天數(Days),還有要計算的日期(CDate)
- 如果加總天數為0,則日期要等於計算的日期
- 加總天數為整數(0,1,2...)
目前我的思維是,先不管假日的問題,
直接把計算的日期加上總天數,會得到一個日期區間
CDate = "2021-01-04";
Days = 9;
CDate + Days = "2021-01-13";
// 日期區間為 2021-01-04 ~ 2021-01-13
然後再去把這區間的日期,所有的日期都找出來
再從這些區間的日期裡,找尋禮拜六跟禮拜日
然後再把日期往後加減
但是這樣效能很差,區間很大的話,要計算很多
但是想不到有什麼其他方法可以解決
想請問各位大大,會用什麼其他邏輯思考去解這個問題
用moment.js吧
參考文件,你查查看 addWeekDays() 應該就是你需要的功能。
https://momentjs.com/docs/
一週用五天去算
編號:把週一當作 0,週二當作 1,週五當作 4
分別把 Days
「 對 5 取餘數」、「對 5 取整數除法」
然後再去計算處理
打包成函式
function addByWorkDay(startDate, nDays)
{
let arr=startDate.split('-').map(x=>parseInt(x,10));
let date=new Date(arr[0], arr[1]-1, arr[2]);
//計算需增加 nWeeks 週又 modDay 天
let modDay=nDays%5;
let nWeeks=(nDays-modDay)/5;
//計算要加幾天
let wkBegin=date.getDay();
let dayOffset=nWeeks*7+modDay+((wkBegin+6)%7+modDay>4?2:0);
date.setDate(date.getDate()+dayOffset);
return [date.getFullYear(), date.getMonth()+1, date.getDate()].map(x=>x<10?'0'+x:x).join('-');
}
測試
let start='2021-01-04';
for(let i=0;i<16;++i) {
console.log(`${start} + ${i} = ${addByWorkDay(start, i)}`);
}
我也給個有假日版本的
(也有考慮補班的狀況)
function WorkDay(data)
{
this.arr=[]; //儲存工作日,例如 ['2020-02-01', '2020-02-02', ...]
this.idxMap={}; //儲存「日期 => arr 的 idx」,例如 {'2020-02-01':0, '2020-02-02':1}
this.holidays=this._arrayToMap(data.holidays);
this.workDays=this._arrayToMap(data.workDays);
let ts=this._strToDate(data.begin).getTime();
let endTs=this._strToDate(data.end).getTime();
let d=new Date;
let tmp=[];
while(ts<=endTs) {
d.setTime(ts);
let str=this._dateToStr(d);
let wd=d.getDay();
if((0<wd && wd<6 || this.workDays[str]) && !this.holidays[str]) {
if(tmp.length) {
tmp.forEach(x=>{
this.idxMap[x]=this.arr.length;
});
tmp=[];
}
this.idxMap[str]=this.arr.length;
this.arr.push(str);
} else {
tmp.push(str);
}
ts+=86400000;
}
}
WorkDay.prototype._strToDate=function (str) {
let arr=str.split('-').map(x=>parseInt(x,10));
return new Date(arr[0], arr[1]-1, arr[2]);
}
WorkDay.prototype._dateToStr=function(date) {
return [date.getFullYear(), date.getMonth()+1, date.getDate()].map(x=>x<10?'0'+x:x).join('-');
}
WorkDay.prototype._arrayToMap=function(arr) {
return arr.reduce((o, x)=>{
o[x]=1;
return o;
}, {});
}
WorkDay.prototype.getOffset=function(start, nDayAdd)
{
let x=this.idxMap[start]; //第幾個工作日
return this.arr[x+nDayAdd]; //加上幾個工作日後回傳
}
測試
//data 是後端給的假日資料,涵蓋所有要查詢的區間
const data={
begin: '2021-02-01',
end: '2021-02-28',
//假日
holidays: ['2021-02-10', '2021-02-11', '2021-02-12', '2021-02-15', '2021-02-16'],
//補班日期
workDays: ['2021-02-20']
}
//依據假日資料建立 WorkDay 物件,時間複雜度跟 data.begin-data.end 成正比
const workDay=new WorkDay(data);
//之後就可以重複查詢,時間複雜度為 const
console.log(workDay.getOffset('2021-02-07', 7));
console.log(workDay.getOffset('2021-02-01', 10));
另一個版本供參考
function addWorkDays(startDate, days) {
if(isNaN(days)) {
console.log("Value provided for \"days\" was not a number");
return
}
if(!(startDate instanceof Date)) {
console.log("Value provided for \"startDate\" was not a Date object");
return
}
// Get the day of the week as a number (0 = Sunday, 1 = Monday, .... 6 = Saturday)
var dow = startDate.getDay();
var daysToAdd = parseInt(days);
// If the current day is Sunday add one day
if (dow == 0)
daysToAdd++;
// If the start date plus the additional days falls on or after the closest Saturday calculate weekends
if (dow + daysToAdd >= 6) {
//Subtract days in current working week from work days
var remainingWorkDays = daysToAdd - (5 - dow);
//Add current working week's weekend
daysToAdd += 2;
if (remainingWorkDays > 5) {
//Add two days for each working week by calculating how many weeks are included
daysToAdd += 2 * Math.floor(remainingWorkDays / 5);
//Exclude final weekend if remainingWorkDays resolves to an exact number of weeks
if (remainingWorkDays % 5 == 0)
daysToAdd -= 2;
}
}
startDate.setDate(startDate.getDate() + daysToAdd);
return startDate;
}
//And use it like so (months are zero based)
var today = new Date(2016, 10, 22);
today = addWorkDays(today, 5);
$(document).ready(function(){
$('#calc').click(function(){
var d1 = $('#d1').val();
var d2 = $('#d2').val();
$('#dif').text(workingDaysBetweenDates(d1,d2));
});
});
function workingDaysBetweenDates(d0, d1) {
var startDate = parseDate(d0);
var endDate = parseDate(d1);
//節日陣列,自己改一下 僅列到228補假,其他自行以行事曆列出
var holidays = ['2021-01-01','2021-02-10','2021-02-11','2021-02-12','2021-02-13','2021-02-14','2021-02-15','2021-02-16', '2018-03-01'];
// 結束日一定要大於開始日,否則回傳0
if (endDate < startDate)
return 0;
var z = 0; // 遇到國定假日的日數
for (i = 0; i < holidays.length; i++)
{
var cand = parseDate(holidays[i]);
var candDay = cand.getDay();
if (cand >= startDate && cand <= endDate && candDay != 0 && candDay != 6)
{
// 如果假日不是星期六日,就
z++;
}
}
// 計算兩個日期間的總日數
var millisecondsPerDay = 86400 * 1000; // 一天的總milliseconds微秒數
startDate.setHours(0,0,0,1); // 一天的起點
endDate.setHours(23,59,59,999); // 終點
var diff = endDate - startDate; // 兩天之間的總微秒數
var days = Math.ceil(diff / millisecondsPerDay);
// 算出周數weeks
var weeks = Math.floor(days / 7);
// 總日數 - (周數*2)=實際工作天(包含國定假日)
days = days - (weeks * 2);
var startDay = startDate.getDay();
var endDay = endDate.getDay();
// 減去周末
if (startDay - endDay > 1)
days = days - 2;
// 如果開始日是星期天,結束日不是星期六
if (startDay == 0 && endDay != 6)
days = days - 1
// 如果結束日星期六,開始日不是星期天
if (endDay == 6 && startDay != 0)
days = days - 1
// 減掉國定假日
return days - z;
}
function parseDate(input) {
// 轉換日期格式
var parts = input.match(/(\d+)/g);
// new Date(year, month [, date [, hours[, minutes[, seconds[, ms]]]]])
return new Date(parts[0], parts[1]-1, parts[2]); // months are 0-based
}
出勤系統都會遇到同樣的問題,
單單計算假日不可靠,比如說突然颱風天放假,跨月份補上班或跨月份連假,
這些都不是單純扣掉假日(六&日)就能解決的。
這個要用後端資料庫來做才會【有效能】
Date.prototype.addDays = function(days) {
this.setDate(this.getDate() + days);
return this;
}
Date.prototype.addWorkDays = function(days) {
const weekday = this.getDay() == 0? 6: this.getDay() -1
this.addDays( parseInt(days/5)*2 + days )
if (weekday >= 4) this.addDays( 6-weekday )
return this;
}