본문 바로가기

JAVA

Calendar 로 달력 만들기

1. Calendar 란?

 

JDK1.8 부터는 java.time 패키지가 CalendarDate 의 단점을 보안한 형태로 추가되었으나,

기존의 클래스를 알고 있어야 새로운 패키지의 활용에도 수월해지기 때문에 Calendar 클래스를 활용해 보고자 한다.

 

Calendar 클래스는 추상 클래스로 인스턴스를 생성할 수 없고 getInstance() 라는 static 메서드를 통하여 인스턴스를 생성할 수 있다.

📌 추상 클래스에서 인스턴스를 생성할 수 없는 이유

      추상 클래스에는 추상 메서드가 있을 수 있다는 가능성으로 인하여 불완전한 설계도가 되어

      이로 인하여 자신의 인스턴스를 만들 수가 없어진다.

      대신 익명 클래스 등을 활용하여 추상 메서드를 작성하면 외부에서 인스턴스를 생성할 수도 있다.

📌 getInstance() 가 static 인 이유

      추상 클래스인 Calendar 클래스는 위의 이유로 인하여 인스턴스 생성을 하지 못 한다.

      때문에 getInstance() 가 클래스 메서드가 아닌 인스턴스 메서드였다면, 

      이를 호출하기 위하여 또 다른 인스턴스로 호출을 해야 한다는 것인데 이것이 불가능하기 때문이다.

Calendar calendar = Calendar.getInstance();

getInstance() 메서드는 국제화(i18n)가 적용되어 있어 시스템을 실행하는 국가 또는 지역 설정에 따라

GrogorianCalendar(그레고리력) 와 BuddhishCalendar(불기) 의 인스턴스를 반환한다.

 

만일 getInstance() 가 아닌 직접적으로 GrogorianCalendar 또는 BuddhishCalendar 를 인스턴스로 생성하여 

코드를 작성해야 했다면, GrogorianCalendar 로 작성한 코드는 BuddhishCalendar 를 사용하는 국가에서 제대로 작동하지 못할 가능성이 생기게 된다.

 

즉, getInstance() 의 사용으로 인하여 코드의 활용성이 높아진 것이다.

 

 

2. Date 와 Calendar 간의 변환

 

DateCalendar 가 추가되기 전부터 존재하던 초기 클래스이다.

때문에 현재는 많이 사용하지 않지만, 여전히 사용되고 있는 곳이 있기 때문에 DateCalendar 이 둘 다 알아둬야 한다.

 

Date 관련 메서드는 많은 수가 deprecated 되어 있어서 활용에 주의해야 하는데,

Date 관련 생성자는 new Date()new Date(long date) 를 사용할 수 있다.

 

1️⃣ Calendar 👉 Date 

Calendar calendar = Calendar.getInstance();
Date date = new Date(calendar .getTimeInMillis());

2️⃣ Date 👉 Calendar 

Date date = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);

 

 

3. Calendar 활용하기

 

Calendar 에는 set 을 별도로 하지 않으면 현재를 기준으로 자동 적용된다는 특징이 있다.

이를 바탕으로 몇 가지 메서드를 적용해 보자.

Calendar calendar = Calendar.getInstance();
		
System.out.println("현재 연도 : " + calendar.get(Calendar.YEAR));
System.out.println("현재 달 : " + (calendar.get(Calendar.MONTH) + 1));
System.out.println("현재 일 : " + calendar.get(Calendar.DAY_OF_MONTH));
System.out.println("현재 요일 : " + calendar.get(Calendar.DAY_OF_WEEK));
System.out.println("현재 달의 마지막 날 : " + calendar.getActualMaximum(Calendar.DATE));
		
System.out.println("현재 시 : " + calendar.get(Calendar.HOUR));
System.out.println("현재 분 : " + calendar.get(Calendar.MINUTE));
System.out.println("현재 초 : " + calendar.get(Calendar.SECOND));
System.out.println("현재 Epoch Time : " + calendar.get(Calendar.MILLISECOND));

📌 Epoch Time

      일명 유닉스 시간이라고도 불리는 에포크 타임은 1970년 1월 1일 00:00:00 로부터의 경과 시간을 초 단위로 환산하여        기록된 시간으로 이는 정수로 나타내는데,

      자바에서 정수를 나타내는 타입 중 가장 큰 범위를  저장할 수 있는 long 으로 변환되어 활용 가능하다.

      특히 자바에서는 날짜 간의 산술 연산이 불가하기 때문에 이러한 경우 적용된다.

 

위의 예시에서는 현재를 기준으로 코드를 짜보았다.

Calendar 에는 여러 상수가 static 으로 존재하는데 get() 메서드의 field 값으로 사용 가능하다.

 

Calendar 상수 중에는 사용에 주의를 기울여야 하는 상수가 있는데, 바로 Calendar.MONTH 이다.

과거 Calendar 를 사용하기 전에는 month 를 나타내기 위하여 배열을 사용했는데

int month = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];

배열에서의 index 사용이 Calendar 에도 그 활용 흔적이 남아 0 부터 11 까지 표기된다.

즉, 출력 결과에서 정확한 값을 출력받고 싶다면 '+1' 해줘야 원하는 값으로 출력할 수 있게 된다.

 

위의 예시에서는 현재를 기준하여 작성해 보았다.

임의의 시간을 설정하여 Calendar 를 사용하고 싶다면 set() 메서드를 활용하면 된다.

void set(int field, int value)
void set(int year, int month, int date)
void set(int year, int month, int date, int hourOfDay, int minute)
void set(int year, int month, int date, int hourOfDay, int minute, int second)

Calendar 클래스의 메서드 중에는 add() 와 roll() 이라는 조금 특수한 메서드도 있다.

 

1️⃣ add() 메서드

// 출력 결과를 보기 쉽게 하기 위하여 SimpleDateFormat 을 사용
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

Calendar calendar = Calendar.getInstance();
calendar.set(2022, 0, 1);
System.out.println(dateFormat.format(calendar.getTime()));

// 현재 day + 1
calendar.add(Calendar.DATE, 1);
System.out.println(dateFormat.format(calendar.getTime()));

// 현재 month + 1
calendar.add(Calendar.MONTH, 1);
System.out.println(dateFormat.format(calendar.getTime()));

// 현재 year - 1
calendar.add(Calendar.YEAR, -1);
System.out.println(dateFormat.format(calendar.getTime()));

2️⃣ roll() 메서드

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

Calendar calendar = Calendar.getInstance();
calendar.set(2022, 0, 1);
System.out.println(dateFormat.format(calendar.getTime()));

// 현재 day + 30
calendar.roll(Calendar.DATE, 30);
System.out.println(dateFormat.format(calendar.getTime()));

// 현재 month + 1
calendar.roll(Calendar.MONTH, 1);
System.out.println(dateFormat.format(calendar.getTime()));

// 현재 month - 6
calendar.roll(Calendar.MONTH, -6);
System.out.println(dateFormat.format(calendar.getTime()));

// 현재 year -1 
calendar.roll(Calendar.YEAR, -1);
System.out.println(dateFormat.format(calendar.getTime()));

 

이 둘은 비슷한 기능을 수행하지만 roll() 메서드의 경우 지정한 필드 이외는 고정한 채 변경한다는 특징이 있다.

또한 공통적으로는 월별로 이동 시에 마지막 일자에 대해 크게 영향을 받게 되는데,

위의 예시처럼 31일에서 한 달을 이동하게 하였더니 28일로 이동하는 것을 확인할 수 있다.

이는 2월에는 31일을 나타낼 수 없기 때문에 자동으로 2월의 마지막 일자로 변경된 것이다.

이렇게 변경된 값은 별도로 변경하지 않으면 계속 고정되어 월별 이동으로 하더라도 8월 28일의 예시처럼 지속된다.

 

지금까지 Calendar 클래스의 메서드 활용에 대해 알아보았다.

이제 이를 활용하여 달력을 만들어 보자.

 

먼저 달력을 만들기 전에는 달에 대한 2 가지에 대한 정보가 필요하다.

1️⃣ 해당하는 달의 시작 요일 (ex) 월, 화...)
2️⃣ 해당하는 달의 마지막 날짜 (ex) 28, 29, 30, 31)

이를 고려하면 다음과 같이 만들 수 있다.

// 해당 달의 시작 요일
int day = calendar.get(Calendar.DAY_OF_WEEK);

// 해당 달의 마지막 날짜 
int lastDate = calendar.getActualMaximum(Calendar.DATE);

그 다음에는 1일부터 마지막 날짜까지 출력을 해야 한다.

이는 for 문을 활용해보자.

for(int i = 1; i <= 42; i++) {
	System.out.printf("%3d", i);
}

// 출력 결과
1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

📌 42 번 반복하는 이유

     한 달이 최대 가질 수 있는 날짜 칸이 42(6주 * 7일) 이기 때문이다.

 

이번에는 가독성을 위하여 주차를 나타낼 수 있도록 코드를 바꿔보자.

for(int i = 1; i <= 42; i++) {
	System.out.printf("%3d", i);
    if(i % 7 == 0) {
		System.out.println();
	}
}

// 출력 결과
 1  2  3  4  5  6  7
  8  9 10 11 12 13 14
 15 16 17 18 19 20 21
 22 23 24 25 26 27 28
 29 30 31 32 33 34 35
 36 37 38 39 40 41 42

 

그리고 월의 시작 요일에 맞춰 1일이 시작할 수 있도록 변경해줘야 한다.

앞서 월의 시작 요일을 구했었다.

만일 시작 요일이 토요일이라면 int day 에 저장되는 값은 '7'이게 되는 것이고,

현재 '1' 위치에 있는 1일을 '7'로 옮겨야 하는 것이다.

 

이는 for 문의 7번째 출력이 1이어야 한다는 말이다.

i 가 7 일 때 일련의 연산을 통해 출력값이 1이 되어야 한다.

1 = 7 - day + 1

즉 이러한 연산이 적용되면 되는 것이다.

하지만 이렇게 되면 i 가 6일 때까지는 음수와 0이 출력된다는 문제점이 있다.

달력에서는 보이면 안 되기 때문에 적절한 처리를 해줘야 한다.

 

양수일 때는 위의 연산식을 적용한 숫자가 출력이 되어야 하며,

음수와 0일 때에는 공백을 출력하도록 코드를 변경해보자.

for(int i = 1; i <= 42; i++) {
	int tmp = i - day + 1;
    if(tmp > 0) {
		System.out.printf("%3d", tmp);
	}
		else {
		System.out.printf("%3c", ' ');
	}
	
    if(i % 7 == 0) {
		System.out.println();
	}
}

// 출력 결과
2022-10 기준

                    1
  2  3  4  5  6  7  8
  9 10 11 12 13 14 15
 16 17 18 19 20 21 22
 23 24 25 26 27 28 29
 30 31 32 33 34 35 36

하지만 아직도 문제가 남아있다.

이렇게 하면 단순히 날짜가 출력이 되며, 마지막 날짜 이후의 숫자까지 불필요하게 출력된다.

원하는 달의 마지막 날짜까지만 출력되도록 코드를 바꾸려면

달에 따라 마지막 날짜가 달라지기 때문에 조건식이 필요하게 된다.

for(int i = 1; i <= 42; i++) {
	int tmp = i - day + 1;
    if(tmp > 0) {
		System.out.printf("%3d", tmp);
	}
		else {
		System.out.printf("%3c", ' ');
	}
	
    if(i % 7 == 0) {
		System.out.println();
	}
    
    if(tmp >= lastDate) {
		break;
	}
}


// 출력 결과
2022 10월 기준

                    1
  2  3  4  5  6  7  8
  9 10 11 12 13 14 15
 16 17 18 19 20 21 22
 23 24 25 26 27 28 29
 30 31

 

전체 코드로 보면 다음과 같이 나타낼 수 있다.

		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		
		Calendar calendar = Calendar.getInstance();
		
		calendar.set(Calendar.YEAR, nextInt("연도 입력 >> "));
		calendar.set(Calendar.MONTH, nextInt("월 입력 >> ") -1 );
		calendar.set(Calendar.DATE, 1);
		
		System.out.println("요청하신 달력 : " + dateFormat.format(calendar.getTime()));
		
		// 해당 달의 시작 요일
		int day = calendar.get(Calendar.DAY_OF_WEEK);
		
		// 해당 달의 마지막 날짜 
		int lastDate = calendar.getActualMaximum(Calendar.DATE);
	
		
		for(int i = 1; i <= 42; i++) {
			int tmp = i - day + 1;
			if(tmp > 0) {
				System.out.printf("%3d", tmp);
			}
			else {
				System.out.printf("%3c", ' ');
			}
		    if(i % 7 == 0) {
				System.out.println();
			}
		    if(tmp >= lastDate) {
				break;
			}
		}

 

'JAVA' 카테고리의 다른 글

쓰레드 실행 제어  (0) 2022.10.13
싱글쓰레드와 멀티쓰레드  (0) 2022.10.10
SimpleDateFormat : 날짜와 시간 출력하기  (0) 2022.10.07
Serializable 과 File 활용하기  (0) 2022.10.05
문자 기반 스트림 : Reader, Writer  (0) 2022.10.03