12. 새로운 날짜와 시간 API
자바 API는 복잡한 어플리케이션을 만드는 데 필요한 여러가지 유횽한 컴포넌트 제공.
자바 API가 항상 완벽한 것은 아님.
대부분의 자바 개발자가 지금까지의 날짜와 시간 관련 기능에 불만족.
- 자바 8에서 지금까지의 문제를 개선하는 새로운 날짜와 시간 API 제공.
자바1.0에서는 java.util.Date 클래스 하나로 날짜와 시간 관련 기능 제공.
- 날짜를 의미하는 Date라는 클래스의 이름과 달리 Date 클래스는 특정 시점을 날짜가 아닌 밀리초 단위로 표현.
- 게다가 1900년을 기준으로 하는 오프셋, 0에서 시작하는 달 인덱스 등 모호한 설계로 유용성 저하.
- Date는 JVM 기본시간대인 CET, 즉, 중앙 유럽 시간을 사용.
- Date클래스가 자체적으로 시간대 정보를 알고 있는 것도 아님.
자바1.0의 Date클래스에 문제가 있다는 점에는 의문의 여지가 없지만 과거 버전과 호환성을 깨뜨리지 않으면서 이를 해결할 방법이 없음.
- 자바1.1에서는 Date클래스의 열 메서드를 사장(Deprecated) 시키고 java.util.Calendar라는 클래스를 대안으로 제공.
- 안타깝게도 Calendar 클래스 역시 쉽게 에러를 일으키는 설계 문제를 갖고 있음.
DateFormat에도 문제가 있었음.
- DateFormat은 스레드에 안전하지 않음. 즉, 두 스레드가 동시에 하나의 포메터로 날짜를 파싱할 때 예기치 못한 결과 발생 가능.
Date와 Calendar는 모두 가변 클래스.
부실한 날짜와 시간 라이브러리 때문에 많은 개발자는 Joda-Time같은 써드파티 날짜와 시간 라이브러리를 사용해옴.
오라클은 좀 더 훌륭한 날짜와 시간 API 제공을 결정.
- 자바8에서는 Joda-Time의 많은 기능을 java.time 패키지로 추가.
1 LocalDate, LocalTime, Instant, Duration, Period
- java.time 패키지는 LocalDate, LocalTime, LocalDateTime, Instant, Duration, Period등 새로운 클래스 제공.
1.1 LocalDate와 LocalTime 사용
- 새로운 날짜와 시간 API를 사용할 때 처음 접하게 되는 것은 LocalDate.
- LocalDate 인스턴스는 시간을 제외한 날짜를 표현하는 불변 객체.
> 특히 LocalDate 객체는 어떤 시간대 정보도 포함하지 않음.
- 정적 팩토리 메서드 of로 LocalDate 인스턴스 생성 가능.
- 팩토리 메서드 now는 시스템 시계의 정보를 이용해서 현재 날짜 정보를 얻음.
- get 메서드에 TemporalField를 전달해서 정보를 얻는 방법도 존재.
> TemporalField는 시간 관련 객체에서 어떤 필드의 값에 접근할지 정의하는 인터페이스.
> 열거자 CheoneField는 TemporalField인터페이스를 정의하므로 ChronoField의 열저가 요소를 이용해 원하는 정보를 얻을 수 있음.
- 13:45:20 같은 시간은 LocalTime 클래스로 표현 가능.
- 날짜와 시간 문자열로 LocalDate와 LocalTime의 인스턴스를 만드는 방법도 존재.
> parse 정적 메서드 사용 가능.
* ex) LocalDate date = LocalDate.parse("2017-07-24");
* ex) LocalTime time = LocalTime.parse("13:45:20");
- parse 메서드에 DateTimeFormatter 전달 가능.
- DateTimeFormatter의 인스턴스는 날짜, 시간 객체의 형식을 지정.
> DateTimeFormatter는 java.util.DateFormat 클래스를 대채하는 클래스.
- 문자열을 LocalDate나 LocalTime으로 파싱할 수 없을 때 parse 메서드는 DateTimeParseException(RuntimeException을 상속 받는 예외) 발생.
1.2 날짜와 시간 조합
- LocalDateTime은 LocalDate와 LocalTime을 쌍으로 갖는 복합 클래스.
- 날짜와 시간 모두 표현이 가능하며 날짜와 시간 조합도 가능.
- LocalDate의 atTime 메서드에 시간을 제공하거나 LocalTime의 atDate 메서드에 날짜를 제공해서 LocalDateTime 생성 가능.
1.3 Instant: 기계의 날짜와 시간
- 사람은 보통 주, 날짜, 시간, 분으로 날짜와 시간을 계산.
- 하지만 기계에서는 이와 같은 단위로 시간을 표현하기 어려움.
- 기계의 관점에서는 연속된 시간에서 특정 지점을 하나의 큰 수로 표현하는 것이 가장 자연스러운 시간 표현 방법.
- 새로운 java.time.Instant 클래스에서는 이와 같은 기계적인 관점에서 시간을 표현.
> 즉, Instant 클래스는 유닉스 에포크 시간을 기준으로 특정 지점까지의 시간을 초로 표현.
* Unix epoch time(1970년 1월1일 0시 0분 0초 UTC)
- LocalDate등을 포함하여 사람이 읽을 수 있는 날짜 시간 클래스에서 그랬던 것 처럼 Instant클래스도 사람이 확인 가능한 시간표시 메서드 now 제공.
> 하지만, Instant는 기계 전용의 유틸리티라는 점을 상기.
> 즉, Instant는 초와 나노초 정보를 포함.
> 따라서 Instant는 사람이 읽을 수 있는 시간 정보를 제공하지 않음.
- Instant에서는 Duration과 Period 클래스를 함께 사용 가능.
1.4 Duration과 Period 정의
- Temporal 인터페이스는 특정 시간을 모델링하는 객체으 값을 어떻게 읽고 조작할지 정의.
- Duration 클래스의 정적 팩토리 메서드 between으로 두 시간의 객체 사이의 지속시간 생성 가능.
- LocalDateTime은 사람이 사용, Instant는 기계가 사용하도록 만들어진 클래스로 두 인스턴스는 서로 혼합 불가.
- 또한 Duration 클래스는 초와 나노초로 시간 단위를 표현하므로 between 메서드에 LocalDate 전달 불가.
- 년,월,일로 시간을 표현할 때는 Period 클래스를 사용.
> 즉, Period 클래스의 팩토리 메서드 between을 이용하면 두 LocalDate의 차이 확인 가능.
- Duration과 Period 클래스는 자신의 인스턴스를 만들 수 있도록 다양한 팩토리 메서드 제공.
표 12-1 간격을 표현하는 날짜와 시간 클래스의 공통 메서드
메서드 |
정적 |
설명 |
between |
네 |
두 시간 사이의 간격을 생성함. |
from |
네 |
시간 단위로 간격을 생성함. |
of |
네 |
주어진 구성 요소에서 간격 인스턴스를 생성함. |
parse |
네 |
문자열을 파싱해서 간격 인스턴스를 생성함. |
addTo |
아니요 |
현재값의 복사본을 생성한 다음에 지정된 Temporal 객체에 추가함. |
get |
아니요 |
현재 간격 정보값을 읽음. |
isNegarive |
아니요 |
간격이 음수인지 확인함. |
isZero |
아니요 |
간격이 0인지 확인함, |
minus |
아니요 |
현재값에서 주어진 시간을 뺀 복사본을 생성함. |
multipliedBy |
아니요 |
현재값에서 주어진 값을 곱한 복사본을 생성함, |
negated |
아니요 |
주어진 값의 부호를 반전한 복사본을 생성함. |
plus |
아니요 |
현재값에서 주어진 시간을 더한 복사본을 생성함. |
subtractFrom |
아니요 |
지정된 Temporal 객체에서 간격을 뺌. |
- 지금까지 살펴본 모든 클래스는 불변.
> 불변 클래스는 함수형 프로그래밍 그리고 스레드 안전성과 도메인 모델의 일관성을 유지하는데 좋은 특징.
> 하지만, 새로운 날짜와 시간 API에서는 변경된 객체 버전을 만들 수 있는 메서드를 제공.
2 날짜 조정, 파싱, 포매팅
- withAttribute 메서드로 기존의 LocalDate를 바꾼 버전을 직접 간단하게 생성 가능.
- Temporal 인터페이스는 LocalDate, LocalTime, LocalDateTime, Instant처럼 특정 시간을 정의.
> 정확히 표현하자면 get과 with메서드로 Temporal 객체의 필드값을 읽거나 수정 가능.
- 어떤 Temporal 객체가 지정된 필드를 지원하지 않으면 UnsupportedTemporalTypeExcetion이 발생.
- 메서드의 인수에 숫자와 TemporalUnit 활용 가능.
- ChronoUnit 열거형은 TemporlaUnit 인터페이스를 쉽게 활용할 수 있는 구현을 제공.
- LocalDate, LocalTime, LocalDateTime, Instant등 날짜와 시간을 표현하는 모든 클래스는 서로 비슷한 메서드를 제공.
표 12-2 특정 시점을 표현하는 날짜 시간 클래스의 공통 메서드
메서드 |
정적 |
설명 |
from |
네 |
주어진 Temporal 객체를 이용해서 클래스의 인스턴스를 생성함. |
now |
네 |
시스템 시계로 Temporal 객체를 생성함. |
of |
네 |
주어진 구성 요소에서 Temporal 객체의 인스턴스를 생성함. |
parse |
네 |
문자열을 파싱해서 Temproal 객체를 생성함. |
atOffset |
아니요 |
시간대 오프셋과 Temporal 객체를 합침. |
atZone |
아니요 |
시간대와 Temporal 객체를 합침. |
format |
아니요 |
지정된 포매터를 이용해서 Temporal 객체를 문자열로 변환함.(Instant는 미지원) |
get |
아니요 |
Temporal 객체의 상태를 읽음. |
minus |
아니요 |
특정 시간을 뺀 Temporal 객체의 복사본을 생성함. |
plus |
아니요 |
특정 시간을 더한 Temporal 객체의 복사본을 생성함. |
with |
아니요 |
일부 상태를 바꾼 Temporal 객체의 복사본을 생성함. |
2.1 TemporalAdjusters 사용하기
- 좀 더 다양한 동작을 수행할 수 있는 기능을 제공하는 TemporalAdjuster를 통해 다양한 문제 해결 가능.
- 날짜와 시간 API는 다양한 상황에서 사용할 수 있도록 다양한 TemporalAdjuster 제공.
표 12-3 TemporalAdjusters 클래스의 팩토리 메서드
메서드 |
설명 |
dayOfWeekInMonth |
3월의 둘째 화요일’처럼 서수 요일에 해당하는 날짜는 반환하는 TemporalAdjuster를 반환함. |
firstDayOfMonth |
현재 달의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환함. |
firstDayOfNextMonth |
다음 달의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환함, |
firstDayOfNextYear |
내년의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환함. |
firstDayOfYear |
올해의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환함. |
firstInMonth |
3월의 첫 번째 화요일’처럼 현재 달의 첫 번째 요일에 해당하는 날짜를 반환하는 TemporalAdjuster를 반환함. |
lastDayOfMonth |
현재 달의 마지막 날짜를 반환하는 TemporalAdjuster를 반환함. |
lastDayOfNextMonth |
다음 달의 마지막 날짜를 반환하는 TemporalAdjuster를 반환함. |
lastDayOfYear |
올해 마지막 날짜를 반환하는 TemporalAdjuster를 반환함. |
lastInMonth |
3월의 마지막 화요일’처럼 현재 달의 마지막 요일에 해당하는 날짜를 반환하는 TemporalAdjuster를 반환함. |
next |
현재 날짜 이후로 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환함.(현재 날짜는 미포함) |
previous |
현재 날짜 이후로 역으로 날짜를 거슬러 올라가며 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환함(현재 날짜 미포함) |
nextOrSame |
현재 날짜 이후로 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환함(현재 날짜 포함) |
previousOrSame |
현재 날짜 이후로 역으로 날짜를 거슬로 올라가며 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환함(현재 날짜 포함) |
- TemporalAdjuster를 이용하면 좀 더 복잡한 날짜 조정 기능을 직관적으로 해결 가능.
> 추가로 필요한 기능이 정의되어 있지 않을 때 비교적 쉽게 커스텀 TemporalAdjuster 구현 가능.
* TemporalAdjuster 인터페이스는 하나의 메서드만 정의함.(하나의 메서드만 정의하므로 함수형 인터페이스)
* TemporalAdjuster 인터페이스 구현은 Temporal 객체를 어떻게 다른 Temporal 객체로 변환할지 정의.
2.2 날짜와 시간 객체 출력과 파싱
- 날짜와 시간 관련 작업에서 포매팅과 파싱은 서로 떨어질 수 없는 관계.
> 심지어 포매팅과 파싱 전용 패키지인 java.time.format이 새로 추가.
> 해당 패키지에서 가장 중요한 클래스는 DateTimeFormatter.
- DateTimeFormatter 클래스는 BASIC_ISO_DATE와 ISO_LOCAL_DATE 등의 상수를 미리 정의.
- DateTimeFormatter를 이용해서 날짜나 시간을 특정 형식의 문자열로 생성 가능.
- 반대로 날짜나 시간을 표현하는 문자열을 파싱해서 날짜 객체 다시 생성 가능.
- 날짜와 시간 API에서 특정 시점이나 간격을 표현하는 모든 클래스의 팩토리 메서드 parse를 이용해서 문자열을 날짜 객체로 생성 가능.
- 기존의 java.util.DateFormat 클래스와 달리 모든 DateTimeFormatter는 스레드에서 안전하게 사용할수 있는 클래스.
- DateTimeFormatterBuilder 클래스로 복합적인 포매터를 정의하므로써 좀 더 세부적으로 포매터 제어 가능.
> 즉, DateTimeFormatterBuilder 클래스로 대소문자를 구분하는 파싱, 관대한 규칙을 적용하는 파싱(정해진 형식과 정확하게 일치하지 않는 입력을 해석할 수 있도록 체험적 방식의 파서 사용), 패딩, 포매터의 선택사항 등을 활용.
3 다양한 시간대와 캘린더 활용 방법
- 새로운 날짜와 시간 API의 큰 편리함 중 하나는 시간대를 간단하게 처리할 수 있다는 점.
- 기존의 java.util.TimeZone을 대체할 수 있는 java.time.ZoneId 클래스가 새롭게 등장.
- 새로운 클래스를 이용하면 서머타임 같은 복잡한 사항이 자동으로 처리.
- 날짜와 시간 API에서 제 공하는 다른 클래스와 마찬가지로 ZoneId는 불변 클래스.
- 표준 시간이 같은 지역을 묶어서 시간대로 규정.
- ZoneRules 클래스에는 약 40개 정도의 시간대가 존재.
> ZoneId의 getRules()를 이용해서 해당 시간대의 규정 획득 가능.
3.1 UTC/GMT 기준의 고정 오프셋
- 때로는 UTC(Universal Time Coordinated)(협정 세계 시) / GMT(Greenwich Mean Time)(그리니치 표준시)를 기준으로 시간대를 표현하기도 함.
- 예로 "뉴욕은 런던보다 5시간 느리다"라고 표현 가능.
> ZoneId의 서브클래스인 ZoneOffset 클래스로 런던의 그리니치 0도 자오선과 시간값의 차이 표현 가능.
* ZoneOffset newYorkOffseet = ZoneOffset.of("-05:00");
> 실제로 미국 동부 표준시의 오프셋값은 -05:00.
* 하지만 서머타임을 제대로 처리할 수 없으므로 비권장 방식.
3.2 대안 캘린더 시스템 사용하기
- ISO-8601 캘린더 시스템은 실질적으로 전 세계에서 통용.
- 자바8에서는 추가로 4개의 캘린더 시스템을 제공.
- thaiBuddhistDate, MinguoDate, JapaneseDate, HijrahDate 4개의 클래스가 각각의 캘린더 시스템을 대표.
- 4개의 틀래스와 LocalDate 클래스는 ChronoLocalDate 인터페이스를 구현하는데, ChronoLocalDate는 임의의 연대기에서 특정 날짜를 표현할 수 있는 기능을 제공하는 인터페이스.
- 날짜와 시간 API의 설계자는 ChronoLocalDate보다는 LocalDate를 사용하라고 권고.
- 프로그램의 입출력을 지역화하는 상황을 제외하고는 모든 데이터 저장, 조작, 비즈니스 규칙 해석등의 작업에서 LocalDate를 사용해야 함.
이슬람력
- 자바8에 추가된 새로운 캘린더 중 HijrahDate(이슬람력)가 제일 복잡.
> 이슬람력에는 변형이 있기 때문.
- HijrahDate 캘린더 시스템은 태음월(lunar month)에 기초.
- 새로운 달을 결정할 떄 새로운 달을 전 세계 어디에서나 볼 수 있는지 아니면 사우디아라비아에서 처음으로 새로운 달을 볼 수 있는지 등의 변형 방법을 결정하는 메서드 존재.
- withVariant 메서드로 원하는 변형 방법 선택 가능.
- 자바8에서는 HijrahDate의 표준 변형 방법으로 Umm Al-Qura를 제공.
4 요약
- 자바8 이전 버전에서 제공하는 기존의 java.util.Date 클래스와 관련 클래스에서는 여러 불일치점들과 가변성, 어설픈 오프셋, 기본값, 잘못된 이름 결정 등의 설계 결함 존재.
- 새로운 날짜와 시간 API에서 날짜와 시간 객체는 모두 불변.
- 새로운 API는 각각 사람과 기계가 편리하게 날짜와 시간 정보를 관리할 수 있도록 두 가지 표현 방식 제공.
- 날짜와 시간 객체를 절대적인 방법과 상대적인 방법으로 처리 가능, 기존 인스턴스를 변환하지 않도록 처리 결과로 새로운 인스턴스가 생성.
- Temporaldjuster를 이용하면 단순히 값을 바꾸는 것 이상의 복잡한 동작 수행 가능. 자신만의 커스텀 날짜 변환 기능 정의 가능.
- 날짜와 시간 객체를 특정 포맷으로 출력하고 파싱하는 포매터 정의 가능. 패턴을 이용하거나 프로그램으로 포매터를 만들 수 있으며 포매터는 스레드 안정성을 보장.
- 특정 지역/장소에 상대적인 시간대 또는 UTC/GMT 기준의 오프셋을 이용해서 시간대 정의 가능. 이 시간대를 날짜와 시간 객체에 적용해서 지역화 가능.
- ISO-8601 표준 시스템을 준수하지 않는 캘린더 시스템도 사용 가능.
'Java8 > Java 8 in Action' 카테고리의 다른 글
null 대신 Optional (0) | 2017.07.12 |
---|---|
디폴트 메서드 (0) | 2017.07.05 |
리팩토링, 테스팅, 디버깅 (0) | 2017.07.03 |
병렬 데이터 처리와 성능 (0) | 2017.06.29 |
스트림으로 데이터 수집 (0) | 2017.06.29 |