ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] ChineseCalendar / KoreanLunarCalendar
    나는야 개발자/Java 2022. 7. 14. 09:57

    [개요]

      주말과, 공휴일을 제외한 워킹데이만 설정하여 설정일을 기점으로 목표일을 반환해야 하는 기능 구현.

    예 ) 7월 8일을 기준으로 3일을 설정 → 7월 12일 리턴 (7월 9, 10은 주말)

    예 ) 9월 8일을 기준으로 3일을 설정 → 9월 14일 리턴 (9월 9, 10, 11, 12(대체)는 추석)

     

    [문제 해결 아이디어]

      1. 이번 문제를 해결하기 위해 본인은 LocalDateTime을 이용하였다.

      2. 주말 제외는 LocalDateTime의 getDayOfWeek를 이용하여 쉽게 처리

      3. 지정 공휴일 중 양력에 해당되는 공휴일은 하드코딩으로 직접 지정

        (신정: 1월 1일, 3.1절: 3월 1일, ... 등)

      4. 음력 공휴일에 해당되는 공휴일은 ChineseCalendar와 KoreanLunarCalendar를 이용

        해당 라이브러리를 이용하면 음력에 해당되는 양력날짜를 받을 수 있음.

     

    [참고]

      1. ChineseCalendar는 Maven, Gradle에 의존성을 주입함으로써 라이브러리 사용가능.

    >> https://mvnrepository.com/artifact/com.ibm.icu/icu4j

     

    Maven Repository: com.ibm.icu » icu4j

    International Component for Unicode for Java (ICU4J) is a mature, widely used Java library providing Unicode and Globalization support VersionVulnerabilitiesRepositoryUsagesDate71.1.x71.1Central30Apr, 202270.1.x70.1Central70Oct, 202169.1.x69.1Central45Apr,

    mvnrepository.com

      2. KoreanLunarCalendar는 gitHub에서 도움을 받았다.

    >> https://github.com/usingsky/KoreanLunarCalendar

     

    GitHub - usingsky/KoreanLunarCalendar: Libraries to convert Korean lunar-calendar to Gregorian calendar written in java

    Libraries to convert Korean lunar-calendar to Gregorian calendar written in java - GitHub - usingsky/KoreanLunarCalendar: Libraries to convert Korean lunar-calendar to Gregorian calendar written in...

    github.com

     

    [소스코드 첨부]

    package com.example.demo.service;
    
    import com.ibm.icu.util.ChineseCalendar;
    import org.springframework.stereotype.Service;
    
    import java.time.Instant;
    import java.time.LocalDateTime;
    import java.time.ZoneId;
    import java.time.format.DateTimeFormatter;
    import java.util.*;
    
    @Service
    public class DemoService {
        KoreanLunarCalendar calendar = KoreanLunarCalendar.getInstance();
        Integer year;
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
    
        // 전체 공휴일 리스트를 받음
        public List getHolliday() {
            this.year = LocalDateTime.now().getYear();
    
            ArrayList<String> holidayList = new ArrayList<>();
    
            // 법정 지정 공휴일
            // 양력 공휴일
            String[] SolarHoliday = {
                    convertDate2String(LocalDateTime.of(year, 1, 1, 0, 0, 0)), // 신정
    
                    convertDate2String(LocalDateTime.of(year, 3, 1, 0, 0, 0)), // 3.1절
                    substituteHoliday(LocalDateTime.of(year, 3, 1, 0, 0, 0), "3.1절"),  // 3.1절 대체공휴일
    
                    convertDate2String(LocalDateTime.of(year, 5, 5, 0, 0, 0)), // 어린이날
                    substituteHoliday(LocalDateTime.of(year, 5, 5, 0, 0, 0), "어린이날"),  // 어린이날 대체공휴일
    
                    convertDate2String(LocalDateTime.of(year, 6, 6, 0, 0, 0)),  // 현충일
    
                    convertDate2String(LocalDateTime.of(year, 8, 15, 0, 0, 0)),  // 광복절
                    substituteHoliday(LocalDateTime.of(year, 8, 15, 0, 0, 0), "광복절"),  // 광복절 대체공휴일
    
                    convertDate2String(LocalDateTime.of(year, 10, 3, 0, 0, 0)),  // 개천절
                    substituteHoliday(LocalDateTime.of(year, 10, 3, 0, 0, 0), "개천절"),  // 개천절 대체공휴일
    
                    convertDate2String(LocalDateTime.of(year, 10, 9, 0, 0, 0)),  // 한글날
                    substituteHoliday(LocalDateTime.of(year, 10, 9, 0, 0, 0), "한글날"),  // 한글날 대체공휴일
    
                    convertDate2String(LocalDateTime.of(year, 12, 25, 0, 0, 0)),   // 크리스마스
            };
            ArrayList<String> SolarList = new ArrayList<String>(Arrays.asList(SolarHoliday));
    
            // 음력 공휴일
            String[] LunarHoliday = {
                    convertDate2String(convertLunar2Solar(LocalDateTime.of(year, 1, 1, 0, 0, 0)).minusDays(1)), // 설날 연휴
                    convertDate2String(convertLunar2Solar(LocalDateTime.of(year, 1, 1, 0, 0, 0))),  // 설날
                    convertDate2String(convertLunar2Solar(LocalDateTime.of(year, 1, 1, 0, 0, 0)).plusDays(1)),  // 설날 연휴
                    substituteHoliday(convertLunar2Solar(LocalDateTime.of(year, 1, 1, 0, 0, 0)), "설날"),
    
                    convertDate2String(convertLunar2Solar(LocalDateTime.of(year, 8, 15, 0, 0, 0)).minusDays(1)), // 추석 연휴
                    convertDate2String(convertLunar2Solar(LocalDateTime.of(year, 8, 15, 0, 0, 0))), // 추석
                    convertDate2String(convertLunar2Solar(LocalDateTime.of(year, 8, 15, 0, 0, 0)).plusDays(1)),  // 추석 연휴
                    substituteHoliday(convertLunar2Solar(LocalDateTime.of(year, 8, 15, 0, 0, 0)), "추석")
            };
            ArrayList<String> LunarList = new ArrayList<String>(Arrays.asList(LunarHoliday));
    
            calendar.setLunarDate(year, 4, 8, false);  // 석가탄신일
            holidayList.add(calendar.getSolarIsoFormat());
    
            holidayList.addAll(SolarList);
            holidayList.addAll(LunarList);
    
            Collections.sort(holidayList); // 날짜순으로 정렬
            holidayList.removeAll(Arrays.asList(""));
    
            return holidayList;
        }
    
        // 음력 > 양력 변환
        public LocalDateTime convertLunar2Solar(LocalDateTime lunarDate) {
            ChineseCalendar lunarCalendar = new ChineseCalendar();
    
            if (lunarDate.equals(null)) return null;
    
            int lunar_year = lunarDate.getYear(),
                    lunar_month = lunarDate.getMonthValue(),
                    lunar_day = lunarDate.getDayOfMonth();
    
            lunarCalendar.set(ChineseCalendar.EXTENDED_YEAR, lunar_year + 2637);
            lunarCalendar.set(ChineseCalendar.MONTH, lunar_month - 1);
            lunarCalendar.set(ChineseCalendar.DAY_OF_MONTH, lunar_day);
    
            LocalDateTime solarDate = Instant.ofEpochMilli(lunarCalendar.getTimeInMillis()).atZone(ZoneId.of("UTC")).toLocalDateTime();
    
            return solarDate;
        }
    
        /*
         * 대체공휴일 적용
         *   설날, 어린이날, 추석
         *   광복절, 개천절, 한글날
         *   3.1절
         */
        public String substituteHoliday(LocalDateTime date, String holidayName) {
            String returnDate = new String();
            int getDate = date.getDayOfWeek().getValue();
    
            switch (holidayName) {
                case "설날":
                case "추석":
                    if (getDate == 6 || getDate == 7) returnDate = convertDate2String(date.plusDays(2));
                    break;
                default:
                    if (getDate == 6) returnDate = convertDate2String(date.plusDays(2));
                    if (getDate == 7) returnDate = convertDate2String(date.plusDays(1));
                    break;
            }
    
            return returnDate;
        }
    
        public String convertDate2String(LocalDateTime date) {
            return date.format(dateTimeFormatter);
        }
    
        public LocalDateTime getFinishDate(LocalDateTime date, Integer count) {
            Integer plusDays = 0;
    
            List<String> holidayList = this.getHolliday();
            List<Integer> WEEKDAYS = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
    
            while (count > 1) {
                plusDays++;
    
                LocalDateTime tempDate = date.plusDays(plusDays);
                Integer now = tempDate.getDayOfWeek().getValue();
                // 평일 or 주말
                if (WEEKDAYS.contains(now)) {
                    // 평일이 공휴일인가?
                    if (!holidayList.contains(convertDate2String(tempDate))) {
                        count--;
                    } else {
                        // 공휴일이면 SKIP
                    }
                } else {
                    // 주말이면 무조건 SKIP
                }
            };
    
            return date.plusDays(plusDays);
        }
    }

    급하게 해결해야 할 일이라 코드 정리가 안되어있고, 많이 지저분하다.

    나중에 여유가 생긴다면, 다듬어서 잘 이용할 수 있도록 해야겠다.

     

    [Self Q&A]

      Q. KoreanLunarCalendar를 굳이 이용한 이유?

      A. ChineseCalendar만을 이용해서 공휴일을 지정하다보니 설날과 추석에는 오차가 발생하지 않았는데, 석가탄신일에 오차가 발생하는것을 확인할 수 있었다. 해당 현상을 막기위해 불가피하게 석가탄신일에 적용하고자 사용하였다.

    좌) ChineseCalendar / 우) KoreanLunarCalendar

      * 실제 23년의 석가탄신일은 '5월 27일'이다.

     

      Q. 기능을 수정 or 추가 한다면?

      A. 일단 월별 워킹데이 일수를 리스트 형태로 가져오는 메소드를 하나 추가할 것이고,

      수정 사항에 대해서는 천천히 생각해 보려고한다. 일단은 기존에 짜여진 메소드들을 세분화 할 계획이다.

    '나는야 개발자 > Java' 카테고리의 다른 글

    [Java/Study] String Calculation(문자열 계산기)  (0) 2022.04.28

    댓글

Designed by Tistory.