글 작성자: beaniejoy

📌 1. Problem

  1. 사용자에게 날짜를 입력받아 달력 혹은 해당 날짜를 출력
  2. 연, 월, 일 전부다 입력 받을 시 "YYYY년 MM월 DD일 입니다." 라는 문구 출력
  3. 연, 월 까지만 입력 시 해당 연도 월 달력을 출력
  4. 연도만 입력 시 해당연도 전체 달력을 출력

 

🔖 1-1. Problem Solving 

 여기서 중요한 것은 월 달력을 출력할 때 해당 월 1일이 무슨 요일인지와 거기에 따라 토요일을 기준으로 어떻게 개행("\n")처리를 해야할 것인가이다. 여기에 초점을 맞춰 코드를 짜봤다.

 

1-1-1. 월 1일은 무슨 요일?

 1년 1월 1일이 월요일이라는 것을 이용한다. 입력한 연도 전년도까지 모든 일수를 구하고 입력한 연도의 월, 일까지의 일수를 더해주면 1년 1월 1일부터 입력한 날짜까지의 총 일수가 나올 것이다. 거기에 7을 나눈 나머지를 기준으로 0(일요일) ~6(토요일)이라는 것을 이용해 입력한 날짜가 무슨 요일인지 구한다.

 여기에는 윤년이 포함되어 있기 때문에 이를 꼭 염두하고 코드를 작성해야 한다.
→ 윤년 = 4의 배수 but 100의 배수X, but 400의 배수는 포함이라는 것을 이용해 method화 했다.

 

1-1-2. 토요일을 기준으로 어떻게 개행처리해야 하는가?

 본인은 일요일을 일주일의 시작으로 해서 토요일을 기점으로 달력 숫자가 아래로 내려가는 형식으로 작성했다. 해당 월의 1일이 무슨요일 시작인지를 아는 것이 중요하다. 위에서 (총일수 % 7)을 통한 나머지를 가지고 시작점이 어딘지를 알고 나서 토요일은 나머지가 6이기 때문에 "6 - (1일의 나머지값)"을 해주고 for loop을 돌릴 때 이 값에 도달하면 "\n"처리해서 넘겼다.

public final class CalendarDev {

    public static final int[] monthArray = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    public static boolean isLeaf(int year) {
        return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 ? true : false;
    }

    // 해당 연도, 월 직전까지 총 일수를 계산하는 method
    public static int calTotalDay(int year, int month) {
        int totalDay = 0;
        int priorYear = year - 1;

        totalDay = priorYear * 365;
        totalDay += (priorYear / 4 - priorYear / 100 + priorYear / 400);

        for (int i = 0; i < (month - 1); i++) {
            if (i == 1) {
                // 해당연도가 윤년인 경우
                // 2월달에는 29일을 총 일수에 더해주기 위해 continue 지정
                if (isLeaf(year)) {
                    totalDay += 29;
                    continue;
                }
            }
            totalDay += monthArray[i];
        }

        return (totalDay + 1);
    }

    // calendar를 그리는 method
    public static void calendarForm(int year, int month) {
        int dayOfWeek = calTotalDay(year, month) % 7;

        System.out.println("일\t월\t화\t수\t목\t금\t토");
        for (int i = 0; i < dayOfWeek; i++) {
            System.out.print(" \t");
        }
        int monthDay = monthArray[month - 1];
        // 윤년 중 2월달에 해당하는 일수 29일로 고정
        if (isLeaf(year)) {
            if (month == 2) {
                monthDay = 29;
            }
        }
        // 월달력 출력하는 부분
        for (int i = 1; i <= monthDay; i++) {
            System.out.print(i);
            if ((i - 1) % 7 == (6 - dayOfWeek)) {
                System.out.print("\n");
            } else {
                System.out.print("\t");
            }
        }
    }

    // 해당 연 달력 출력
    public static void printCal(int year) {
        for (int i = 1; i <= 12; i++) {
            System.out.println(i + "월 달력");
            calendarForm(year, i);
            System.out.println();
            System.out.println();
        }
    }
    
    // 해당연도 월 달력 출력
    public static void printCal(int year, int month) {
        calendarForm(year, month);
    }
    
    // 해당연도 월 일 출력
    public static void printCal(int year, int month, int day) {
        System.out.printf("%d년 %d월 %d일 입니다.\n", year, month, day);
        // 요일 구할 때 숫자로 요일을 표현하지말고
        // MONDAY 상수화해서 명확하게 표현하는 것이 좋다.
    }
}
 

 

import java.util.Scanner;

public class CalendarDevDemo {

    public static void main(String[] args) {
        // 1. 4개지 방법으로 표현 그 외의 표현은 예외처리
        // => "2019-11-30", "2019/11/30", "2019.11.30", "2019 11 30"
        // 2. 존재하지 않는 날짜도 예외처리
        System.out.print("날짜를 입력하세요>");
        Scanner sc = new Scanner(System.in);

        String date = sc.nextLine();
        sc.close();

        try {
            int[] dateNum = CalendarFormCheck.splitDate(date);

            if (dateNum.length == 3) {
                CalendarDev.printCal(dateNum[0], dateNum[1], dateNum[2]);
            } else if (dateNum.length == 2) {
                CalendarDev.printCal(dateNum[0], dateNum[1]);
            } else {
                CalendarDev.printCal(dateNum[0]);
            }
        } catch (IncorrectCalendarFormException e) {
            System.out.println(e.getMessage());
        } catch (DateOutOfBoundsException e) {
            System.out.println(e.getMessage());
        }

        System.exit(0);
    }
}

 

 여기서 추가로 입력받을 때 형식을 지키기 위해 형식에 어긋난 입력값은 전부 예외 처리를 했다.
→ 2019-12-04 or 2019/12/04 or 2019 12 04 or 2019.12.04 이 4개의 형식으로만 받게 처리
→ 월, 일은 1의 자리 숫자들에 대해서 04 or 4와 같이 자리에 상관없이 자유롭게 입력할 수 있다.
→ 연은 1~9999년 범위를 한정 / 월, 일은 1~2자리 숫자로 제한

 올바른 형식을 받았으면 다음으로 입력한 날짜가 존재하는 날짜인지도 조사했다. 이 두 개의 예외 상황에 대해 따로 예외클래스를 만들어서 throw를 이용해 임의로 던졌다.

 

import java.util.ArrayList;

public final class CalendarFormCheck {

    public static void isRightForm(String date) throws IncorrectCalendarFormException {
        // 정규표현식을 이용해서 올바른 날짜 양식을 받게끔 제한함
        // year => 1 ~ 9999
        // month => 2자리 정수
        // day => 2자리 정수
        // 중간 특수기호 : '.' '/' '-' ' ' 4개 문자로 제한
        // YYYY-MM-DD or YYYY/MM/DD or YYYY.MM.DD or YYYY MM DD
        if (!date.matches("[1-9][0-9]{0,3}[ -./][0-9]{1,2}[ -./][0-9]{1,2}")) {
            // YYYY-MM or YYYY/MM or YYYY.MM or YYYY MM
            if (!date.matches("[1-9][0-9]{0,3}[ -./][0-9]{1,2}")) {
                // YYYY
                if (!date.matches("[1-9][0-9]{0,3}")) {
                    // 모두다 만족 못할 시 false
                    throw new IncorrectCalendarFormException("올바른 날짜 형식이 아닙니다.");
                }
            }
        }
    }
    public static int[] splitDate(String date) throws IncorrectCalendarFormException, DateOutOfBoundsException {

        isRightForm(date);
        // ArrayList Generic을 이용해서 입력받은 문자열을 연, 월, 일로 나눔
        ArrayList<String> temp = new ArrayList<String>();
        int begin = 0;
        for (int i = 0; i < date.length(); i++) {
            // 문자 기준으로 나눔
            if (Character.isDigit(date.charAt(i)) == false) {
                temp.add(date.substring(begin, i));
                begin = i + 1;
            }
        }
        temp.add(date.substring(begin));

        // 문자열을 담은 ArrayList를 integer로 변환 후 배열에 저장
        int[] dateNum = new int[temp.size()];
        for (int i = 0; i < dateNum.length; i++) {
            dateNum[i] = Integer.parseInt(temp.get(i));
        }
        // "연도"만 입력한 경우 이미 isRightForm method에서
        // 검증을 완료했기에 따로 체크안해도 된다.
        
        // "연도 월"까지 입력한 경우
        if (dateNum.length == 2) {
            if (dateNum[1] < 1 || dateNum[1] > 12) {
                throw new DateOutOfBoundsException();
            }
        }
        // "연도 월 일"까지 입력한 경우
        if (dateNum.length == 3) {
            if (dateNum[1] < 1 || dateNum[1] > 12) {
                throw new DateOutOfBoundsException();
            } else {
                // 월 별로 입력한 일이 올바른지 점검
                // 1,3,5,7,8,10,12월은 => 1~31일
                // 4,6,9,11월은 => 1~30일
                // 2월은 해당연도가 윤년인지 판단 후 => 윤년) 1~29일 / 윤년X) 1~28일
                switch (dateNum[1]) {
                case 1:
                case 3:
                case 5:
                case 7:
                case 8:
                case 10:
                case 12:
                    if (dateNum[2] < 1 || dateNum[2] > 31) {
                        throw new DateOutOfBoundsException();
                    }
                    break;
                case 2:
                    if (CalendarDev.isLeaf(dateNum[0]) {
                        if (dateNum[2] < 1 || dateNum[2] > 29) {
                            throw new DateOutOfBoundsException();
                        }
                    } else {
                        if (dateNum[2] < 1 || dateNum[2] > 28) {
                            throw new DateOutOfBoundsException();
                        }
                    }
                    break;
                case 4:
                case 6:
                case 9:
                case 11:
                    if (dateNum[2] < 1 || dateNum[2] > 30) {
                        throw new DateOutOfBoundsException();
                    }
                    break;
                default:
                }
            }
        }
        return dateNum;
    }
}

 

 

 

public class DateOutOfBoundsException extends Exception {
    public DateOutOfBoundsException() {
        super("존재하지 않는 날짜입니다.");
    }

    public DateOutOfBoundsException(String message) {
        super(message);
    }
}
public class IncorrectCalendarFormException extends Exception {
    public IncorrectCalendarFormException() {
        super("올바른 날짜 형식이 아닙니다.");
    }

    public IncorrectCalendarFormException(String message) {
        super(message);
    }
}