어떤 작업을 수행했던 시작과 끝 시간이 주어지고 해당 기간에 소요된 공수를 일단위로 계산한 결과가 필요할 때 아래와 같이 계산할 수 있 다


요건

  • 주5일(월~금) 하루 8시간 근무를 기준으로 계산한다
  • 점심시간(12~13시) 한 시간은 계산시 제외한다
  • 시작일과 끝일은 분단위까지 계산에 반영한다
  • 결과값은 수수점 셋째자리에서 올림처리하고 소수점 둘째 자리까지 표현한다
  • 입력값은 yyyyMMddHHmm 포맷의 12자리 문자열이다


코드

function calcManDay(st, ed) {
    // date 기준 diff 일수 이후 날짜 계산
    function addDays(date, diff){
        const oneday = 60 * 1000 * 60 * 24;
        return new Date(date.getTime() + oneday * diff);
    }
    
    // 시작일자가 토/일 이면 앞선 금요일로 보정한다
    function setStartDate(date){
        if(date.getDay()===0){
            // 일요일이면 이틀 전으로 땡겨서 계산
            return addDays(date1, -2)
        }else if(date.getDay()===6){
            // 토요일이면 하루전으로 땡겨서 계산
            return addDays(date1, -1)
        }else {
            return date;
        }
    }
    
    // 끝 일자가 토/일 이면 다음 월요일로 보정한다
    function setEndDate(date){
        if(date.getDay()===0){
            // 일요일이면 하루 뒤로 미뤄서 계산
            return addDays(date, 1)
        }else if(date.getDay()===6){
            // 토요일이면 이틀 뒤로 미뤄서 계산
            return addDays(date, 2);
        }else{
            return date;
        }
    }
    
    // 시작일자와 날짜차이를 입력받아서 토/일요일을 일수에서 차감한다. 
    function workingDayCnt(date, dayDiff){
        var fullWeekCnt = Math.floor(dayDiff / 7);
        var restDayCnt = dayDiff % 7;
    
        if (date.getDay() + restDayCnt <= 5) {
            return fullWeekCnt * 5 + restDayCnt;
        } else {
            return fullWeekCnt * 5 + restDayCnt - 2;
        }    
    
    }
    
    // date1 과 date2의 일수차이를 계산
    function dayDiff(date1, date2){
        // 시작일과 끝일이 주말일 경우 날짜 보정
        date1 = setStartDate(date1);
        date2 = setEndDate(date2);
    
        var timeDiff = Math.abs(date2.getTime() - date1.getTime());
    
        return Math.ceil(timeDiff / (1000 * 3600 * 24));    
    }
    
    //  시작일자와 끝일자에서 소요된 시간을 분단위로 계산해서 ManDay를 계산한다
    function workingHour(workingDayCnt, HH1, mm1, HH2, mm2){
        var hh1 = Number(HH1);
        var mm1 = Number(mm1);
        if(hh1 < 9){
            // 9시 이전으로 등록된 시각은 9로 세팅
            hh1 = 9;
            mm1 = 0;
        }
    
        var hh2 = Number(HH2);
        var mm2 = Number(mm2);
        if(hh2 >= 18){
            // 18시 이후로 처리된 건은 모두 18시로 세팅
            hh2 = 18;
            mm2 = 0;
        }
    
        // 점심시간 차감
        var h1 = hh1 >= 13 ? (hh1 - 1) : hh1;
        var h2 = hh2 >= 13 ? (hh2 - 1) : hh2;
    
        // 하루 8시간 근무 기준으로
        if (workingDayCnt === 0) {
            return ((h2 - h1 - 1)*60 + (60-mm1) + mm2) / (8*60);
        }else{
            return ((17 - h1 -1 )*60 + (60-mm1) + (h2 - 9)*60 + mm2) / (8*60);
        }
    }
    
    var yyyy1 = st.substr(0, 4);
    var MM1 = st.substr(4, 2);
    var dd1 = st.substr(6, 2);
    var HH1 = st.substr(8, 2);
    var mm1 = st.substr(10, 2);

    var yyyy2 = ed.substr(0, 4);
    var MM2 = ed.substr(4, 2);
    var dd2 = ed.substr(6, 2);
    var HH2 = ed.substr(8, 2);
    var mm2 = ed.substr(10, 2);

    var date1 = new Date(MM1 + "/" + dd1 + "/" + yyyy1);
    var date2 = new Date(MM2 + "/" + dd2 + "/" + yyyy2);


    var dayDiff = dayDiff(date1, date2);
    var wd = workingDayCnt(date1, dayDiff);
    var wh = workingHour(wd, HH1, mm1, HH2, mm2);
    
    var res;
    if(wd <= 1){
        res = wh;
    }else{
        res = wd -1 + wh;
    }
    
    // 소수점 셋째 자리에서 올림처리하고 소수점 두째자리까지 표현
    return Math.ceil(res * 100) / 100;
}


테스트코드

var arr = [{"seq":0,"start":"201901101029","end":"201901101053"},{"seq":1,"start":"201901100959","end":"201901101417"},{"seq":2,"start":"201901091701","end":"201901091802"},{"seq":3,"start":"201901091612","end":"201901091612"},{"seq":4,"start":"201901081818","end":"201901081818"},{"seq":5,"start":"201901091105","end":"201901091700"},{"seq":6,"start":"201901091047","end":"201901091559"},{"seq":7,"start":"201901091029","end":"201901091046"},{"seq":8,"start":"201901090956","end":"201901110932"},{"seq":9,"start":"201901090938","end":"201901090941"},{"seq":10,"start":"201901090927","end":"201901090936"},{"seq":11,"start":"201901071640","end":"201901071710"},{"seq":12,"start":"201901041605","end":"201901041640"},{"seq":13,"start":"201901040910","end":"201901041437"},{"seq":14,"start":"201901021000","end":"201901031713"},{"seq":15,"start":"201901031145","end":"201901031623"},{"seq":16,"start":"201901021118","end":"201901101432"},{"seq":18,"start":"201812311414","end":"201812311416"},{"seq":19,"start":"201812311322","end":"201901041348"},{"seq":20,"start":"201812311010","end":"201812311055"},{"seq":21,"start":"201812271035","end":"201812281339"},{"seq":22,"start":"201812271558","end":"201812271652"},{"seq":23,"start":"201812271453","end":"201812271501"},{"seq":24,"start":"201812271335","end":"201812271357"},{"seq":25,"start":"201812261735","end":"201812261736"},{"seq":26,"start":"201812261634","end":"201812261643"},{"seq":27,"start":"201812261032","end":"201812261634"},{"seq":28,"start":"201812241119","end":"201812241121"},{"seq":29,"start":"201812211616","end":"201812271414"}];

arr.forEach(v => {
    function format(str){
        return str.substr(0,4) + "/" + str.substr(4,2) + "/" + str.substr(6,2) + " " + str.substr(8,2) + ":" + str.substr(10,2)
    }
    console.log(format(v.start) + " ~ " + format(v.end) + " : " + calcManDay(v.start, v.end))
})


출력결과

2019/01/10 10:29 ~ 2019/01/10 10:53 : 0.05
2019/01/10 09:59 ~ 2019/01/10 14:17 : 0.42
2019/01/09 17:01 ~ 2019/01/09 18:02 : 0.13
2019/01/09 16:12 ~ 2019/01/09 16:12 : 0
2019/01/08 18:18 ~ 2019/01/08 18:18 : 0
2019/01/09 11:05 ~ 2019/01/09 17:00 : 0.62
2019/01/09 10:47 ~ 2019/01/09 15:59 : 0.53
2019/01/09 10:29 ~ 2019/01/09 10:46 : 0.04
2019/01/09 09:56 ~ 2019/01/11 09:32 : 1.95
2019/01/09 09:38 ~ 2019/01/09 09:41 : 0.01
2019/01/09 09:27 ~ 2019/01/09 09:36 : 0.02
2019/01/07 16:40 ~ 2019/01/07 17:10 : 0.07
2019/01/04 16:05 ~ 2019/01/04 16:40 : 0.08
2019/01/04 09:10 ~ 2019/01/04 14:37 : 0.56
2019/01/02 10:00 ~ 2019/01/03 17:13 : 1.78
2019/01/03 11:45 ~ 2019/01/03 16:23 : 0.46
2019/01/02 11:18 ~ 2019/01/10 14:32 : 6.28
2018/12/31 14:14 ~ 2018/12/31 14:16 : 0.01
2018/12/31 13:22 ~ 2019/01/04 13:48 : 4.06
2018/12/31 10:10 ~ 2018/12/31 10:55 : 0.1
2018/12/27 10:35 ~ 2018/12/28 13:39 : 1.39
2018/12/27 15:58 ~ 2018/12/27 16:52 : 0.12
2018/12/27 14:53 ~ 2018/12/27 15:01 : 0.02
2018/12/27 13:35 ~ 2018/12/27 13:57 : 0.05
2018/12/26 17:35 ~ 2018/12/26 17:36 : 0.01
2018/12/26 16:34 ~ 2018/12/26 16:43 : 0.02
2018/12/26 10:32 ~ 2018/12/26 16:34 : 0.63
2018/12/24 11:19 ~ 2018/12/24 11:21 : 0.01
2018/12/21 16:16 ~ 2018/12/27 14:14 : 3.75


제약사항

  • 토,일 뿐 아니라 법정공휴일도 제외할 수 있으면 좋은데 거기까지는 처리를 못했다.