본문 바로가기

Backend/Java

[JAVA] 날짜와 시간 출력을 위한 Calendar, Date 클래스

 


날짜와 시간 출력을 위한 Calendar, Date 클래스 알아보기


 

 

1. Calendar 클래스


자바에서 제공하는 대표적인 날짜와 시간을 다루는 클래스로, 싱글톤 방식의 클래스이다.

 

싱글톤이란, 생성자가 여러번 호출되더라도 인스턴스 값이 하나만 존재하도록 보장하는 방식으로 인스턴스 객체 하나에만 접근할 수 있도록 한다.

 

Calendar tomorrow = Calendar.getInstance();

 

싱글톤의 특성상 하나의 객체를 여러 참조변수가 공유해서 쓰지만 Calendar 클래스의 구조는 공유되는 공간만들어진 객체마다 따로 갖는 공간이 함께 존재한다. Calendar의 경우 달력이라는 공간은 공유하되, 저장된 오늘 날짜(현재 날짜)는 별도로 각각 따로 저장한다.

 

 

 

2. Calendar 클래스의 날짜 추출 ( .get )


Calendar 클래스에는 static 변수(final - 변하지 않음)가 여러개 있다.

System.out.println("년도 상수 : " + Calendar.YEAR);
System.out.println("월 상수 : " + Calendar.MONTH);
System.out.println("일 상수 : " + Calendar.DATE);

 

앞서 저장된 오늘 날짜는 달력이라는 공간과 별개로 각각 따로 저장한다고 했다. 그렇게 따로 저장된 '오늘 날짜' 에 대한 상수를 YEAR, MONTH, DATE 를 통해 호출할 수 있다.

 

특별히 날짜를 지정하지 않으면 생성된 객체의 날짜는 오늘 날짜로 설정된다. 하지만 해당 코드를 호출한 날짜는 2024년 10월 20일로 결과값만을 보고서는 당췌 며칠인지 알 수가 없다. 만약 우리가 접하는 형식으로 데이터를 추출하기 위해서는 객체참조변수.get() 형식을 이용해야 한다.

 

Calendar today = Calendar.getInstance();

System.out.println("today 객체의 년도 : " + today.get(1));
System.out.println("today 객체의 년도 : " + today.get(Calendar.YEAR));
System.out.println("today 객체의 월 : " + today.get(Calendar.MONTH));
System.out.println("today 객체의 일 : " + today.get(5));

 

이러한 차이가 발생하는 이유는 Calendar가 년, 월, 일, 시, 분, 초를 int형으로 저장하기 때문이다. 날짜 데이터가 String으로 만들어졌다면 "2024-10-20"에서 "2024" 데이터를 추출하기 위해 substring 메소드를 사용하면 되겠지만 int 형의 데이터는 그럴 수 없다. 따라서 별도의 메소드를 이용해야 원하는 값을 얻을 수 있는 것이다. ( 그 중 하나가 .get )

 

 

그런데 가만 보자하니... 이번에는 월의 표시가 이상하다.

분명 10월인데 Calendar.MONTH 형식으로는 10이 아닌 9가 표시되는 것이다. 이 현상은 Calendar.MONTH 가 1부터 12가 아닌 0~12의 값을 반환하기 때문이다.

 

int i = today.get(Calendar.MONTH);
System.out.println("월 : (0~11) : " + i + "월"); // 9
System.out.println("월 : (1~12) : " + (i+1) + "월"); // 10

 

 

우리가 사용하는 1월부터 12월까지의 형태에 맞춰 추출하기 위해서는 추출한 값에 임의로 +1을 해주어야 한다.

 

 

Calendar today = Calendar.getInstance();

i = today.get(Calendar.DATE);
System.out.println("일 : " + i);

i = today.get(Calendar.DAY_OF_WEEK);
System.out.println("요일(1~7, 1:일요일, 2:월요일 ...) : " + i);

 

Calendar에는 요일을 추출하는 Calendar.DAY_OF_WEEK 도 있다.

엥? 위 예시를 보면 그냥 상수가 출력됐는데? 하겠지만, 우리가 달력을 펼쳤을 때 한 주의 시작(줄바꿈 이후)을 일요일부터 표시하고 있다. 이를 이용한 방법으로 1부터 7까지의 숫자 중, 1을 일요일로 설정하여 2 = 월요일, 3 = 화요일... 7 = 토요일을 나타내는 방법이다.

 

 

Calendar today = Calendar.getInstance();

i = today.get(Calendar.WEEK_OF_YEAR);
System.out.println("금년의 몇째주 : " + i);
i = today.get(Calendar.WEEK_OF_MONTH);
System.out.println("현재 달의 몇째주 : " + i);
i = today.get(Calendar.DAY_OF_YEAR);
System.out.println("현재 년도의 며칠 " + i);

 

이외에도 년도 기준으로 몇째주가 되었는지 알려주는 WEEK_OF_YEAR, 현재 달을 기준으로 몇째주가 흘렀는지 알려주는 WEEK_OF_MONTH, 년도 기준으로 며칠이 흘렀는지를 표시하는 DAY_OF_YEAR 도 있다.

 

 

 

3. Calendar 클래스의 시간 추출


Calendar 클래스는 시간에 관한 데이터 값도 가지고 있다.

 

Calendar today = Calendar.getInstance();

System.out.println("오전_오후(0:오전, 1:오후) : " + today.get(Calendar.AM_PM));
System.out.println("시간(0~11) : " + today.get(Calendar.HOUR));
System.out.println("시간(0~23) : " + today.get(Calendar.HOUR_OF_DAY));
System.out.println("분(0~59) : " + today.get(Calendar.MINUTE));
System.out.println("초(0~59) : " + today.get(Calendar.SECOND));
System.out.println("1000분의 1초(0~999) : " + today.get(Calendar.MILLISECOND));

 

오전-오후 여부(AM_PM) , 0-11시 기준 시간(HOUR) , 24시 기준 전체 시간(HOUR_OF_DAY) , 분(MINUTE) , 초(SECOND) 하다못해 밀리초(MILLISECOND)도 지원한다.

 

 

 

4. 날짜와 시간의 설정 ( .set )


우리가 꼭 오늘 날짜만을 추출하지는 않을 것이다. 지정된 날짜, 년도만 바뀐 날짜, 일만 바뀐 날짜, 하다 못해 어제 단순 어제 날짜를 추출하고 싶을 때는 어떻게 해야할까?

 

바로 .set() 메소드를 이용하는 것이다.

 

Calendar date1 = Calendar.getInstance();

// date1의 현재 날짜를 2015년 8월 15일로 설정
date1.set(2015, 7, 15);
prnDate(date1);

 

우선 Calendar.getInstance(); 를 통해 값을 받아오고, date1로 Calendar 객체를 만들었다. 그리고 date1에 .set() 메소드를 붙인 코드다. set 안에 원하는 년도, 월, 일의 int 값을 대입하면 해당 날짜로 출력되는 모습을 알 수 있다.

 

 

더보기
public static void prnDate(Calendar date) {
    String[] weekday = {"", "일", "월", "화", "수", "목", "금", "토"};
    
    int y = date.get(Calendar.YEAR);
    int m = date.get(Calendar.MONTH)+1;
    int d = date.get(Calendar.DATE);
    int w = date.get(Calendar.DAY_OF_WEEK);

    System.out.println(y + "년 " + m + "월 " + d + "일 " + weekday[w] + "요일");
}

 

출력과 관련해서는 prnDate 라는 메소드를 자체 제작했다. 앞으로 prnDate는 " 2024년 10월 20일 일요일"의 형태를 띌 것이다. MONTH는 1~12까지의 값을 추출하기 위해 임의로 +1 했다.

Calendar date2 = Calendar.getInstance();

// date2의 년도를 2016년으로
date2.set(Calendar.YEAR, 2016); // 2016-10-20
prnDate(date2);

// date2의 일을 30일로
date2.set(Calendar.DATE, 30); // 2016-10-30
prnDate(date2);

// date2의 일을 35일로 -> 알아서 월-일 관계 계산
date2.set(Calendar.DATE, 35); // 2016-11-4
prnDate(date2);

 

위와 같은 예제로 set 메소드 매개변수에 (변경하고자 하는 위치, 변경하고자 하는 값) 을 대입하면 년, 월, 일 등 일부 값만을 변경할 수도 있다.

 

 

Calendar date2 = Calendar.getInstance();

// date2의 일을 1일로 수정 후 다시 -1일로 수정하면
date2.set(Calendar.DATE, 1); // 2016-11-01
date2.set(Calendar.DATE, -1); // 2016-10-30
prnDate(date2);

// date2에서 3개월 후의 날짜
int m = date2.get(Calendar.MONTH);
date2.set(Calendar.MONTH, m+3);
prnDate(date2);

 

숫자 연산을 사용해도 된다. 달력을 공유하고 있어 년-월-일간 변경이 자유롭다. 날짜가 1일인 상태에서 -1을 대입해도 알아서 10월 30일(10월의 마지막)으로 표기한다.

 

3개월 뒤의 값을 보기 위해  get 메소드로 값을 따로 출력하고(값을 '계산'하기 위해 set을 사용할 때 필요한 위한 필수과정), 해당 값에 +3 을 해도 알아서 년도까지 변경되어 2017년 1월 30일 값이 표기된다. 하지만 이 방법은 상대적인 날짜 차이 계산을 위한 적절한 방법은 아니다. 바람직한 방법에 관해서는 밑에 Calendar의 .add 메소드 이용에 대해 다루겠다.

 

 

시간에 대한 값도 .set 메소드를 이용해 수정할 수 있다.

 

더보기
public static void prnTime(Calendar t) {
    System.out.println("time : "
                + t.get(Calendar.HOUR_OF_DAY) + "시 "
                + t.get(Calendar.MINUTE) + "분 "
                + t.get(Calendar.SECOND) + "초 "
                + t.get(Calendar.MILLISECOND) + "밀리초");
}

 

시간에 대한 출력 메소드는 다음과 같은 코드로 작성해보았다.

Calendar time1 = Calendar.getInstance();
Calendar time2 = Calendar.getInstance();

// 10시 10분 10초
time1.set(Calendar.HOUR_OF_DAY, 10);
time1.set(Calendar.MINUTE, 10);
time1.set(Calendar.SECOND, 10);

// 11시 30분 10초
time2.set(Calendar.HOUR_OF_DAY, 11);
time2.set(Calendar.MINUTE, 30);
time2.set(Calendar.SECOND, 10);

prnTime(time1);
prnTime(time2);

 

 

시간 계산도 무리없다.

long t1 = time1.getTimeInMillis();
long t2 = time2.getTimeInMillis();
System.out.println(t1);
System.out.println(t2);

long dif = t2-t1;
System.out.println("time1과 time2의 차이는 " + dif + "밀리초 입니다.");
System.out.println("time1과 time2의 차이는 " + (dif/1000) + "초 입니다.");
System.out.println("time1과 time2의 차이는 " + (dif/1000/60) + "분 입니다.");

밀리초는 long 자료형이라 선언을 long 으로 지정했다. 

 

위와 같이 밀리초를 이용해 시간 계산도 가능하다.

 

 

 

5. 상대적인 날짜의 계산 ( .add )


사용법 자체는 set에서 다뤘던 것과 다르지는 않지만 상대적인 날짜 차이를 계산하기 위해서 set은 부적절하고 불편한 메소드다. 앞서 예제를 기억하는 가? 우리는 3개월 뒤의 값을 출력하기 위해 get 메소드를 이용해 현재 달을 변수로 따로 받고, 해당 변수에 +3 을 하여 set 메소드에 적용시켰다.

// date2에서 3개월 후의 날짜
int m = date2.get(Calendar.MONTH);
date2.set(Calendar.MONTH, m+3);
prnDate(date2);

 

 

그런데 만약 get 메소드를 이용해 값을 받지 않고 적용시키면 어떻게 될까? 3일 전의 날짜를 출력하는 예제 코드를 통해 알아보자.

더보기
private static String dateToString(Calendar c) {
    String cDate = c.get(Calendar.YEAR) + "년 "
            + (c.get(Calendar.MONTH)+1) + "월 "
            + c.get(Calendar.DATE) + "일 ";

    return cDate;
}

 

이번에는 날짜를 출력하는 dateToString 메소드를 만들어 사용하겠다.

 

System.out.println("== 오늘 날짜 ==");
Calendar date = Calendar.getInstance();
System.out.println(dateToString(date));

date.set(Calendar.DATE, -3);
System.out.println(dateToString(date));

 

분명 DATE 에 -3 을 적용했지만 어째서인지 10월 17일이 아닌 9월 27일이란 엉뚱한 값이 출력됐다.

이처럼 set을 이용해 계산을 하기 위해서는 get 메소드로 날짜를 얻어내고 → 해당 날짜에 계산값을 적용한 뒤 → set 메소드를 이용해서 설정해야 한다. 이는 굉장히 불편한 방법으로 날짜 계산할 일이 생긴다면 add 메소드를 이용하는 것이 좋다. set은 날짜를 '설정'하는 메소드로 알아두자.

 

 

System.out.println("== add로 연산한 3일 전 ==");
date.add(Calendar.DATE, -3);
System.out.println(dateToString(date));

System.out.println("== 3개월 전 ==");
date.add(Calendar.MONTH, -3);
System.out.println(dateToString(date));

System.out.println("== 15일 전 ==");
date.add(Calendar.DATE, 15);
System.out.println(dateToString(date));

System.out.println("== 30일 후 ==");
date.add(Calendar.DATE, 30);
System.out.println(dateToString(date));

 

 

 

6. 입출력용 Date


앞서 Calendar 클래스를 통해 년, 월, 일, 요일 등의 날짜를 형식에 맞춰 출력하고 싶을 때 코드를 따로 작성했다. 굳이 이런 번거로운 행동을 한 이유는 무엇일까?

 

Calendar date1 = Calendar.getInstance();
System.out.println(date1); // 참조변수 출력

 

java.util.GregorianCalendar[time=1729361845908,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Seoul",offset=32400000,dstSavings=0,useDaylight=false,transitions=30,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2024,MONTH=9,WEEK_OF_YEAR=43,WEEK_OF_MONTH=4,DAY_OF_MONTH=20,DAY_OF_YEAR=294,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=3,AM_PM=0,HOUR=3,HOUR_OF_DAY=3,MINUTE=17,SECOND=25,MILLISECOND=908,ZONE_OFFSET=32400000,DST_OFFSET=0]

 

Calendar 객체의 date1 코드를 출력하니 위와 같은 결과가 출력된다. 보아하니 year, month 등이 나오기는 하는데.. 형식이 영 눈에 띄지는 않는다.

 

그래서 Calendar 클래스만을 이용해 2024년 10월 20일 일요일의 형태로 추출하고 싶다면

public class Class_Calendar02 {

    // static 메소드에서 인스턴스 변수 사용 불가
    static String[] weekday = {"", "일", "월", "화", "수", "목", "금", "토"};

    public static void main(String[] args) {
    
        Calendar date1 = Calendar.getInstance();

        // Calendar는 달력을 위한 클래스
        // Calendar에 저장한 날짜를 출력하기 위해 날짜 출력 양식을 지원하는 java.util.Date 클래스로 변환 후 출력
        prnDate(date1);
    }

    public static void prnDate(Calendar date) {
        int y = date.get(Calendar.YEAR);
        int m = date.get(Calendar.MONTH)+1;
        int d = date.get(Calendar.DATE);
        int w = date.get(Calendar.DAY_OF_WEEK);

        System.out.println(y + "년 " + m + "월 " + d + "일 " + weekday[w] + "요일");
    }
}

 

일일이 값을 추출해 형식을 원하는 대로 지정하는 코드를 짜주어야 했던 것이다.

 

그런데 매번 이런 코드를 작성하려면 얼마나 번거로운가. 그래서 간편하게 날짜와 시간을 추출해주는 Calendar의 입출력용 클래스 DATE 를 사용한다.

 

System.out.println("Calendar 날짜를 Date형식으로 변환");
Calendar today = Calendar.getInstance();
Date date = today.getTime(); // Calendar -> Date
// today.setTime(date) // Date -> Calendar
System.out.println(date);

 

Calendar의 값을 .getInstance() 로 받아주고 → 받은 값에 .getTime() 메소드를 이용해 Date 객체로 변환하면 위와 같은 형식으로 손쉽게 출력할 수 있다. 앞서 우리가 지정했던 ' 2024년 10월 20일 ' 의 형태로 출력하기 위해서는 SimpleFormatDate라는 포맷 지정 클래스를 이용해야 한다. 이는 다음 포스트로 다룰 예정이다.

 

 

// 생성자 Date(매개변수)가 없어짐
Date d2 = new Date(2024, 5, 1, 20, 0, 0);
System.out.println(d2);

// date 는 입출력 용으로 쓰일 뿐 날짜 계산으로는 잘 쓰이지 않음
d2.setYear(2015);
System.out.println(d2);

 

생성자 Date(매개변수) 가 없고, set - 으로 날짜 변경도 쓰이지 않게 됐다. 그냥 입출력 용으로만 사용하자.

 

 

 

7. 입력에 따라 이전달, 다음달 달력을 출력하는 예시


import java.util.Calendar;
import java.util.Scanner;

public class Class_Calendar05 {

    static Scanner sc = new Scanner(System.in);

    public static void main(String[] args) {

            Calendar startDay = Calendar.getInstance(); // 1일자를 현재 날짜로 설정
            Calendar endDay = Calendar.getInstance(); // 말일자를 현재 날짜로 설정

            startDay.set(Calendar.DATE, 1);
            endDay.set(Calendar.DATE, endDay.getActualMaximum(Calendar.DATE));

            while(true) {

                prnTitle(startDay);
                prnCalendar(startDay, endDay);

                System.out.print("\n[이전달(1)] | [다음달(2)] | [종료(3)] ");
                int input = Integer.parseInt(sc.nextLine());
                if(input == 3) break;

                if(input == 1) startDay.add(Calendar.MONTH, -1);
                else startDay.add(Calendar.MONTH, 1);

                endDay.set(
                        startDay.get(Calendar.YEAR),
                        startDay.get(Calendar.MONTH),
                        startDay.getActualMaximum(Calendar.DATE));
            }
    }

    private static void prnCalendar(Calendar sDay, Calendar eDay) {
        int week = sDay.get(Calendar.DAY_OF_WEEK);
        for(int i = 1; i < week; i++) System.out.print("   ");
        for(int i = 1; i <= eDay.get(Calendar.DATE); i++) {
            System.out.printf("%2d ", i);
            if(week++ % 7 == 0) System.out.println();
        }
    }

    private static void prnTitle(Calendar startDay) {
        System.out.println();
        System.out.println(startDay.get(Calendar.YEAR) + "년 " + (startDay.get(Calendar.MONTH)+1) + "월 ");
        System.out.println("--------------------------------------------------");
        System.out.println("Su Mo Tu We Th Fr Sat");
        System.out.println("--------------------------------------------------");
    }

}