본문 바로가기
Java/Java Basic

[Java Basic] 29. 날짜 및 시간 관련 클래스

by Rosmary 2022. 8. 24.
728x90
반응형

 

 

 

IT에서 날짜와 시간은 매우 중요하다. 컴퓨터의 계산 속도가 워낙 빠르다 보니, 인간이 며칠 걸려서 진행해야 할 일들도 단 몇 초면 끝나게 된다. 그렇기 때문에 복잡한 프로그램을 하나 개발해서 테스트하다가 문제가 발생하면 어디서 문제가 발생했는지 쉽게 발견하기가 어려운 경우가 생긴다. 이 때문에 거의 모든 프로그래머들은 프로그램 개발 시 프로그램의 동작 행위를 일일이 기록할 수 있는 로그라는 것을 별도의 파일에 작성하게 한다. 프로그램 상에서 동작의 문제가 발생했을 때 이 로그를 참조하면 어디서 문제가 발생했는지 쉽게 파악할 수 있다. 

 

그리고 대부분의 로그는 발생 시간도 굉장히 중요하다. 특히 다량의 데이터를 처리하거나 네트워크 통신이 필요한 프로그램의 경우 더욱 중요한데, 통신이 안되거나 데이터가 많아 처리에 시간이 오래 걸려 동작을 안하는 것인지, 아니면 프로그램 자체의 문제인 것인지 확인하려면 이벤트가 발생한 시간에 대한 정보도 필요하기 때문이다.

 

필자가 개인 프로젝트로 만든 프로그램의 로그 기록이다. 각 줄의 가장 앞부분에 이벤트가 발생한 시간이 기록되어 있다.

 

 

이번 포스팅에서는 Java에서 날짜와 시간 표시에 사용되는 클래스들과 그들의 관계, 그리고 시간을 예쁘게(?) 표시할 수 있는 방법에 대해 알아보려 한다.

 

 

 

 

1. 날짜, 시간 관련 클래스

 

Java에서 제공하는 날짜와 시간 클래스는 크게 세 가지가 있다. Calendar, Date, LocalDateTime이다. 이 중 Date는 Java의 초기버전부터 제공되어 왔으나, 몇몇 기능의 누락으로 인해 Calendar가 태어났다. 그럼에도 불구하고 날짜와 시간을 조금 더 효율적으로 다루기 위해 LocalDate, LocalTime과 함께 LocalDateTime이라는 클래스가 탄생하게 된다.

 

Calendar와 Date는 java.util 패키지에 속한 클래스고, LocalDateTime과 연관 클래스는 전부 java.time 패키지에 속해있다. 클래스는 서로 다르지만 나중에 개발된 클래스가 이전에 존재하는 클래스를 어느정도 참조했기에 서로 형 변환에는 문제가 없다.

 

 

 

먼저 Date와 Calendar 클래스에 대해 알아본 뒤, LocalDateTime 클래스에 대해 살펴보려 한다.

 

 

 

(1) java.util.Date 클래스

 

Date 클래스로 날짜/시간을 저장하려면 클래스 인스턴스화를 진행해야 한다. 생성자에 매개 변수가 존재하지 않는다면 현재 시간으로 Date 객체가 생성되며, 년, 월, 일 순서대로을 정수형으로 매개 인자로 지정하면 해당 매개변수의 값에 맞는 날짜가 Date 객체에 저장된다.

 

 

 

그런데, 자세히 보면 조금 특이한 점이 보인다. 년도는 2022년임에도 불구하고 인자값이 122이고, 광복절을 8월임에도 인자값이 7이다. Date 클래스에서 년도는 기준년도가 1900년으로 되어 있다. 매개 인자값은 이 기준 년도값과 합산된 값으로 Date에 저장된다. 즉, 1900 + 122 = 2022가 되어 Date 객체에 2022년이 저장되는 것이다.

 

월은 어떻게 된 걸까? 월 역시 1월이 1이 아닌 0부터 지정되어 있어서 실제 값보다 1이 작은 값이 매개 인자로 들어가야 한다. 즉, 1월은 0으로 표시해야되며 8월은 7로 표시해야한다. Date 클래스를 사용하다보면 이러한 점이 불편할 때가 한 두 번이 아니다보니 LocalDateTime과 같은 새로운 클래스가 생성되는 것이다.

 

이 Date 클래스는 getTime()이라는 매서드를 가지는데, 이 매서드의 기능은 현재의 일시를, 1970년 1월 1일 0시를 기준으로 경과된 시간을 밀리초 단위로 반환하는 역할을 한다. 

 

eclipse에서 제공하는 Date.getTime() 매서드의 설명

 

이 매서드를 이용하면 특정 일자 사이 흐른 시간을 계산하는 것도 가능해진다. 

 

 

 

Date 클래스는 get으로 시작하는 이름의 매서드를 여러 개 가지고 있는데, 대부분 날짜와 시간의 특정 값을 추출하기 위한 기능을 가진다. 예를 들어, Date 객체에 저장된 년도를 추출하기 위해서는 getYear()를, 월은 getMonth() 등등이 있다.

 

 

 

getYear()와 getMonth()의 경우 앞서 설명한 Date 클래스의 특성으로 인해 의도하지 않은 값이 반환되는데, 이는 코딩 시 고려해야할 부분이다. 반환되는 값에 년도는 1900을, 월은 1을 합산한 값을 화면에 표시하면 되기 때문에 어렵지는 않지만 잊어버릴 경우 파급효과가 엄청나 주의를 기울여야 한다.

 

get으로 시작하는 매서드 외에 set으로 시작하는 유사한 이름의 매서드들이 존재하는데, 이는 매서드를 호출한 인스턴스 객체에 저장된 날짜와 시간을 변경하는 역할을 한다. 

 

 

 

두 Date 객체를 비교하는 매서드도 존재한다. after(), before(), compareTo() 매서드가 그 역할을 하는데, 세 메서드 모두 Date 클래스의 인스턴스 객체로부터 호출되며, 매개인자로 Date 객체를 입력 받는다. after는 매개 인자의 날짜와 일시가 호출 인스턴스보다 뒤에 위치한 경우 true를 반환하며, before는 앞에 위치한 경우 true를 반환한다. compareTo()는 문자열과 마찬가지로 매개 인자 일시가 뒤에 있다면 1을, 앞에 있다면 -1을 반환하며, 같을 경우 0을 반환한다.

 

 

 

 

 

 

(2)  java.util.Calendar

 

두 번째로 확인할 클래스는 java.util 패키지에 속한 Calendar다. Calendar 클래스로 날짜/시간 객체를 저장하는 방법은 일반 클래스와는 조금 다른데, Calendar 클래스가 추상 클래스로 정의되어 있어 new 키워드를 사용하여 인스턴스 생성이 불가능하기 때문이다. 

 

 

 

Calendar 객체로 저장된 날짜/시간 형식은 getTime() 매서드로 호출하면 Date 객체로 반환된다. 즉, Date 클래스의 인스턴스화로 저장한 포맷이 그대로 나타나게 된다는 것이다.

 

 

cToday.getTime() 반환 타입 클래스명: java.util.Date

 

 

위의 예시를 통해, Date와 Calendar 클래스 객체 사이 변환은 아래와 같이 진행됨을 알 수 있다.

 

 

 

위의 예시를 보면 Date와 Calendar 사이 변환은 아래와 같이 진행됨을 알 수 있다.

 

[ Date -> Calendar ]  Calendar.getInstance().setTime(Date_인스턴스객체변수명);

[ Calendar -> Date ]  Calendar_인스턴스객체변수명.getTime();

 

 

Calendar 클래스는 Date 클래스와 다른 구조를 가진다. Calendar는 년도, 월, 일, 시, 분, 초 등등에 대해 필드라는 값을 가지는데, 이 필드값으로 년도, 월, 일, 시, 분 초 등을 추출할 수 있다. 년도는 필드값이 1, 월은2, 일은 3... 과 같은 형식으로 지정되어 있다. 이 필드값은 클래스 내에 상수로 지정되어 있다.

 

 

 

시간의 각 요소를 추출하기 위해서는 get() 매서드를 사용하면 되는데, 매개인자로는 추출할 요소의 필드값을 입력하면 된다.

 

 

 

 

시간 요소의 변경은 set으로 시작하는 이름의 매서드를 사용하면 된다. 이 매서드는 매개인자로 요소 필드와 변경값을 입력받는다.

 

 

 

Date에서는 년도와 월 입력 시 실제 입력값과 괴리가 있는 값들이 화면에 출력됨에 반해, Calendar는 get() 매서드와 필드값으로 연, 월을 출력하면 프로그램 작성자가 의도한 값이 그대로 지정된다. 그러나 getTime()이라는 매서드를 사용하면 월 요소는 Date와 동일하게 입력한 달이 아닌 전 달이 출력된다. 그나마 다행인 것은 Date와 달리 연도까지 의도하지 않은 값으로 출력되지는 않는다는 것이다.

 

 

 

Calenar 클래스는 요일 출력도 지원한다. 요일 출력은 필드 DAY_OF_WEEK을 사용하는데, 반환되는 값은 1~7까지이며 일요일부터 토요일까지 위의 숫자에 대응한다. 즉, 일요일은 1, 월요일은 2가 반환된다.

 

 

 

 

위의 내용을 종합하여 사용하면 특정 년도의 월별 달력을 출력하는 것도 가능하다.

 

 

 

 

 

 

 

3. LocalDateTime

 

마지막으로 살펴볼 날짜/시간 관련 클래스는 LocalDateTime이다. 아마 현재 Java에서 날짜와 시간 관련 클래스로 가장 활발히 사용되고 있을 것이라 생각하는데, 개발 초기 단계부터 존재하던 Calendar와 Date의 사용상 불편함을 크게 개선한 클래스이기 때문이다.

 

LocalDateTime 클래스는 java.time 패키지 내에 존재한다. 이 클래스는 동일한 패키지에 존재하는 LocalTime과 LocalDate의 기능을 하나로 합친 클래스라 보면 된다.

 

 

 

LocalDateTime으로 날짜와 시간을 생성하는 방법은 Calendar, Date와는 조금 다르다. Calendar와 Date는 반드시 인스턴스를 생성해야만 날짜/시간 객체가 저장되지만 LocalDateTime은 날짜/시간을 생성하는 매서드가 static이라 별도로 인스턴스를 생성하지 않아도 된다. 

 

날짜와 시간을 생성하는 방식은 두 가지가 있다. 하나는 now() 매서드를 사용하여 현재 시간을 객체로 만드는 것이고, 다른 하나는 of() 매서드로 특정 날짜와 시간을 객체로 만드는 것이다. of()는 오버로딩 되어 있어 여러 매개 변수를 사용할 수 있는데, 전부 설명하기에는 필자가 조금 귀찮으니 Java Documentation을 참고하자.

 

 

 

LocalDateTime으로 현재 시간과 지정 시간을 객체로 만들어보자.

 

 

 

이 객체들로부터 날짜와 시간의 각 요소를 출력하는 방식은 Calendar나 Date 클래스의 매서드들과 크게 다르지는 않다. 하지만 결과값이 Calendar나 Date에서 년도와 월을 출력할 때처럼 터무니없는 값이 나오는 경우가 없기 때문에 훨씬 편하다. 또한 '월'의 경우 정수형은 물론 영문 문자열도 반환할 수 있는 매서드를 제공한다.

 

 

 

LocalDateTime 이 유래된 클래스인 LocalDate, LocalTime 역시 날짜와 시간 객체를 구현할 때 now()와 of() 매서드를 사용한다. 또한 날짜와 시간 요소값을 가져오는 get()의 경우에도, LocalDateTime과 사용 방법이 동일하다. 

 

Calendar와 Date처럼, 각 요소에 대해 상수로 지정되있지는 않은지 궁금할 수 있는데, LocalDateTime 역시 상수가 지정되어 있다. 다만, Calendar와 Date처럼 클래스 내부에 정의된 상수가 아니라 java.time.temporal 패키지 내의 ChronoField라는 클래스에 정의가 되어 있다. 

 

 

 

위의 예시와 같이 getYear()는 get() 매서드 내에 ChronoField의 YEAR 상수를 매개인자로 구현한 것이다. 이 외에도 getMinute(), getSecond() 매서드 모두 ChronoField의 상수를 사용하여 구현되어 있다.

 

LocalDateTime에서 객체의 날짜와 시간 변경은 어떻게 진행할까? Calendar와 Date처럼 set() 매서드가 있을 것이라 생각하는 분들이 많겠지만, with()라는 매서드로 변경한다. with() 매서드는 매개인자로 TemporalField, 즉 ChronoField의 상수와 변경할 값 두 가지를 입력하면 된다. 추가로 with() 매서드 역시 특정 날짜 / 시간 요소에 대해서만 값을 변경할 수 있게 withYear(), withMonth()와 같은 매서드들을 제공한다.

 

 

 

with()의 경우 반환값이 LocalDateTime이기 때문에 변경 값을 출력하는 것에 그치지 않고 저장해야 하는 경우 LocalDateTime 객체 변수에 반환값을 대입해주어야 한다.

 

with()와 유사한 사용방식을 가지면서 날짜 / 시간 요소 값의 연산을 진행할 수 있는 plus(), minus() 매서드도 있다. 이 매서드들을 사용하면, 특정 날짜나 시간으로부터 몇 분, 몇 일 뒤(전)를 쉽게 계산할 수 있다. 

 

참고로 minus() 대신 plus() 인자에 음수 값을 사용해도 동일한 효과가 나타난다.

 

 

마지막으로 LocalDateTime은 TimeZone 관련 작업을 진행할 수 있다. TimeZone이란, 전 세계 시간이 각기 다르기에 일정 지역은 동일한 시간대를 사용하게 설정된 영역을 의미한다. 한국의 경우 일본과 경도상 차이가 있으나 동아시아 시간대를 공유하고 있다. 기준점이 되는 TimeZone은 영국과 같은 경도에 위치한 그리니치(Greenwich)의 시간대인 UTC다.

 

만약 현재 표시된 시간이 어느 지역의 시간대인지 표시가 필요한 경우라면 어떻게 해야 할까? 이 시간대를 지정해주는 매서드가 atZone()이라는 매서드다. 이 매서드는 매개인자를 java.time 패키지 내에 존재하는 ZoneId 클래스 객체 또는 UTC로부터 입력하고자하는 시간대가 몇 시간이나 차이나는지 문자열로 입력받는다. 예를 들어 한국의 경우 "Asia/Seoul" 또는 "+9" 를 입력하면 된다.

 

ZoneId 클래스는 of() 매서드를 통해 UTC와의 시간 차이(+0900) 및 특정 Zone의 명칭([Asia/Seoul])을 문자열로 입력받으며, 이 ZoneId 객체를 LocalDateTime의 atZone() 매서드에 적용하면 반환값이 ZonedDateTime 객체가 된다. 즉

 

LocalDateTime + ZoneId = > ZonedDateTime

 

으로 변환이 된다. 현재 시간에 TimeZone을 적용해보자.

 

 

 


 

 

날짜와 시간 관련 Java에서 제공하는 클래스는 매우 무궁무진하다. 따라서 모든 내용을 하나의 포스팅에서 다루지는 못하고, 자주 쓰일 수 있는 기능 위주로만 설명을 진행했다. 

 

다음 포스팅에서는 화면 출력을 깔끔하게 진행해주는 형식화 클래스에 대해 알아보려 한다. 

 

 

 

 

Fin.

반응형

댓글