Java – 공휴일 계산

얼마전 수행한 프로젝트에서 작업 스케쥴링을 위해 휴일 계산이 필요하게 되었습니다.
아래의 코드는 양력 휴일 / 음력 휴일 / 대체 공휴일 / 토요일 / 일요일을 계산하여, 업무를 수행하는 날짜인지 체크할때 사용하였습니다.
참고로 대체 공휴일의 정의는 아래와 같습니다.

법정공휴일은 ‘관공서의 공휴일에 관한 규정'(대통령령)에 의해 공휴일이 된 날을 말한다.
법령에 따르면 법정공휴일은 일요일, 국경일, 1월 1일, 음력 1월 1일(설날)과 전후 이틀, 석가탄신일(음력 4월 8일),
어린이날(5월 5일), 현충일(6월 6일), 음력 8월 15일(추석)과 전후 이틀, 성탄절(12월 25일),
보궐선거를 제외한 각종 선거투표일 등 정부에서 수시로 정하는 날 등이다.
국경일은 ‘국경일에 관한 법률’에 의한 3ㆍ1절, 제헌절, 광복절, 개천절, 한글날을 말한다.

대체공휴일제의 도입 (안 제3조)
– 설날, 추석 연휴가 다른 공휴일과 겹치는 경우 그 날 다음의 첫 번째 비공휴일을 공휴일로 함
– 어린이날이 토요일 또는 다른 공휴일과 겹치는 경우 그 날 다음의 첫 번째 비공휴일을 공휴일로 함
(어린이날 외의 토요일은 대체공휴일에 포함되지 않습니다)

음력날짜는 ICU(International Components for Unicode)를 사용하였습니다.
관련 라이브러리는 https://sites.google.com/site/icusite/ 에서 다운로드할 수 있습니다.
대체 공휴일의 경우 길게 작성됐는데.. 실제로 발생하는 대체공휴일이 많지 않아서 그냥 하드코딩 하였습니다..

package util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import com.ibm.icu.util.ChineseCalendar;

public class HolidayUtil {
    private static String[] solarArr = new String[]{"0101", "0301", "0505", "0606", "0815", "1225"};
    private static String[] lunarArr = new String[]{"0101", "0102", "0408", "0814", "0815", "0816"};
    
    /**
     * 해당일자가 법정공휴일, 대체공휴일, 토요일, 일요일인지 확인
     * @param date 양력날짜 (yyyyMMdd)
     * @return 법정공휴일, 대체공휴일, 일요일이면 true, 오류시 false
     */
    public static boolean isHoliday(String date) {
        try {
            return isHolidaySolar(date) || isHolidayLunar(date) || isHolidayAlternate(date) || isWeekend(date);
        } catch (ParseException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 토요일 또는 일요일이면 true를 리턴한다.
     * @param date 양력날짜 (yyyyMMdd)
     * @return 일요일인지 여부
     * @throws ParseException
     */
    private static boolean isWeekend(String date) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        Calendar cal = Calendar.getInstance();
        cal.setTime(sdf.parse(date));
        return cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY || cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY;
    }
    
    /**
     * 해당일자가 대체공휴일에 해당하는 지 확인
     * @param 양력날짜 (yyyyMMdd)
     * @return 대체 공휴일이면 true
     */
    private static boolean isHolidayAlternate(String date) {
        
        String[] altHoliday = new String[] {
                "20150929", "20160210", "20170130", "20180926", 
                "20180507", "20190506", "20200127", "20220912", 
                "20230124", "20240212", "20240506", "20251008", 
                "20270209", "20290924", "20290507"}; 
        
        return Arrays.asList(altHoliday).contains(date); 
        
        /*
        int year = Integer.parseInt(date.substring(0, 4));
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        
        // 설날
        String dayFirst2 = convertLunarToSolar(year + "0101");
        String dayFirst3 = convertLunarToSolar(year + "0102");
        String dayFirst1 = String.valueOf(Integer.parseInt(dayFirst2) - 1);     
        
        // 추석
        String dayThanks1 = convertLunarToSolar(year + "0814");
        String dayThanks2 = convertLunarToSolar(year + "0815");
        String dayThanks3 = convertLunarToSolar(year + "0816");
        
        // 어린이날
        String dayChild = year + "0505";
        
        // 해당 년도의 대체휴일 목록
        List<String> altHolyday = new ArrayList<String>();
        
        if(getDayOfWeek(dayFirst1) == Calendar.SUNDAY || getDayOfWeek(dayFirst2) == Calendar.SUNDAY || getDayOfWeek(dayFirst3) == Calendar.SUNDAY || isHolidaySolar(dayFirst1) || isHolidaySolar(dayFirst2) || isHolidaySolar(dayFirst3)) {
            int y = Integer.parseInt(dayFirst3.substring(0, 4));
            int m = Integer.parseInt(dayFirst3.substring(4, 6)) - 1;
            int d = Integer.parseInt(dayFirst3.substring(6)) + 1;
            Calendar c = Calendar.getInstance();
            c.set(y, m, d);
            altHolyday.add(sdf.format(c.getTime()));
        }
            
        if(getDayOfWeek(dayThanks1) == Calendar.SUNDAY || getDayOfWeek(dayThanks2) == Calendar.SUNDAY || getDayOfWeek(dayThanks3) == Calendar.SUNDAY || isHolidaySolar(dayThanks1) || isHolidaySolar(dayThanks2) || isHolidaySolar(dayThanks3)) {
            int y = Integer.parseInt(dayThanks3.substring(0, 4));
            int m = Integer.parseInt(dayThanks3.substring(4, 6)) - 1;
            int d = Integer.parseInt(dayThanks3.substring(6)) + 1;
            Calendar c = Calendar.getInstance();
            c.set(y, m, d);
            altHolyday.add(sdf.format(c.getTime()));
        }
        
        int childWeek = getDayOfWeek(dayChild); 
        
        if(childWeek == Calendar.SATURDAY) {
            int y = Integer.parseInt(dayChild.substring(0, 4));
            int m = Integer.parseInt(dayChild.substring(4, 6)) - 1;
            int d = Integer.parseInt(dayChild.substring(6)) + 2;
            Calendar c = Calendar.getInstance();
            c.set(y, m, d);
            altHolyday.add(sdf.format(c.getTime()));
        }
            
        if(childWeek == Calendar.SUNDAY) {
            int y = Integer.parseInt(dayChild.substring(0, 4));
            int m = Integer.parseInt(dayChild.substring(4, 6)) - 1;
            int d = Integer.parseInt(dayChild.substring(6)) + 1;
            Calendar c = Calendar.getInstance();
            c.set(y, m, d);
            altHolyday.add(sdf.format(c.getTime()));
        }
        
        return altHolyday.contains(date); 
        */
    }


    /**
     * 해당일자가 음력 법정공휴일에 해당하는 지 확인
     * @param 양력날짜 (yyyyMMdd)
     * @return 음력 공휴일이면 true
     */
    private static boolean isHolidayLunar(String date) {
        try {
            Calendar cal = Calendar.getInstance();
            ChineseCalendar chinaCal = new ChineseCalendar();
            
            cal.set(Calendar.YEAR, Integer.parseInt(date.substring(0, 4)));
            cal.set(Calendar.MONTH, Integer.parseInt(date.substring(4, 6)) - 1);
            cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)));
            
            chinaCal.setTimeInMillis(cal.getTimeInMillis());
            
            // 음력으로 변환된 월과 일자
            int mm = chinaCal.get(ChineseCalendar.MONTH) + 1;
            int dd = chinaCal.get(ChineseCalendar.DAY_OF_MONTH);
            
            StringBuilder sb = new StringBuilder();
            sb.append(String.format("%02d", mm));
            sb.append(String.format("%02d", dd));
                        
            // 음력 12월의 마지막날 (설날 첫번째 휴일)인지 확인
            if (mm == 12) {
                int lastDate = chinaCal.getActualMaximum(ChineseCalendar.DAY_OF_MONTH);
                if (dd == lastDate) {
                    return true;
                }
            }

            // 음력 휴일에 포함되는지 여부 리턴
            return Arrays.asList(lunarArr).contains(sb.toString()); 
        } catch(Exception ex) {
            System.out.println(ex.getStackTrace());
            return false;
        }
    }


    /**
     * 해당일자가 양력 법정공휴일에 해당하는 지 확인
     * @param date 양력날짜 (yyyyMMdd)
     * @return 양력 공휴일이면 true
     */
    private static boolean isHolidaySolar(String date) {
        try {
            // 공휴일에 포함 여부 리턴 
            return Arrays.asList(solarArr).contains(date.substring(4));
        } catch(Exception ex) {
            System.out.println(ex.getStackTrace());
            return false;
        }
    }
    
    
    /**
     * 음력날짜를 양력날짜로 변환
     * @param 음력날짜 (yyyyMMdd)
     * @return 양력날짜 (yyyyMMdd)
     */
    private static String convertLunarToSolar(String yyyymmdd) {
        ChineseCalendar cc = new ChineseCalendar();
        Calendar cal = Calendar.getInstance();

        if (yyyymmdd == null)
            return "";

        String date = yyyymmdd.trim();
        if (date.length() != 8) {
            if (date.length() == 4)
                date = date + "0101";
            else if (date.length() == 6)
                date = date + "01";
            else if (date.length() > 8)
                date = date.substring(0, 8);
            else
                return "";
        }

        cc.set(ChineseCalendar.EXTENDED_YEAR, Integer.parseInt(date.substring(0, 4)) + 2637);
        cc.set(ChineseCalendar.MONTH, Integer.parseInt(date.substring(4, 6)) - 1);
        cc.set(ChineseCalendar.DAY_OF_MONTH, Integer.parseInt(date.substring(6)));
        
        cal.setTimeInMillis(cc.getTimeInMillis());

        int y = cal.get(Calendar.YEAR);
        int m = cal.get(Calendar.MONTH) + 1;
        int d = cal.get(Calendar.DAY_OF_MONTH);

        StringBuffer ret = new StringBuffer();
        ret.append(String.format("%04d", y));
        ret.append(String.format("%02d", m));
        ret.append(String.format("%02d", d));

        return ret.toString();
    }
    
    /**
     * 양력날짜의 요일을 리턴
     * @param 양력날짜 (yyyyMMdd)
     * @return 요일(int)
     */
    private static int getDayOfWeek(String day) {
        int y = Integer.parseInt(day.substring(0, 4));
        int m = Integer.parseInt(day.substring(4, 6)) - 1;
        int d = Integer.parseInt(day.substring(6));
        Calendar c = Calendar.getInstance();
        c.set(y, m, d);
        return c.get(Calendar.DAY_OF_WEEK);
    }


    public static void main(String[] args) {
        System.out.println("20150217        : " + HolidayUtil.isHoliday("20150217"));       
        System.out.println("20150218 (음력) : " + HolidayUtil.isHoliday("20150218")); // 설날 - 전날
        System.out.println("20150219 (음력) : " + HolidayUtil.isHoliday("20150219")); // 설날       
        System.out.println("20150220 (음력) : " + HolidayUtil.isHoliday("20150220")); // 설날 - 다음날
        System.out.println("20150221        : " + HolidayUtil.isHoliday("20150221"));       
        System.out.println("20151225 (양력) : " + HolidayUtil.isHoliday("20151225")); // 크리스마스
        System.out.println("20151226        : " + HolidayUtil.isHoliday("20151226"));
        System.out.println("20150929 (대체) : " + HolidayUtil.isHoliday("20150929")); // 대체 휴일
    }   
}

 

Notice

  • 이 저작물은 크리에이티브 커먼즈 저작자표시-비영리-변경금지 2.0 대한민국 라이선스에 따라 이용할 수 있습니다. 크리에이티브 커먼즈 라이선스
  • 저작권과 관련된 파일요청 및 작업요청을 받지 않습니다.
  • 댓글에 대한 답변은 늦을 수도 있습니다.
  • 4 댓글

    댓글 남기기

    이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다