
첼러의 공식 또는 첼러의 합동식이라고 불리는 아래의 공식은 그레고리력 또는 율리우스력에서 특정 날짜가 무슨 요일인지 계산하는 공식입니다.
그레고리력
율리우스력
- 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 깃헙 에서 확인하실 수 있습니다.