섹션 5. Date와 Time
Date와 Time 소개
Java 8에 새로운 날짜와 시간 API가 생긴 이유
- 그전까지 사용하던 java.util.Date 클래스는 mutable, 즉 객체 내부의 값이 변경 가능하기 때문에 thread-safe하지 않다.
- 서로 다른 Thread에서 접근하여 Date 객체의 값을 서로 변경하면?
- 클래스 이름이 명확하지 않다. Date인데 시간까지 다룬다.
- 버그 발생할 여지가 많다. (타입 안정성이 없고, 월이 0부터 시작한다거나…)
- 날짜 시간 처리가 복잡한 애플리케이션에서는 보통 Joda Time 을 쓰곤 했다.
예제
import java.util.Date;
public class Practice1 {
public static void main(String[] args) throws InterruptedException{
Date date = new Date();
long time = date.getTime();
System.out.println(date);
System.out.println(time);
Thread.sleep(1000 * 3);
Date after3Seconds = new Date();
System.out.println(after3Seconds);
after3Seconds.setTime(time);
System.out.println(after3Seconds);
}
}
/*
Thu Apr 29 17:48:16 KST 2021
1619686096218
Thu Apr 29 17:48:19 KST 2021
Thu Apr 29 17:48:16 KST 2021 -> mutable
*/
자바 8에서 제공하는 Date-Time API
JSR-310 스팩 의 구현체를 제공한다.
디자인 철학
- Clear: API의 기능이 명확 (epoch time 사용하거나 getTime() 썼는데 Date 반환한다던가 하지 않음)
- Fluent: null을 리턴하거나 null을 받는 메소드가 없음
- Immutable: 기존의 인스턴스에 값을 더할 경우 새로운 인스턴스가 만들어짐
LocalDateTime now = LocalDateTime.now(); now.plus(10, ChronoUnit.DAYS); // Nothing Happens LocalDateTime plus = now.plus(10, ChronoUnit.DAYS);
- Extensible: API를 이용하여 다양한 시간을 나타내는 클래스를 만들 수 있다.
주요 API
- 기계용 시간 (machine time)과 인류용 시간(human time)으로 나눌 수 있다.
- 기계용 시간은 EPOCH (1970년 1월 1일 0시 0분 0초)부터 현재까지의 타임스탬프를 표현한다.
- 인류용 시간은 우리가 흔히 사용하는 연,월,일,시,분,초 등을 표현한다.
- 타임스탬프는 Instant를 사용한다.
- 특정 날짜(LocalDate), 시간(LocalTime), 일시(LocalDateTime)를 사용할 수 있다.
- 기간을 표현할 때는 Duration (시간 기반)과 Period (날짜 기반)를 사용할 수 있다.
- DateTimeFormatter를 사용해서 일시를 특정한 문자열로 포매팅할 수 있다.
Date와 Time API
지금 이 순간을 기계 시간으로 표현하는 방법
Instant.now()
: 현재 UTC (GMT)를 리턴한다.- Universal Time Coordinated = Greenwich Mean Time
Instant now = Instant.now();
System.out.println(now);
System.out.println(now.atZone(ZoneId.of("UTC")));
ZonedDateTime zonedDateTime = now.atZone(ZoneId.systemDefault());
System.out.println(zonedDateTime);
인류용 일시를 표현하는 방법
LocalDateTime.now()
: 현재 시스템 Zone에 해당하는(로컬) 일시를 리턴한다.LocalDateTime.of(int, Month, int, int, int, int)
: 로컬의 특정 일시를 리턴한다.ZonedDateTime.of(int, Month, int, int, int, int, Zoneid)
: 특정 Zone의 특정 일시를 리턴한다.
LocalDateTime now2 = LocalDateTime.now();
System.out.println(now2);
LocalDateTime birthday =
LocalDateTime.of(1992, 06, 04, 04, 50, 06);
ZonedDateTime nowInKorea = zonedDateTime.now(ZoneId.of("Asia/Seoul"));
System.out.println(nowInKorea);
Instant nowInstant = Instant.now();
ZonedDateTime zonedDateTime1 = nowInstant.atZone(ZoneId.of("Asia/Seoul"));
System.out.println(zonedDateTime1);
기간을 표현하는 방법
Period
/Duration
:between()
,until()
Period between = Period.between(today, thisYearBirthday);
System.out.println(between.getDays());
Period until = today.until(thisYearBirthday);
System.out.println(until.get(ChronoUnit.MONTHS));
// 1
// 1
Period
는 기간을 연, 월, 일로 표현하기 때문에 30일이 넘어간 정보는 월에 담기게 된다. 전체 일수를 계산하고 싶다면 ChronoUnit
이 제공하는 between(now, future)
을 사용하면 된다.
기계 시간 사이의 기간을 계산할 때는 Duration
, 인류용 일시 사이의 기간을 계산할 때는 Period
를 사용한다.
파싱 또는 포매팅
LocalDateTime.parse(String, DateTimeFormatter)
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("MM/d/yyyy");
LocalDate date = LocalDate.parse("06/04/1992", formatter);
System.out.println(date);
System.out.println(today.format(formatter));
// 1992-06-04
// 05/3/2021
레거시 API 지원
GregorianCalendar
와Date
타입의 인스턴스를Instant
나ZonedDateTime
으로 변환 가능java.util.TimeZone
에서java.time.ZoneId
로 상호 변환 가능
ZoneId newZoneAPI = TimeZone.getTimeZone("PST").toZoneId();
TimeZone legacyZoneAPI = TimeZone.getTimeZone(newZoneAPI);
GregorianCalendar gregorianCalendar = new GregorianCalendar();
ZonedDateTime dateTime = gregorianCalendar.toInstant().atZone(ZoneId.systemDefault());
GregorianCalendar from = GregorianCalendar.from(dateTime);
Instant newInstant = new Date().toInstant();
Date legacyInstant = Date.from(newInstant);