첼러의 공식 또는 첼러의 합동식이라고 불리는 아래의 공식은 그레고리력 또는 율리우스력에서 특정 날짜가 무슨 요일인지 계산하는 공식입니다.
그레고리력
율리우스력
- h: 요일 ( 0 = 토, 1 = 일, 2 = 월, ..., 6 = 금 )
- q: 일
- m: 월
- K: 연도 뒷2자리 ( 연도를 100으로 나눈 나머지 )
- J: 세기 ( [연도 / 100] )
- mod는 나머지 연산을 의미하며, [ ]는 가우스 기호로 "X 보다 크지 않은 최대 정수"를 의미합니다.
⚠️ 주의사항
주의해야될 점은 1월과 2월을 계산할때는 값을 집어넣기전에 부가적인 작업이 필요합니다.
m
에 12를 더한 후 계산한다. ( 1월 = 13 , 2월 = 14 )K
와 J
를 설정한다.2000년 1월 1일
의 경우 q = 1
, m = 13
, K = 99
, J = 19
바로 위의 예시를 계산하면 (1+[(13×(13+1)÷5)]+99+[99÷4]+[19÷4]−2×19) mod7 = 0
즉, 2000년 1월 1일
은 토요일임을 확인할 수 있습니다.
아래의 모든 코드에서
Day
는 요일을 의미하며Date
는 날짜를 의미합니다.
자바를 이용해 달력을 출력하는 프로그램을 설계할때 요일 정보를 가져오는 방법은 크게 세가지입니다.
java.util.Calendar
사용하기2022년 1월 1일은 토요일
이렇게 기본값을 설정해두고 계산을 통해 요일 알아내기Zeller's Congruence
와 같이 공식을 사용해 요일 알아내기첫번째, 두번째 방법을 이용하면 좀 더 손쉽게 요일을 알아낼 수 있지만 이 두 방식은 정답지를 베끼는 느낌이라("자, 숫자 2를 기억해놓고 시험을 볼때는 3을 더해서 정답 5를 써내면 됩니다." 요런 느낌이랄까...) 첼러의 공식을 이용해 코드를 설계하였습니다.
📚 코드 구조
Calendar.java
main()
detectMinusOne()
CalendarPrinter.java
getDay()
isLeapYear()
getEndDate()
printCalendar()
💡 코드 실행순서
1.main()
에서 연도와 월을 입력
2.getDay()
에서 첼러의 공식으로 해당 월의 1일이 무슨 요일인지 구함
3.getEndDate()
로 해당 월의 마지막 날짜를 구함
4.printCalendar()
에서 위의 정보를 조합해 달력을 출력
main()
public static void main(String[] args) {
int month, year = 0;
Scanner sc = new Scanner(System.in);
CalendarPrinter calendarPrinter = new CalendarPrinter();
while (true) {
System.out.println("종료하려면 -1을 입력하시오.");
System.out.print("연도를 입력하시오 : ");
year = sc.nextInt();
if (detectMinusOne(year)) break;
System.out.print("월을 입력하시오 : ");
month = sc.nextInt();
if (detectMinusOne(month)) break;
calendarPrinter.printCalendar(year, month);
}
sc.close();
}
detectMinusOne()
메소드를 호출해 -1
을 입력받았을 경우에는 프로그램을 종료할 수 있도록 합니다.detectMinusOne()
private static boolean detectMinusOne(int year) {
if (year == -1) {
System.out.println("프로그램을 종료합니다.");
return true;
}
return false;
}
getDay()
static int getDay(int year, int month) {
int h = 0, q = 1; // h는 요일(0~6 == 토~일), q는 날짜
double m, K, J; // m은 월, K는 연도 마지막 두자리, J는 세기
if (month <= 2) { // 1, 2월은 월에 12를 더하고 년도에 1을 빼서 계산
m = month + 12;
K = (int)(year - 1) % 100;
J = (int)(year - 1) / 100;
} else {
m = month;
K = (int)year % 100; // 연도 마지막 두자리
J = (int)year / 100; // 세기
}
// Zeller's congruence
h = (q +
(int)floor((13 * (m + 1)) / 5) +
(int)K +
(int)floor(K / 4) +
(int)floor(J / 4) -
(int)(2 * J)
) % 7;
if (h < 0) { // 나머지 값 결과가 음수일 경우 값을 보정
h += 7;
}
//System.out.println("h = " + h);
return h;
}
Math.floor()
를 통해 계산하였으며 반환형이 double
형이기 때문에 정수형 변수 h
에 저장하기 위해 (int)
형변환 과정을 거쳤습니다.%
)을 할 경우 mod 연산과 값이 다르기때문에 값의 보정이 필요합니다. (자세한 내용은 Jhon Grib님의 블로그 참고)isLeapYear()
static boolean isLeapYear(int year) {
if (year % 4 == 0) {
if (year % 400 != 0 && year % 100 == 0) {
return false;
} else {
return true;
}
} else {
return false;
}
}
⚠️ 윤년
1. 서력 기원 연수가 4로 나누어 떨어지는 해는 윤년으로 한다.
2. 서력 기원 연수가 4, 100으로 나누어 떨어지는 해는 평년으로 한다.
3. 서력 기원 연수가 4, 100, 400으로 나누어 떨어지는 해는 윤년으로 둔다.
-출처: 위키백과
getEndDate()
static int getEndDate(int year, int month) {
if (isLeapYear(year) && month == 2) {
return 29;
} else {
switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
return 31;
case 4: case 6: case 9: case 11:
return 30;
case 2:
return 28;
default:
System.out.println("월을 제대로 입력하시오!");
System.exit(0);
return 0;
}
}
}
printCalendar()
void printCalendar(int year, int month) {
int day = getDay(year, month);
int date = 1, skipper = 0, endDay = getEndDate(year, month);
if (day == 0) {
skipper = 6;
} else {
skipper = day - 1;
}
System.out.printf("일\t월\t화\t수\t목\t금\t토\n" +
"--------------------------\n");
for (int i=0; i<6; i++) {
for (int j=0; j<7; j++) {
if (skipper > 0) {
System.out.printf("\t");
skipper -= 1;
} else if (date > endDay) {
System.out.printf("\t");
} else {
System.out.printf("%d\t", date);
date++;
}
}
System.out.println();
}
}
getDay()
와 getEndDate()
를 호출해 요일을 day
에 저장하고 해당 월의 마지막 날짜는 endDay
에 저장합니다.skipper
이라는 변수를 통해서 빈 공간을 출력하도록 설계하였습니다. ( 토요일은 6번 스킵, 월요일은 1번 스킵 )\t
를 이용해 칸을 조정하였습니다.프로젝트를 실행시켜보면 아래와 같이 2022년도의 달력이 출력됩니다.
윤년에 해당되는 2016년의 2월도 정상적으로 29일까지 출력되는 모습을 확인할 수 있습니다.
사용자로부터 연도와 월을 입력받는 코드는 while문을 통해 계속 입력받도록 설계하였기 때문에 아래와 같이 -1
을 입력하면 프로그램을 종료할 수 있습니다.
❗️해당 프로젝트의 전체 코드는 gerry-mandering 깃헙 에서 확인하실 수 있습니다.