[Java] 대한민국 월 별 주차 수 구하기

0

대한민국은 주의 시작을 월요일 입니다.

그리고 주간 수 결정법에 의해 목요일의 포함 여부에 따라 주차가 달라지는데요.

자바에서는 WeekFields 라는 클래스를 사용하여 대응하면 될 것 같지만 약간의 문제가 있습니다.

이 달력을 봤을 때 6월 1일은 몇 주차 일까요?

검증해봅시다.

    @Test
    void test() {
        WeekFields weekFields = WeekFields.of(DayOfWeek.MONDAY, DayOfWeek.THURSDAY.getValue());
        LocalDate date = LocalDate.of(2024, 6, 1);
        System.out.println(date.getMonthValue() + "월 " + date.get(weekFields.weekOfMonth()) + "주차");
		// 6월 0주차
        LocalDate date1 = LocalDate.of(2024, 6, 3);
        System.out.println(date1.getMonthValue() + "월 " + date1.get(weekFields.weekOfMonth()) + "주차");
		// 6월 1주차
        LocalDate date2 = LocalDate.of(2024, 7, 29);
        System.out.println(date2.getMonthValue() + "월 " + date2.get(weekFields.weekOfMonth()) + "주차");
        // 7월 5주차
    }

원하는 결과가 나오지 않았습니다.
2024년 6월 1일의 경우 0주차가 나왔습니다. 0주차라는 게 있나요?
2024년 6월 3일의 경우 1주차가 나왔습니다. 맞습니다.
2024년 7월 29일의 경우 5주차가 나왔습니다. 하지만 7월의 마지막 일인 31일은 수요일입니다. 7월은 5주가 없고 29일부터 31일은 8월의 1주차에 편입되어야 합니다.

public enum Week {
    FIRST(1),
    SECOND(2),
    THIRD(3),
    FOURTH(4),
    FIFTH(5),
    LAST(-1);

    private static final Map<Integer, Week> registry = Arrays.stream(values())
            .collect(Collectors.toUnmodifiableMap(Week::numberOfWeek, Function.identity()));
    private final int numberOfWeek;

    Week(int numberOfWeek) {
        this.numberOfWeek = numberOfWeek;
    }

    public static Week valueOf(int value) {
        return Optional.ofNullable(registry.get(value))
                .orElseThrow(() -> new IllegalArgumentException("Invalid week value: " + value));
    }

    public int numberOfWeek() {
        return numberOfWeek;
    }
}


public record WeekOfMonth(
        Month month,
        Week week
) {

    public static final int MINIMAL_DAYS_IN_FIRST_WEEK = DayOfWeek.THURSDAY.getValue();
    public static final DayOfWeek FIRST_DAY_OF_WEEK = DayOfWeek.MONDAY;
    public static final WeekFields WEEK_FIELDS = WeekFields.of(FIRST_DAY_OF_WEEK, MINIMAL_DAYS_IN_FIRST_WEEK);

    public static WeekOfMonth of(Month month, Week week) {
        return new WeekOfMonth(month, week);
    }

    public static WeekOfMonth of(LocalDate date) {
        // 해당 월의 첫번째 날 구하기
        LocalDate firstDayOfMonth = date.withDayOfMonth(1);
        
        // 해당 월의 첫번째 날이 목요일 이후라면 이전달의 마지막 주차를 구해야 함
        if (firstDayOfMonth.getDayOfWeek().getValue() > MINIMAL_DAYS_IN_FIRST_WEEK) {
            // 해당 월의 첫번째 날이 목요일 이후인 경우 해당 주의 시작 날을 구함 (전 달 마지막 주의 월요일을 구함)
            LocalDate weekStart = firstDayOfMonth.with(FIRST_DAY_OF_WEEK);

            // 해당 일의 날짜가 전 달 마지막 주에 포함 되는지 확인
            if (date.isBefore(weekStart.plusWeeks(1))) {
                return new WeekOfMonth(weekStart.getMonth(), Week.valueOf(weekStart.get(WEEK_FIELDS.weekOfMonth())));
            }

        }
        // 해당 월의 마지막 날 구하기
        LocalDate lastDayOfMonth = date.withDayOfMonth(date.lengthOfMonth());

        // 마지막 날이 목요일 이전인지 확인
        if (lastDayOfMonth.getDayOfWeek().getValue() < MINIMAL_DAYS_IN_FIRST_WEEK) {
            // 마지막 주의 시작 날을 구함
            LocalDate lastWeekStart = lastDayOfMonth.with(FIRST_DAY_OF_WEEK);

            // 해당 일의 날짜가 다음 달 첫 주에 포함 되는지 확인
            if (date.isAfter(lastWeekStart.minusDays(1))) {
                LocalDate nextMonthDate = date.plusWeeks(1);
                return new WeekOfMonth(nextMonthDate.getMonth(), Week.FIRST);
            }
        }

        int weekOfMonth = date.get(WEEK_FIELDS.weekOfMonth());
        return new WeekOfMonth(date.getMonth(), Week.valueOf(weekOfMonth));
    }


    public boolean isWithin(LocalDate date) {
        if (Week.LAST == this.week) {
            LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
            if (lastDayOfMonth.getDayOfWeek().getValue() < MINIMAL_DAYS_IN_FIRST_WEEK) {
                LocalDate firstDayOfLastWeek = lastDayOfMonth.with(TemporalAdjusters.previousOrSame(FIRST_DAY_OF_WEEK));
                LocalDate adjustedPreviousLastDayOfLastWeek = firstDayOfLastWeek.minusDays(8);
                return date.isAfter(adjustedPreviousLastDayOfLastWeek) && date.isBefore(firstDayOfLastWeek);
            }
        }

        return this.equals(WeekOfMonth.of(date));
    }
}

목요일의 포함여부에 따라 마지막 주차와 첫 주차가 변경되는 부분을 대응한 코드입니다.

class WeekOfMonthTests {
    @Test
    @DisplayName("마지막 주차에 해당하는 날짜가 포함되는지 확인")
    void t1() {
        WeekOfMonth sut = WeekOfMonth.of(Month.JULY, Week.LAST);
        boolean within = sut.isWithin(LocalDate.of(2024, 7, 26));
        Assertions.assertThat(within).isTrue();
    }

    @Test
    @DisplayName("네 번째 주차에 해당하는 날짜가 포함되는지 확인")
    void t2() {
        WeekOfMonth sut = WeekOfMonth.of(Month.JULY, Week.FOURTH);
        boolean within = sut.isWithin(LocalDate.of(2024, 7, 26));
        Assertions.assertThat(within).isTrue();
    }

    @Test
    @DisplayName("마지막 주차에 해당하는 날짜가 포함되지 않는지 확인")
    void t3() {
        // given
        LocalDate date = LocalDate.of(2024, 7, 29);
        WeekOfMonth sut = WeekOfMonth.of(Month.JULY, Week.LAST);

        // when
        boolean within = sut.isWithin(date);

        // then
        Assertions.assertThat(within).isFalse();
        WeekOfMonth weekOfMonth = WeekOfMonth.of(date);
        Assertions.assertThat(weekOfMonth.month()).isEqualTo(Month.AUGUST);
        Assertions.assertThat(weekOfMonth.week()).isEqualTo(Week.FIRST);
    }

    @Test
    @DisplayName("이전 달의 마지막 날짜가 목요일 이전인 경우 날짜가 다음 달의 첫 주차에 포함되는지 확인")
    void t4() {
        LocalDate date = LocalDate.of(2024, 7, 29);
        WeekOfMonth sut = WeekOfMonth.of(date);
        Assertions.assertThat(sut.month()).isEqualTo(Month.AUGUST);
        Assertions.assertThat(sut.week()).isEqualTo(Week.FIRST);
    }
}

0개의 댓글