- 날짜와 시간
- 형식화 클래스
- java.time패키지
Date는 날짜와 시간을 다룰 목적으로 JDK1.0부터 제공되어온 클래스이다. Date클래스 기능이 부족하여 Calendar라는 새로운 클래스를 JDK1.1부터 제공하기 시작했다. Calendar는 Date보다 훨씬 나았지만 몇 가지 단점들이 발견되어 JDK1.8부터 java.time패키지
로 기존의 단점들을 개선한 새로운 클래스들이 추가되었다.
여기서 말하는 Date클래스는 java.util패키지에 속한것이다. ( java.sql패키지의 date클래스와 혼동하지 말것)
Calendar는 추상 클래스이기 때문에 직접 객체를 생성할 수 없고, 메서드를 통해서 완전히 구현된 클래스의 인스턴스를 얻어야 한다.
Calendar cal = new Calendar(); //에러 -> 추상클래스는 인스턴스 생성 불가
Calendar cal = Calendar.getInstance(); //OK -> getInstance()메서드는 Calendar를 구현한 클래스의 인스턴스를 반환한다.
Calendar를 상속받아 완전히 구현한 클래스로는 GregorianCalendar와 BuddhistCalendar가 있는데 getInstance()는 시스템의 국가와 지역설정을 확인해서 태국인 경우 BuddhistCalendar를 그외에는 GregorianCalendar의 인스턴스를 반환한다. ( 태국 제외 나머지 국가는 GregorianCalendar를 사용 )
getInstance()메서드를 사용하여 인스턴스를 생성하는 이유는 최소한의 변경으로 프로그램이 동작할 수 있도록 하기 위함이다. getInstance()메서드는 static이기 때문에 메서드내에 인스턴스변수나 메서드를 호출하지 않으며 추상클래스의 메서드이기 때문에 인스턴스 생성후 사용될수 없다.
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);
getInstance()를 통해서 얻은 인스턴스는 기본적으로 현재 시스템의 날짜와 시간에 대한 정보를 담고 있다. 원하는 날짜나 시간을 설정하려면 set메서드를 사용하면 된다. int get(int field)
를 이용해서 원하는 필드의 값을 얻어오는 것도 가능하다.
get메서드의 매개변수로 사용되는 int값들은 Calendar에 정의된 static상수이고 Java Api문서를 참고하여 확인이 가능하다.
System.out.println("월(0~11) , 0: 1월" + today.get(Calendar.MONTH));
get(Calendar.MONTH)로 가져오는 값은 1~12월이아닌 0~11로 1을 더한값이 본래값이다.
public static void main(String[] args) {
Calendar date1 = Calendar.getInstance();
Calendar date2 = Calendar.getInstance();
final String[] DAY_OF_WEEK = {"","월","화","수","목","금","토","일"};
date1.set(2015,7,15);
System.out.println("date1은 " + toString(date1) + DAY_OF_WEEK[date1.get(Calendar.DAY_OF_WEEK)] + "요일이고 , " );
System.out.println("date2는 " + toString(date2) + DAY_OF_WEEK[date2.get(Calendar.DAY_OF_WEEK)]+ "요일이다. ");
// 두 날짜간의 차이
long difference = (date2.getTimeInMillis() - date1.getTimeInMillis()) / 1000;
System.out.println("그 날(date1) 부터 지금 ( date2) 까지 " + difference + "초가 지났습니다.");
System.out.println("일(day)로 계산하면 " + difference/(24*60*60) + "일입니다." ); //1일 = 24*60*60
}
public static String toString(Calendar date) {
return date.get(Calendar.YEAR)+ "년" + (date.get(Calendar.MONTH)+1) + "월 " + date.get(Calendar.DATE) + "일 " ;
}
두 날짜간의 차이를 구하기 위해서는 두 날짜를 최소단위인 초단위로 변경한 다음 그 차이를 구하면 된다. getTimeInMillis() 는 1/1000초 단위로 값을 반환하기 때문에 초단위로 얻기 위해서는 1000으로 나눠 주어야 하고, 일단위로 얻기 위해서는 24*60*60*1000
으로 나눠 주어야 한다.
시간상의 전후를 알고 싶을 때는 두 날짜간의 차이가 양수인지 음수인지를 판단하거나 boolean after(Object when)
이나 boolean before(Object when)
을 사용하면 된다.
두개의 시간 데이터로부터 초 단위로 시간 차이를 구한 다음, 시분초로 바꾸기 위해서 시간은 3600초로 나누고 남은 나머지를 다시 분 60초로 나누면 그 나머지는 초 단위의 값이 된다.
for(int i = 0;i<TIME_UNIT.length;i++){
tmp += difference/TIME_UNIT[i] + TIME_UNIT_NAME[i];
difference %= TIME_UNIT[i];
}
add(int field, int amout)
를 사용하면 지정한 필드의 값을 원하는 만큼 증가 또는 감소 시킬 수 있다. roll(int field,int amount)
도 지정한 필드의 값을 증가 또는 감소시킬 수 있는데, 다른 필드에 영향을 미치지 않는다. (예로, add메서드를 통해 DATE에 31을 증가시킬 경우 다음 달로 넘어가지만 roll메서드는 DATE만 31을 증가시킨 값을 표시한다. )
날짜를 형식에 맞게 출력하려면 굉장히 복잡해진다. 이를 간단하게 해준 클래스가 형식화 클래스이다. java.text패키지에 포함되어 있으며, 숫자/날짜/텍스트 데이터를 일정한 형식에 맞게 표현할 수 있는 방법을 객체지향적으로 설계하여 표준화 하였다.
형식화 뿐만아니라 역으로 원래의 데이터를 얻어낼 수도 있다.
형식화 클래스 중에서 숫자를 형식화 하는데 사용되는 것으로 숫자데이터를 정수,부동소수점,금액 등의 다양한 형식으로 표현할 수 있고 반대로 일정한 형식의 텍스트 데이터를 숫자로 쉽게 변환하는 것도 가능하다.
원하는 형식으로 표현 또는 변환하기 위해서 패턴을 정의한다.
기호 | 의미 |
---|---|
0 | 10진수(값이 없을 경우 0) |
# | 10진수(값없으면 생략) |
. | 소수점 |
- | 음수부호 |
, | 단위구분자 |
E | 지수기호 |
; | 패턴구분자 |
% | 퍼센트 |
\u2030 | 퍼밀(퍼센트* 10) |
\u00A4 | 통화 |
` | escape문자 |
해당 클래스를 사용하는 방법은 먼저 원하는 출력형식의 패턴을 작성하여 DecimalFormat인스턴스를 생성한 다음, 출력하고자 하는 문자열로 foramt메서드를 호출하면 원하는 패턴에 맞게 변환된 문자열을 얻게 된다.
double number = 1234567.89;
DecimalFormat df = new DecimalFormat("#.#E0");
String result = df.format(number);
패턴을 이용해서 숫자를 다르게 변환하려면 parse메서드 를 이용하여 기호와 문자가 포함된 문자열을 숫자로 쉽게 변환할 수 있다.
Date와 Calendar만으로 날짜 데이터를 원하는 형태로 다양하게 출력하는 것은 불편하고 복잡하다. SimpleDateFormat을 사용하면 해결된다.
기호 | 의미 |
---|---|
G | 연대(BC,AD) |
y | 년도 |
M | 월 |
w | 년의 몇번째 주 (1~53) |
W | 월의 몇번째 주 (1~5) |
D | 년의 몇번째 일 (1~365) |
d | 월의 몇번째 일(1~31) |
F | 월의 몇번째 요일 (1~5) |
E | 요일 |
a | 오전/오후 |
H | 시간(0~23) |
k | 시간(1~24) |
K | 시간(0~11) |
h | 시간(1~12) |
m | 분(0~59) |
s | 초(0~59) |
S | 천분의 일초(0~999) |
z | Time zone(General) |
Z | Time zone(RFC 822) |
` | escape문자 |
사용하는 방법은 먼저 원하는 출력형식의 패턴을 작성하여 인스턴스를 생성한다음 , 출력하고자 하는 Date인스턴스를 가지고 format(Date d)를 호출하면 지정한 출력형식에 맞게 변환된 문자열을 얻게 된다.
Date today = new Date();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String result = df.format(today);
Date인스턴스만 format메서드를 사용할 수 있으므로 Calendal인스턴스를 Date인스턴스로 변환하여 format메서드를 사용해야 한다.
Calendar -> Date
Calendar cal = Calendar.getInsatnce(); Date day = cal.getTime();
parse(String source) 를 사용해서 날짜 데이터의 출력형식을 변환하는 것이 가능하다. 문자열 source를 날짜Date인스턴스로 변환해준다.
String pattern = "yyyy/MM/dd";
DateFormat df = new DateFormat(pattern);
Date date = df.parse("2020/03/10"); // 문자열 -> date
ChoiceFormat은 특정 범위에 속하는 값을 문자열로 변환해준다. 연속적 또는 불연속적인 범위의 값들을 처리하는데 if문이나 switch문은 적절하지 못한 경우가 많다. 이럴 경우 ChoiceFormat을 사용하면 간단하고 직관적으로 만들수 있다.
ChoiceFormat form = new ChoiceFormat(limits,grades);
//limits : 범위의 경계값 리스트
// grades : 범위에 포함된 값 치환할 문자열 리스트
String result = form.format(80);
// 해당 범위에 속한 문자 출력
경계값은 double형으로 모두 오름차순으로 정ㄹ렬되어 있어야 하며, 치환될 문자열의 개수는 경계값에 의해 정의된 범위의 개수와 일치해야 한다.
패턴은 구분자로 "#", "<" 두가지를 제공하며, limit#value
형태로 사용한다. #은 경계값을 범위에 포함시키지만, <는 포함시키지 않는다. 예] "60#D|70#C|80<D|90#A|"
데이터를 정해진 양식에 맞게 출력할 수 있도록 도와준다. 다수의 데이터를 같은 양식으로 출력할 때 사용하면 좋다. parse를 이용하면 지정된 양식에서 필요한 데이터만을 손쉽게 추출해낼 수도 있다.
String msg = "Name: {0} \nTel : {1} \nAge:{2} \nBirthday:{3}";
Object[] arguments = {
"유진","01-234-567","25","08-09"
};
String result = MessageFormat.format(msg,arguments);
System.out.println(result);
MessageFormat에 사용할 양식인 문자열 msg를 작성할 때 {숫자}
로 표시된 부분이 데이터가 출력될 자리이다. 이 자리는 순차적일 필요는 없고 여러 번 반복해서 사용할 수도 있다. (인덱스가 0부터 시작)
Date와 Calendar가 가지고 있는 단점들을 해소하기 위해 JDK1.8부터 java.time패키지
가 추가 되었다. 밑의 4개의 하위페이지를 가지고 있다.
패키지 | 설명 |
---|---|
java.time | 날짜와 시간을 다루는데 필요한 핵심 클래스들을 제공 |
java.time.chrono | 표준(ISO)이 아닌 달력 시스템을 위한 클래스들 제공 |
java.time.format | 날짜와 시간을 파싱하고, 형식화 하기 위한 클래스 제공 |
java.time.temporal | 날짜와 시간의 필드와 단위를 위한 클래스들을 제공 |
java.time.zone | 시간대(time-zone)와 관련된 클래스들을 제공 |
위의 패키지들에 속한 클래스들의 가장 큰 특징은 String클래스처럼 불변(immutable)
이라는 것이다. 그래서 날짜나 시간을 변경하는 메서드들은 기존의 객체를 변경하는 대신 항상 변경된 새로운 객체를 반환한다. 기존 Calendar클래스는 변경 가능하므로, 멀티 쓰레드 환경에서 안전하지 못하다.
멀티 쓰레드 환경에서는 동시에 여러 쓰레드가 같은 객체에 접근할 수 있기 때문에, 변경 가능한 객체는 데이터가 잘못될 가능성이 있으며, 이를 쓰레드에 안전하지 앟ㄴ다고 한다.
날짜와 시간을 하나로 표현하는 Calendar클래스와 달리, java.time패키지에서는 날짜와 시간을 별도의 클래스로 분리해 놓았다. 시간을 표현할 때는 LocalTime클래스를 사용하고, 날짜를 표현할 때는 LocalDate클래스를 사용한다. 모두 필요한 경우 LocalDateTime클래스를 사용한다.
LocalTime + LocalDate ==> LocalDateTime
시간대까지 다뤄야 한다면, ZonedDateTime클래스를 사용한다.
Date와 유사한 클래스로는 Instant가 있는데, 이 클래스는 날짜와 시간을 초단위로 표현하며 이를 타임스탬프라고 부른다. 날짜와 시간의 차이를 계산하거나 순서를 비교하는데 유리해서 데이터베이스에 많이 사용된다.
날짜와 시간의 간격을 표현하기 위한 클래스도 있는데, Period는 두 날짜간의 차이를 표현하기 위한 것이고, Duration은 시간의 차이를 표현하기 위한 것이다.
now()는 현재 날짜와 시간을 저장하는 객체를 생성한다.
LocalDate date = LocalDate.now(); // 현재 날짜
LocalTime time = LocalTime.now(); //현재 시간
LocalDateTime dateTime = LocalDateTime.now(); //날짜 + 시간
ZonedDateTime dateTimeInKr = ZonedDateTime.now(); //시간대
of()는 단순히 해당 필드의 값을 순서대로 지정해 주기만 하면 된다. 각 클래스마다 다양한 of()가 정의되어 있다.
LocalDate date = LocalDate.of(2022,3,14); // 2022년 3월 14일
LocalTime time = LocalTime.of(23,59,59); //23시 59분 59초
LocalDateTime dateTime = LocalDateTime.of(date,time);
...
날짜와 시간을 표현하기 위한 클래스들은 모두 Temporal,TemporalAccessor,TemporalAdjuster인터페이스를 구현했고, Duration과 Period는 TemporalAmount인터페이스를 구현하였다.
날짜와 시간의 단위를 정의해놓은 것이 TemporalUnit인터페이스이고, 이 인터페이스를 구현한 것이 열거형 ChronoUnit이다. TemporalField는 년,월,일 등 날짜와 시간의 필드를 정의해 놓은 것으로, 열거형 ChronoField가 이 인터페이스를 구현하였다.
날짜와 시간에서 특정 필드의 값만을 얻을 때는 get()이나 get으로 시작하는 이름의 메서드를 이용한다. 특정 날짜와 시간에서 지정된 단위의 값을 더하거나 뺄 때는 plus()또는 minus()에 값과 함께 열거형 ChronoUnit을 사용한다.
TemporalField나 Temporalunit을 사용할 수 있는지 확인하는 메서드는 isSupported()이다.
java.time패키지의 가장 기본이 되는 클래스이다. 객체를 생성하는 방법은 현재의 날짜,시간을 반환하는 now() 메서드와 지정 날짜,시간으로 생생성하는 of() 메서드가 있다. 둘다 static메서드이다. (클래스이름으로 사용)
일 단위나 초단위로 지정이 가능하다. ofYearDay(year,day) 와 ofSecondDay(second) 가 있다.
parse() 를 이용하면 문자열을 날짜와 시간으로 변환할 수 있다.
LocalDate date = LocalDate.parse("1998-04-15");
클래스 | 메서드 | 설명 |
---|---|---|
LocalDate | int getYear() | 년도 |
int getMonthValue() | 월 | |
Month getMonth() | 월 | |
int getDayOfMonth() | 일 | |
int getDayofYear() | 같은 해의 1월1일 부터 몇번째 일 | |
DayOfWeek getDayOfWeek() | 요일 | |
int lengthOfMonth() | 같은 달의 총 일수 | |
int lengthOfYear() | 같은 해의 총 일수 | |
boolean isLeapYear() | 윤년여부 확인 | |
LocalTime | int getHour() | 시 |
int getMinute() | 분 | |
int getSecond() | 초 | |
int getNano() | 나노초 |
LocalDate withYear(int year);
LocalDate withMonth(int month);
..
LocalTime withHour(int hour);
LocalTime withMinute(int minute);
...
with()를 사용하면 원하는 필드를 직접 지정할 수 있다.
LocalDate with(TemporalField field,long newValue);
필드를 변경하는 메서드들은 항상 새로운 객체를 생성해서 반환하므로 대입 연사자를 같이 사용해야 한다.
date = date.withYear(2000);
time = time.withHour(12);
특정 필드에 값을 더하거나 빼는 plus() 와 minus() 가 있다.
LocalDate plusYears(long yearsToAdd)
LocalDate plusMonths(long monthsToAdd)
...
LocalTime plusHours(long hoursToAdd)
LocalTime plusMinutes(long minutesToAdd)
...
LocalTime truncatedTo() 는 지정된 것보다 작은 단위의 필드를 0으로 만든다.
LocalTime time = LocalTime.of(12,34,56);
time = time.truncatedTo(ChronoUnit.HOURS); // 시보다 작은 단위를 0으로 --> 12시 00분 00초
boolean isAfter(ChronoLocalDate other)
boolean isBefore(ChronoLocalDate other)
boolean isEqual(ChronoLocalDate other)
equals()가 있는데도, isEqual()을 제공하는 이유는 연표가 다른 두 날짜를 비교하기 위해서이다. 모든 필드가 일치해야하는 equals()와 달리 isEqual()은 오직 날짜만 비교한다.
Instant는 에포크 타임 부터 경과된 시간을 나노초 단위로 표현한다. 사람에겐 불편하지만 단일 진법으로만 다루기 때문에 계산하기 쉽다.
인스턴스를 생성할 때는 now()
와 ofEpochSecond()
를 사용한다. 필드에 저장된 값을 가져 올때는 getEpochSecond()
혹은 getNano()
를 사용한다.
Instant now = Instant.now();
Instant now1 = Instant.ofEpochSecond(now.getEpochSecond());
Instant now2 = Instant.ofEpochSecond(now.getEpochSecond(),now.getNano());
Instant는 시간을 초 단위와 나노초 단위로 나누어 저장한다. (밀리초 단위를 필요로 하는 경우 toEpochMili()
가 정의되어 있다.)
static Date from(Instant instant); //Instant -> Date
Instant toInstant(); //Date -> Instant
LocalDate와 LocalTime을 합쳐놓은 것이 LocalDateTime 이고 시간때를 추가한 것이 ZonedDateTime이다.
LocalDateTime dt = LocalDateTime(date,time);
LocalDateTime dt1 = date.atTime(time);
LocalDateTime dt2 = time.atDate(date);
LocalDateTime dt3 = date.atTime(12,34,56);
LocalDateTime dt4 = time.atDate(LocalDate.of(2014,12,21));
LocalDateTime dt5 = date.atStartOfDay();
날짜와 시간을 직접 지정할 수 있는 다양한 버젼의 of()와 now() 가 정의되어 있다.
반대로 LocalDateTime을 LoalDate와 LocalTime으로 변환할 수 있다.
LocalDate date = dt.toLocalDate();
LocalTime time = dt.toLocalTime();
LocalDateTime에 시간대를 추가하면 ZonedDateTime이 된다. LocalDateTime에 atZone() 으로 시간대 정보를 추가하면 , ZonedDateTime을 얻을 수 있다.
LocalDate에 atStartOfDay()라는 메서드를 매개변수로 ZoneId를 지정해도 ZonedDateTime을 얻을 수 있다.
자주 쓰일만한 날짜 계산들을 대신 해주는 메서드를 정의해 놓은 것이 TemporalAdjusters클래스이다. 직접 구현도 가능하다.
Period는 날짜의 차이를 Duration은 시간의 차이를 계산하기 위한 것이다.
date1과 date2사이의 차이를 나타내는 period는 between()으로 얻을 수 있다. 이전이면 양수로 이후면 음수로 Period에 저장된다. 시간차이를 구하는 경우 Duration을 사용한다는 것을 제외하고는 Period와 동일하다.
Period pe = Period.between(date1,date2);
Duration du = Duration.between(time1,time2);
특정 필드 값을 얻을 때는 get() 을 사용한다.
until()은 between()과 같은 일을 하지만 between()은 static메서드이고, until()은 인스턴스 메서드이다.
형식화와 관련된 클래스들은 java.time.format패키지에 들어있는데, 이중 DateTimeFormaterr가 핵심이다. 이 클래스에는 자주 쓰이는 다양한 형식들을 기본적으로 정의하고 있으며, 그 외의 형식이 필요하다면 직접 정의해서 사용할 수 도 있다. 날짜와 시간의 형식화에는 format() 이 사용된다. DateTimeFormatter의 ofPattern() 으로 원하는 출력형식을 직접 작성할 수도 있다.
문자열을 날짜 또는 시간으로 변환하려면 static메서드 parse() 를 사용하면 된다.