들어가기 전에
만약 Calendar 클래스를 사용하고 계신다면 아래 포스팅을 참고해 주세요 :)
https://hojun-dev.tistory.com/entry/JAVA-Calendar-한국식-월-주차-구하기
개요
매주 보고서 데이터를 엑셀로 출력하여 전달해야 하는 시스템을 만드는 등 특정 날짜가 몇 월의 몇 주차인지 계산해야 하는 경우가 있다.
필자의 경우도 그랬는데, 구글링을 해도 정보가 잘 없는 내용이기도 했고 누군가 만든 코드를 가져다가 사용했다가 에러가 발생했던 경우가 있어 직접 구상하여 Calendar 클래스로 월 주차 구하는 코드를 만들어 사용했다.
작성일 기준으로 정확히 사용한 지 1년이 되었는데 오류가 발생하지 않고 잘 동작했기 때문에 동일한 개발이 필요한 사람들에게 도움이 되고자 동일한 로직으로 최근 자주 사용하는 LocalDate 클래스를 통해 구하는 방법을 만들어 포스팅을 남긴다.
월 주차 계산법
우리나라에서의 월 주차 계산법을 간단히 설명하면 아래와 같다.
한 주의 시작은 월요일이고, 해당 월에서 4일 이상이 존재하는 첫 번째 주를 해당 월의 첫 주로 계산한다.
말이 조금 어려울 수 있는데, 쉽게 설명하면 한 주가 7일이므로 과반수, 즉 4일 이상이 존재하면 한 주 취급하는 것이다.
시작이 월요일이므로 순서 상 4번째인 목요일이 해당 주차에 존재하는지로 계산할 수도 있다.
위의 계산법으로 첫 주를 계산했으니 이 다음 주들은 맞춰서 주차를 계산하면 된다.
마지막 주도 마찬가지로 목요일이 다음 월로 넘어갔다면 해당 주는 다음 월의 첫 주가 된다.
위 계산법을 잘 생각하면서 코드를 작성해 보자.
LocalDate
Java에서 날짜를 더하거나 빼는 등 날짜를 다룰 때 LocalDate 클래스를 많이 사용한다.
예전엔 Calendar 클래스를 많이 사용했지만 여러 이슈들 때문에 요즘은 거의 사장되었다.
LocalDate가 포함된 java.time 라이브러리에서도 Calendar 클래스와 같이 주차를 구할 때 유용한 메소드가 존재한다.
LocalDate localDate = LocalDate.now();
// 한 주의 시작 요일 및 첫 주를 계산할 때 최소로 있어야 하는 날짜 수 설정
WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY, 4);
// 해당 월의 몇 주차인지 계산
int weekOfMonth = localDate.get(weekFields.weekOfMonth());
Calendar 클래스와 마찬가지로 월 주차 계산법에서 말한 내용에 해당하는 메소드가 존재한다.
Calendar 클래스에서는 calendar.setFirstDayOfWeek(Calendar.MONDAY)
와 calendar.setMinimalDaysInFirstWeek(4)
로 각각 설정했어야 했는데,
LocalDate에서는 WeekFields
클래스를 통해 더욱 간단하게 설정할 수 있다.
한국식으로 주차를 계산하기 위해 한 주의 시작을 월요일로 설정하고,
해당 월에서 4일 이상 존재하는 주를 첫 번째 주로 설정하자.
이후 고려해야 할 점이 몇 가지 더 있다.
weekOfMonth 값을 가져올 때는 기준 날짜가 포함된 월에서의 주차 값을 반환하므로 localDate.get(weekFields.weekOfMonth())
을 통해 주차 값을 가져올 때 기준 날짜가 해당 월의 첫날이 포함된 주일 때 해당 주에 4일 이상 존재하지 않으면 0으로 반환한다.
또한 계산할 날짜가 해당 월의 마지막 날이 포함된 주일 때 해당 주에 4일 이상 존재하지 않더라도 다른 주와 동일하게 카운트를 하나 추가하여 반환한다.
따라서 위의 내용을 바탕으로 주차를 계산하는 코드를 작성해 보자.
월 주차 구하기
완성된 코드는 아래와 같다.
public static String getCurrentWeekOfMonth(LocalDate localDate) {
// 한 주의 시작은 월요일이고, 첫 주에 4일이 포함되어있어야 첫 주 취급 (목/금/토/일)
WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY, 4);
int weekOfMonth = localDate.get(weekFields.weekOfMonth());
// 첫 주에 해당하지 않는 주의 경우 전 달 마지막 주차로 계산
if (weekOfMonth == 0) {
// 전 달의 마지막 날 기준
LocalDate lastDayOfLastMonth = localDate.with(TemporalAdjusters.firstDayOfMonth()).minusDays(1);
return getCurrentWeekOfMonth(lastDayOfLastMonth);
}
// 이번 달의 마지막 날 기준
LocalDate lastDayOfMonth = localDate.with(TemporalAdjusters.lastDayOfMonth());
// 마지막 주차의 경우 마지막 날이 월~수 사이이면 다음달 1주차로 계산
if (weekOfMonth == lastDayOfMonth.get(weekFields.weekOfMonth()) && lastDayOfMonth.getDayOfWeek().compareTo(DayOfWeek.THURSDAY) < 0) {
LocalDate firstDayOfNextMonth = lastDayOfMonth.plusDays(1); // 마지막 날 + 1일 => 다음달 1일
return getCurrentWeekOfMonth(firstDayOfNextMonth);
}
return localDate.getMonthValue() + "월 " + weekOfMonth + "주차";
}
월 주차 계산법에 따라 첫 주에 해당하지 않으면 전 월의 마지막 날 기준으로 다시 계산하고,
마지막 주에 해당하지 않으면 다음 월의 첫날을 기준으로 계산한다.
첫 주에 해당하지 않거나 마지막 주에 해당하지 않는 경우는 해당 주에 4일이 존재하지 않는다(목요일이 포함되어 있지 않다)는 것을 의미하므로 월을 바꾼 기준일자로 다시 계산한다면 해당 날짜의 주차에는 무조건 4일 이상이 존재한다(목요일이 포함되어 있다)는 것을 의미하므로 메소드를 다시 호출한다 해서 무한 루프에 빠질 위험은 없다.
실행 결과
2023년 7월을 기준으로 위의 달력 사진에서 표현한 내용과 동일하게 출력되었다.
마무리
이전 프로젝트에서 사용하던 Date와 Calendar는 월 값에 -1을 해줘야 하기도 하고 메소드들이 유연하지 못해 사용할 때 어려움이 많았는데,
LocalDate를 포함한 java.time 라이브러리는 정말 유연하게 사용할 수 있어 굉장히 편리하다.
참고로 GregorianCalendar 클래스에서는 1582년 10월 4일 다음날이 10월 15일이다.
놀랍게도 현재 전 세계적으로 통용되는 양력 달력인 그레고리력으로 1582년 10월 5일 ~ 10월 14일은 실제로 존재하지 않는다.
참고) https://chedulife.com.au/1582년-10월-4일-다음날이-15일이-되는-그레고리력-시행-교황/
'Java & Spring' 카테고리의 다른 글
[Spring] JPA 다중 서버 환경 DB 동시성 문제 해결하기 (2) | 2023.08.18 |
---|---|
[JAVA] 내부 resource 파일 활용하기 (0) | 2023.07.29 |
[JAVA] Calendar로 한국식 월 주차 구하기 (0) | 2023.07.24 |
[JAVA] Lombok Builder, SuperBuilder와 Generic 사용하기 (0) | 2023.07.21 |
[Spring] JPA 중복 Insert 방지하기 (0) | 2023.07.18 |