준호씨의 블로그

Java - "x개월 x일 남았습니다" 구하기2. oracle months_between 본문

개발이야기

Java - "x개월 x일 남았습니다" 구하기2. oracle months_between

준호씨 2020. 3. 20. 23:59

지난번에 Java8의 LocalDate와 Period를 이용해서 몇 개월 며칠 남았는지 구하는 코드를 만들어 보았습니다.

 

Java - "x개월 x일 남았습니다" 구하기

보통 D-Day는 종종 구할 일이 있고 예제도 많습니다. 하지만 D-Day를 다르게 표현하는 "x개월 x일 남았습니다"는 예제를 찾기 힘듭니다. 개월 수 차이 구하는 예제도 많던데 이상하게도 몇 개월 며칠은 보이지 않..

junho85.pe.kr

사실 이 코드를 만들게 된 이유는 레거시 코드의 개선작업을 하면서 였는데요. 결국 위의 방법은 사용하지 않고 코드를 새로 만들게 되었습니다.

기존에 몇개월 며칠 남은 것을 구할 때 oracle의 months_between 함수를 이용하고 있었습니다. 뭘 이런 걸 구하는데 sql로 했는지 의아하게 생각할 수도 있는데요. 예전에는 sql, procedure, trigger 등을 이용해서 DB에서 로직을 구현하는 경우도 꽤 있었습니다. 요즘은 잘 보기 힘들지만 레거시 코드에는 꽤 남아 있는 편입니다.

아무튼 Period에서 알려 주는 남은 기간은 months_between과는 좀 달랐습니다. 예를 들어 보겠습니다.

select abs(trunc(b)) rest_months,
       trunc(abs(trunc(b)*(-1) + b)/(0.032258064516129))+1 rest_days
from  (select months_between(sysdate, to_date('20210319','yyyymmdd')) b
       from dual
      );

오늘이 2020.03.20인데요. 어딘가 가입을 했는데 멤버십 기간이 1년이라고 가정하면 2021.03.19 까지가 될 것입니다. 위의 sql을 실행해 보면 REST_MONTHS 11, REST_DAYS 30이 나옵니다.

자 지난번 Java 코드를 봅시다. 똑같이 2021년 3월 19일 까지 남은 기간을 구하는 건데요.

LocalDate startDate = LocalDate.now();
LocalDate endDate = LocalDate.of(2021, 3, 19);
Period period = startDate.until(endDate);
System.out.println("REST_MONTHS: " + period.getMonths() + ", REST_DAYS: " + period.getDays());

결과는 REST_MONTHS: 11, REST_DAYS: 27 가 나옵니다.

아니 왜 남은 기간을 구하는데 11개월은 같은데 30일이 나오기도 하고 27일이 나오기도 하죠? 게다가 얼핏 보기에는 11개월 30일이 맞을 거 같습니다. 하지만 사실 11개월 27일이 맞습니다.

오늘 2020년 03월 20일로 부터 11개월 후는 2020년 02월 20일이 될 거고요. 2021년 2월은 28일까지입니다. 2월이 8일 3월이 19일 해서 27일이 남은 게 맞죠.

달력에다가 마지막 달에서 남은 일수를 직접 찍어 보았습니다.

하지만 느낌상으로는 좀 이상합니다. 한달이라함은 느낌상 30일 정도 되는데 말이죠. 추측컨대 기존에 구현한 사람은 30일로 나오는 걸 선호해서 저렇게 만들지 않았을까 생각해 봅니다.

남은 기간을 몇개월 며칠이 아니고 그냥 364일처럼 며칠만 표현하는 게 어떨까 의견도 있었습니다. 하지만 지금은 이게 중요 과제가 아니었기 때문에 표기방법을 바꾸는 건 나중에 생각해 보기로 했습니다. 일단은 기존처럼은 나오게 했어야 했지요. 그래서 Java로 Oracle의 months_between 함수를 구현해 보기로 했습니다.

빠르게 구현해야 되어서 일단 익숙한 Calendar를 이용해서 인터넷 자료들을 참고해서 만들었습니다.

public static double monthsBetween(Date baseDate, Date dateToSubstract) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(baseDate);
    int baseDayOfYear = cal.get(Calendar.DAY_OF_YEAR);
    int baseYear = cal.get(Calendar.YEAR);

    cal.setTime(dateToSubstract);
    int subDayOfYear = cal.get(Calendar.DAY_OF_YEAR);
    int subYear = cal.get(Calendar.YEAR);

    return ((baseYear - subYear) * (cal.getMaximum(Calendar.MONTH) + 1)) +
            (baseDayOfYear - subDayOfYear) / 31.0;
}

이제 monthsBetween 함수를 이용해서 남은 개월과 일 수를 구해봅니다.

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
Date startDate = sdf.parse("20200320");
Date endDate = sdf.parse("20210319");
double v = monthsBetween(startDate, endDate);

int restMonths = Math.abs((int) v);
int restDays = (int) (Math.abs((int) v * (-1) + v) / 0.032258064516129) + 1;

System.out.println("REST_MONTHS: " + restMonths + ", REST_DAYS: " + restDays);

좀 복잡하지만 기존에 sql 로 구했던 결과처럼 REST_MONTHS: 11, REST_DAYS: 30 가 나옵니다.

여기서 0.032258064516129 라는 숫자가 무슨 숫자인가 궁금할 수 있는데요. 한 달을 1이라고 했을 때 31로 나눈 값입니다. months_between의 결과가 남은 개월 수를 소수점까지 표현해 주고 있어서 정부 부분은 개월 수, 소수 부분을 일수로 구하는 것입니다.

코드가 조금 복잡하긴 한데 좀 더 쉽고 이해하기 좋은 방법이 있는지 알아 보는거도 좋을 거 같네요.

0 Comments
댓글쓰기 폼