Date는 날짜와 시간을 다룰 목적으로 JDK1.0부터 제공되어온 클래스이다. JDK1.0이 제공하는 클래스의 수와 기능은 지금과 비교할 수 없을 정도로 빈약했다. 그래서 Calendar라는 새로운 클래스를 그 다음 버젼인 JDK1.1부터 제공하기 시작했다. Calendar는 Date보다는 훨씬 나았지만 단점이 이었다. JDK1.8부터는 java.time패키지
로 기존의 단점들을 개선한 새로운 클래스들이 추가되었다.
Calendar는 추상클래스이기 때문에 직접 객체를 생성할 수 없고, 메서드를 통해서 완전히 구현된 클래스의 인스턴스를 얻어야 한다.
Calendar cal = new Calendar(); // 에러!!! 추상클래스는 인스턴스를 생성할 수 없다.
// getInstance() 메서드는 Calendar 클래스를 구현한 클래스의 인스턴스를 반환한다.
Calendar cal = Calendar.getInstance();
Calendar를 상속받아 완전히 구현한 클래스로는 GregorianCalendar와 BuddhistCalendar가 있는데 getInstance()는 시스템의 국가와 지역설정을 확인해서 태국인 경우에는 BuddhistCalendar의 인스턴스를 반환하고, 그 외에는 GregorianCalendar의 인스턴스를 반환한다.
GregorianCalendar는 Calendar를 상속받아 오늘날 전세계 공통으로 사용하고 있는 그레고리력에 맞게 구현한 것으로 태국을 제외한 나머지 국가에서는 GregorianCalendar를 사용하면 된다.
Calendar가 새로 추가되면서 Date는 대부분 메서드가 deprecated
되었으므로 잘 사용되지 않는다. 그럼에도 불구하고 여전히 Date를 필요로 하는 메서드들이 있기 때문에 Calendar를 Date로 또는 그 반대로 변환할 일이 생긴다.
// 1. Calendar를 Date로 변환
Calendar cal = Calendar.getInstance();
...
Date d = new Date(cal.getTimeInMillis()); // Date(long date)
// 2. Date를 Calendar로 변환
Date d = new Date();
...
Calendar cal = Calendar.getInstance();
cal.setTime(d);
성적처리 프로그램을 작성했을 때 각 점수의 평균을 소수점 2자리로 일정하게 맞춰서 출력하려면 어떻게 해야 할까 고민해본 적이 있을 것이다.
자바에서는 이러한 문제들을 쉽게 해결할 수 있는 방법을 제공하는데 그 것이 바로 형식화 클래스이다. 이 클래스는 java.text 패키지에 포함되어 있으며 숫자, 날짜, 텍스트 데이터를 일정한 형식에 맞게 표현할 수 있는 방법을 객체지향적으로 설계하여 표준화하였다.
형식화 클래스 중에서 숫자를 형식화 하는데 사용되는 것이 DemicalFormat이다.
DemicalFormat을 이용하면 숫자 데이터를 정수, 부동소수점, 금액 등의 다양한 형식으로 표현할 수 있으며, 반대로 일정한 형식의 텍스트 데이터를 숫자로 쉽게 변환하는 것도 가능하다.
형식화 클래스에서는 원하는 형식으로 표현 또는 변환하기 위해서 패턴을 정의하는데, 형식화 클래스에서는 패턴을 정의하는 것이 전부라고 해도 과언이 아니다.
아래의 표에 DemicalFormat의 패턴의 작성에 사용되는 기호와 설명, 그리고 자주 사용될 만한 패턴들을 예로 들었다.
DemicalFormat을 사용하는 방법은 간단하다. 먼저 원하는 출력형식의 패턴을 작성하여 DemicalFormat 인스턴스를 생성한 다음, 출력하고자 하는 문자열로 format 메서드를 호출하면 원하는 패턴에 맞게 변환된 문자열을 얻게 된다
double number = 1234567.89;
DemicalFormat df = new DecimalFormat("#.#E0");
String result = df.format(number);
앞에서 날짜를 계산할 때 Date와 Calendarfㅡㄹ 사용해서 날짜를 계산하는 방법을 배웠는데, 이제는 출력하는 방법에 대해서 배울 차례이다.
Date와 Calendar만으로 날짜 데이터를 원하는 형태로 다양하게 출력하는 것은 불편하고 복잡하다. 그러나 SimpleDateFormat을 이용하면 이러한 문제들이 간단히 해결된다.
SimpleDateFormat을 사용하는 방법은 간단하다. 먼저 원하는 출력형식의 패턴을 작성하여 SimpleDateFormat 인스턴스를 생성한 다음, 출력하고자 하는 Date인스턴스를 가지고 format(Date d)를 호출하면 지정한 출력형식에 맞게 변환된 문자열을 얻게 된다.
Date today = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
// 오늘 날짜를 yyyy-MM-dd 형태로 변환하여 반환한다.
String result = df.format(today);
ChoiceFormat은 특정 범위에 속하는 값을 문자열로 변환해준다. 연속적 또는 불연속적인 범위의 값들을 처리하는 데 있어서 if문이나 switch문은 적절하지 못한 경우가 많다.
이럴 때 ChoiceFormat을 잘 사용하면 복잡하게 처리될 수 없었던 코드를 간단화할 수 있다.
import java.text.*;
class ChoiceFormatEx1 {
public static void main(String args[]) {
double[] limits = {60, 70, 80, 90}; // 낮은 값부터 큰 값 순서로 적어야한다.
// limits, grades 간의 순서와 개수를 맞추어야 한다.
String[] grades = {"D", "C", "B", "A"};
int[] scores = { 100, 95, 88, 70, 52, 60, 70 };
ChoiceFormat form = new ChoiceFormat(limits, grades);
for (int i=0; i < scores.length; i++) {
System.out.println(score[i] + ": " + form.format(scores[i]));
}
}
}
실행결과
100:A
95:A
88:B
70:C
52:D
60:D
70:C
MessageFormat은 데이터를 정해진 양식에 맞게 출력할 수 있도록 도와준다. 데이터가 들어갈 자리를 마련해 놓은 양식을 미리 작성하고 프로그램을 이용해서 다수의 데이터를 같은 양식으로 출력할 때 사용하면 좋다
예를 들면 고객들에게 보낼 안내문을 출력할 때 같은 안내문 양식에 받는 사람의 이름과 같은 데이터만 달라지도록 출력할 때, 또는 하나의 데이터를 다양한 양식으로 출력할 때 사용한다. 그리고 SimpleDateFormat의 parse처럼 MessageFormat의 parse를 이용하면 지정된 양식에서 필요한 데이터만을 손쉽게 추출해 낼 수도 있다.
import java.text.MessageFormat;
public class MessageFormatEx1 {
public static void main(String[] args) {
String msg = "Name: {0} \nTel: {1} \nAge: {2} \nBirthday: {3}";
// arguments의 인덱스 0부터 msg의 {숫자} 안에 들어간다.
Object[] arguments = {
"이자바", "02-123-4567", "29", "08-06"
};
String result = MessageFormat.format(msg, arguments);
System.out.println(result);
}
}
실행결과
Name: 이자바
Tel: 02-123-1234
Age: 27
Birthday: 07-09
이 패키지는 다음과 같이 4개의 하위 패키지를 가지고 있다.
패키지 | 설명 |
---|---|
java.time | 날짜와 시간을 다루는데 필요한 핵심 클래스들을 제공 |
java.time.chrono | 표준(ISO)이 아닌 달력 시스템을 위한 클래스들을 제공 |
java.time.format | 날짜와 시간을 파싱하고, 형식화하기 위한 클래스들을 제공 |
java.time.temporal | 날짜와 시간의 필드 (field)와 단위(unit)를 위한 클래스들을 제공 |
java.time.zone | 시간대(time-zone)와 관련된 클래스들을 제공 |
위의 패키지들에 속한 클래스들의 가장 큰 특징은 String 클래스처럼 불변
이라는 것이다. 그래서 날짜나 시간을 변경하는 메서드들은 기존의 객체를 변경하는 대신 항상 변경된 새로운 객체를 반환한다.
날짜와 시간을 하나로 표현하는 Calendar클래스와 달리, java.time 패키지에서는 날짜와 시간을 별도의 클래스로 분리해 놓았다. 시간을 표현할 때는 LocalTime 클래스를 사용하고, 날짜를 표현할 때는 LocalDate 클래스를 사용한다. 그리고 날짜와 시간이 모두 필요할 때는 LocalDateTime 클래스를 사용하면 된다.
LocalDate + LocalTime -> LocalDateTime
날짜 + 시간 -> 날짜 & 시간
여기서 시간대(time-zone)까지 다뤄야 한다면, ZonedDateTime클래스를 사용하자.
LocalDateTime + 시간대 -> ZonedDateTime
날짜와 시간의 간격을 표현하기 위한 클래스도 있는데, Period는 두 날짜간의 차이를 표현하기 위한 것고, Duration은 시간의 차이를 표현하기 위한 것이다.
날짜 - 날짜 = Period
시간 - 시간 = Duration
now()는 현재 날짜와 시간을 저장하는 객체를 생성한다.
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
ZonedDateTime dateTimeInKr = ZonedDateTime.now();
of()는 단순히 해당 필드의 값을 순서대로 지정해주기만 하면 된다.
LocalDate date = LoalDate.of(2023, 2, 6);
LocalTime time.= LocalTime(23, 59, 59);
LocalDateTime dateTime = LocalDateTime.of(date, time);
ZonedDateTime zDateTime = ZonedDateTime.of(dateTime, ZoneId.of("Asia/Seoul"));
LocalDate와 LocalTime은 java.time 패키지의 가장 기본이 되는 클래스이며, 나머지 클래스들은 이들의 확장이므로 이 두 클래스만 잘 이해하고 나면 나머지는 아주 쉬워진다.
객체를 생성하는 방법은 현재의 날짜와 시간을 LocalDate와 LocalTime으로 각각 반환하는 now()와 지정된 날짜와 시간으로 LocalDate와 LocalTime 객체를 생성하는 of()가 있다.
날짜와 시간에서 특정 필드 값을 변경하려면, 다음과 같이 with로 시작하는 메서드를 사용하면 된다. 이 외에도 특정 필드에 값을 더하거나 빼는 plus()와 minus()가 있다.
Instant는 에포크 타임(EPOCH TIME)부터 경과된 시간을 나노초 단위로 표현한다. 사람에겐 불편하지만, 단일 진법으로만 다루기 때문에 계산하기 쉽다.
Instant now = Instant.now();
Instant는 기존의 java.uitl.Date를 대체하기 위한 것이며, JDK1.8부터 Date에 Instant로 변환할 수 있는 새로운 메서드가 추가되었다.
static Date from(Instant instant) // Instant -> Date
Instant toInstant() // Date -> Instant
앞서 언급한 것과 같이 LocalDate와 LocalTime을 합쳐놓은 것이 LocalDateTime이고, LocalDateTime에 시간대(time zone)를 추가한 것이 ZonedDateTime이다.
LocalDate + LocalTime -> LocalDateTime
LocalDateTime + 시간대 -> ZonedDateTime
앞서 plus(), minus()와 같은 메서드로 날짜와 시간을 계산할 수 있다는 것을 배웠다. 지난 주 토요일이 며칠인지, 또는 이번 달의 3번째 금요일은 며칠인지와 같은 날짜계산을 plus(), minus()로 하기엔 좀 불편하다. 그래서 자주 쓰일만한 날짜 계산들을 대신 해주는 메서드를 정희해놓은 것이 TempoalAdjusters 클래스이다.
LocalDate today = LocalDate.now();
LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
앞서 잠시 언ㄴ급한 것과 같이 Period는 날짜의 차이를, Duration은 시간의 차이를 계산하기 위한 것이다.
날짜 - 날짜 = Period
시간 - 시간 = Duration
between()
예를 들어 두 날짜 date1과 date2의 차이를 나타내는 Period는 between()으로 얻을 수 있다.
LocalDate date1 = LocalDate.of(2014, 1, 1);
LocalDate date2 = LocalDate.of(2015, 12, 31);
Period pe = Period.between(date1, date2);
date1이 date2보다 날짜 상으로 이전이면 양수로, 이후면 음수로 Period에 저장된다.
그리고 시간차이를 구할 때는 Duration을 사용한다는 것을 제외하고는 Period와 똑같다.
until()은 between()과 거의 같은 일을 한다. between()은 static 메서드이고, until()은 인스턴스 메서드라는 차이가 있다.
Period에는 of(), ofYears() ... 등이 있고 Duration에는 of(), ofDays() ... 등이 있다. 사용법은 앞서 Local Date와 LocalTime에서 배운 것과 같다.
Period pe = Period.of(1, 12, 31);
Duration du = Duration.of(60, ChronoUnit.SECONDS);
// Duration du = Duration.ofSeconds(60);
plus(), minus()외에 곱셈과 나눗셈을 위한 메서드도 있다.
pe = pe.minusYears(1).multiplieBy(2);
du = du.plusHours(1).dividedBy(60);
이름이 to
로 시작하는 메서드들이 있는데, 이 들은 Period와 Duration을 다른 단위의 값으로 변환하는데 사용된다. get()은 특정 필드의 값을 그대로 가져오는 것이지만, 아래의 메서드들은 특정 단위로 변환한 결과를 반환한다는 차이가 있다.
날짜와 시간을 원하는 형식으로 출력하고 해석(파싱, parsing)하는 방법에 대해서 배울 차례이다.
형식화(formatting)와 관련된 클래스들은 java.time.format 패키지에 들어있는데, 이중에서 DateTimeFormatter가 핵심이다.
LocalDate date = LocalDate.of(2016, 1, 2);
String yyyymmdd = DateTimeFormatter.ISO_LOCAL_DATE.format(date);
String yyyymmdd = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
날짜와 시간의 형식화에는 위와 같이 format()이 사용되는데, 이 메서드는 DateTimeFormatter 뿐만 아니라 LocalDate나 LocalTime 같은 클래스에도 있다. 같은 기능을 하니까 상황에 따라 편한 쪽을 선택해서 사용하면 된다.
DateTimeFormatter의 static 메서드 ofLocalizedDate(), ofLocalizedTime(), ofLocalized DateTime()은 로케일(locale)에 종속적인 포맷터를 생성한다.
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
String shortFormat = formatter.format(LocalDate.now());
ofPatter()으로 원하는 출력형식을 직접 작성할 수도 있다.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
문자열을 날짜 또는 시간으로 변환하려면 static 메서드 parse()를 사용하면 된다. 날짜와 시간을 표현하는데 사용되는 클래스에는 이 메서드가 거의 다 포함되어 있다. parse()는 오버로딩된 메서드가 여러 개 있는데, 그중에서 다음의 2개가 자주 쓰인다.
static LocalDateTime parse(CharSequence text)
static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter)