Clean Code that Works.

Java 8이 릴리즈 되면서 새로운 Date, Time API도 같이 포함되어 릴리즈 되었다.

아래 링크에 보면 기존 Java의 Date, Time의 문제점에 대해서 잘 설명되어 있다. 한번씩 읽어 보면 좋을 듯 하다.
이런 저런 문제점이 있지만, 그냥 사용하고 있는데가 많다. 나도 그렇고.
정상혁님이 정리한 Java 날짜 및 시간(http://helloworld.naver.com/helloworld/textyle/645609 )

이 내용은 이 아티클(http://www.oracle.com/technetwork/articles/java/jf14-date-time-2125367.html)을 허접하게 번역한 문서이다.

===============================================================================================================

왜 새로운 Date, Time API가 필요할까?

오랜 시간동안 Java에서 제공하는 Date, Time API는 부족한 기능 지원을 포함한 여러가지 문제점을 가지고 있었다.
예를 들어 java.util.Date와 SimpleDateFormatter는 Thread-Safe 하지 않아서 잠재적인 동시성 문제를 가지고 있다. 또한 몇몇 클래스들은 형편없이 디자인 되어있는데 java.util.Date 는 1900년도 부터 시작한다거나 월(month)는 1부터 시작 하지만 일(day)는 0부터 시작하는 등 매우 직관적이지 않도록 설계되어 있다.

이런 이슈들을 포함한 다양한 원인들로 인해 몇몇 개발자들은 Joda-Time 같은 써드파티 라이브러리를 사용 하기도 한다. 
JDK 코어에서 이런 문제점들을 해결하고 더 좋고 직관적인 API들을 제공하기 위해 새롭게 재 디자인한  Date, Time API를 Java SE 8부터 제공 하기로 했다.

Joda-Time의 저작자인 Stephen Colebourne 및 오라클의 주도 아래 JSR 310 표준에 맞춰 Java SE 8의 java.time 패키지에 새로운 API를 포함하여 릴리즈 했다.

핵심 아이디어들

새로운 API들은 아래 3가지 핵심 아이디어들을 따르고 있다.

Immutable-value classes(불변 클래스) : 기존 Java에서의 formatter가 가지고 있는 심각한 취약점 중에 하나는 Thread-safe하지 않다는 것이다. 이 문제점은 개발자들에게 formatter를 사용할 때 날짜와 관련된 내용 뿐만 아니라 동시성에 관련된 내용도 함께 생각해야 된다는 문제점이 있다. 새롭게 제공되는 API는 이러한 문제점을 회피하기 위해 핵심 클래스들을 불변 클래스로 만들고 잘 정의된 값을 제공 한다.

도메인 주도 개발 : 새로운 API 모델은 Date, Time을 사용할때 분명하게 다른 방법을 제공하도록 디자인됬다. 이 차이점은 이전 Java 버전에서는 명확하지 않게 정의 되어 있는데 예를 들어 java.util.Date 는 timeline(UNIX 시간을 기준으로 밀리세컨드)을 제공하지만 만약 toString을 호출하면 해당 timezone에 해당하는 결과를 표시함으로써 개발자들을 햇갈리게 한다. 도메인 주도 개발은 장기적으로 봤을때 명확하고 이해하기 쉽도록 해 주지만 기존 애플리케이션에 새로운 API를 제공할 때 기존 Date model과 비교해서 잘 사용할 수 있도록 해야 한다.

연대의 분리 : 새로운 API는  개발자들이 ISO-8601 표준을 사용하도록 강제 하지 않고 일본이나 태국처럼 세계 어디서나 서로 다른 시간대를 가지고 사용할 수 있도록 해준다. 이것은 표준 연대에서 작업해야되는 개발자들에게 다른 추가 부담을 주지 않고 개발에 집중 할 수 있도록 해준다.

LocalDate 와 LocalTime

새로운 API를 처음 접할때 만나게 되는것은 아마도 LocalDate와 LocalTime 클래스일 것이다. 이들은 Date와 Time에 관련된 내용을 제공하고 당신이 사용중인 달력과 시계와 동일하게 동작한다. LocalDateTime 이라는 LocalDate와 LocalTime의 복합 클래스도 존재한다.

시간대는 사람마다 다르게 적용되는데 시간대를 고정해서 사용할 필요가 없다면 Local 클래스를 사용하면 된다. 예를 들어 JavaFx 데스크탑 애플리케이션을 들 수 있는데, 이 애플리케이션에서 사용되는 시간에 일관성있는 시간을 제공할 수 있다.

Creating Objects

새로운 API의 핵심 클래스는 오브젝트를 생성하기 위해 다양한 factory 메서드를 사용한다. 오브젝트 자기 자신의 특정 요소를 가지고 오브젝트를 생성할 경우 of 메서드를 호출하면 되고 다른 타입으로 변경할 경우에는 from 메서드를 호출하면 된다. 이들은 서로 짝을 이루고 String 값을 파라미터로 받는다.

  1. LocalDateTime timePoint = LocalDateTime.now(); // 현재의 날짜와 시간
  2. LocalDate.of(2012, Month.DECEMBER, 12); // 2012-12-12 from values
  3. LocalDate.ofEpochDay(150); // 1970-05-31 middle of 1970
  4. LocalTime.of(17, 18); // 17:18 (17시 18분)the train I took home today
  5. LocalTime.parse("10:15:30"); // From a String

자바에서 사용되는 표준 Getter 방식에 따라 아래와 같이 사용할 수 있다.

  1. LocalDate theDate = timePoint.toLocalDate();
  2. Month month = timePoint.getMonth();
  3. int day = timePoint.getDayOfMonth();
  4. timePoint.getSecond();

날짜를 변경할 때 오브젝트의 값을 변경해야 되는데 신규 API는 불변 클래스이기 때문에 사용할때 새로운 오브젝트로 생성되서 값을 리턴해준다. 아래 setter 사용하는것을 참조 하라.
서로 다른 값들을 가지고 날짜를 수정하는 방법역시 제공한다.

  1. // 2010-08-10으로 세팅된 신규 Object를 리턴
  2. LocalDateTime thePast = timePoint.withDayOfMonth(10).withYear(2010);
  3. // 2010-08-10에서 3주를 더하고 또 3주를 더한 2010-09-21을 리턴
  4. LocalDateTime yetAnother = thePast.plusWeeks(3).plus(3, ChronoUnit.WEEKS);

신규 API는 adjuster(일반적인 로직을 감싸서 사용되는 코드 블록)의 개념도 가지고 있다. 하나 이상의 필드를 세팅할때 with를 사용하고 날짜나 시간을 더하거나 뺄때 plus를 사용할 수 있다. 값 객체는 조정자 처럼 동작할 수 있는데 객체 자신이 가지고 있는 값을 가지고 갱신한다. API에 정의된 내장 adjuster들도 있지만 사용자가 로직을 정의한 adjuster도 만들어서 사용할 수 있다.

  1. import static java.time.temporal.TemporalAdjusters.lastDayOfMonth;
  2. import static java.time.temporal.TemporalAdjusters.next;
  3. import static java.time.DayOfWeek.*;
  4. LocalDateTime timePoint = LocalDateTime.now()
  5. foo = timePoint.with(lastDayOfMonth()); // 2014-08-31
  6. bar = timePoint.with(previousOrSame(ChronoUnit.WEDNESDAY)); // 2014-08-27
  7. // Using value classes as adjusters, 2014-08-26
  8. timePoint.with(LocalTime.now());

Truncation(날짜 자르기)

신규 API는 날짜, 시간, 일 등에 해당하는 값 들을 잘라내서 표현할 수 있도록 truncatedTo 라는 메서드를 제공한다.

  1. // 20:39:54.073, 20시 39분 54.073초
  2. LocalTime truncatedTime = LocalTime.now();
  3. // 20:39:54
  4. truncatedTime.truncatedTo(ChronoUnit.SECONDS);
  5. // 20:38
  6. truncatedTime.truncatedTo(ChronoUnit.MINUTES);

Time Zone Classes

ZonedDateTime은 아래 예 처럼 완벽하게 기술된 날짜와 시간이다. 특정 서버의 컨텍스트에 의존하지 않고 날짜와 시간을 표현하고 싶다면 이 클래스를 사용하면 된다.

  1. ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]");

OffeSetDateTime은 data를 직렬화 하여 DB에 넣거나 만약 서버가 다른 시간대를 사용한다면 직렬화된 형태로 logging time stamp를 표현하는데 사용할 수 있다.

OffsetTime은 아래와 같은 offset을 해결해준다.

  1. ZoneOffset offset = ZoneOffset.of("+1");
  2. OffsetTime time = OffsetTime.now();
  3. // changes offset, while keeping the same point on the timeline
  4. OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(offset);
  5. // changes the offset, and updates the point on the timeline
  6. OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(offset);
  7. // Can also create new object with altered fields as before
  8. changeTimeWithNewOffset.withHour(3).plusSeconds(2);

기존 자바에서 사용했던 java.util.TimeZone이 있지만 JSR 310를 구현한 클래스들은 전부 immutable 이지만 기존 time zone은 mutable이기 때문에 Java SE 8에서는 사용되지 않는다.


Periods

Period를 사용해서 "3달 하고 1일 뒤"같은 표현을 사용할 수 있다. 이는 타임라인을 사용하는 개념으로 지금까지 살펴봣던 클래스들과는 약간 다르다.

  1. // 2014-08-26
  2. LocalDate oldDate = LocalDate.now();
  3. ZonedDateTime oldDateTime = ZonedDateTime.now();
  4. // 2년2개월1일
  5. Period period = Period.of(2, 2, 1);
  6. // 2년2개월1일뒤, 2016-10-27
  7. LocalDate newDate = oldDate.plus(period);
  8. // 2년2개월1일전, 2012-06-25
  9. ZonedDateTime newDateTime = oldDateTime.minus(period);


Durations

Duration은 시간의 관점에서 측정된 타임라인으로 Period와 유사하지만 서로 다른 정밀도를 보여준다.

  1. Instant firstInstant= Instant.ofEpochSecond(1294881180 ); // 2011-01-13 01:13
  2. Instant secondInstant = Instant.ofEpochSecond(1294708260); // 2011-01-11 01:11
  3. Duration between = Duration.between(firstInstant, secondInstant);
  4. // negative because firstInstant is after secondInstant (-172920)
  5. long seconds = between.getSeconds();
  6. // get absolute result in minutes (2882)
  7. long absoluteResult = between.abs().toMinutes();
  8. // two hours in seconds (7200)
  9. long twoHoursInSeconds = Duration.ofHours(2).getSeconds();

plus, minus, with 기능을 사용할 수 있고 날짜나 시간 값도 수정 할 수 있다.


The Rest of the API

다른 일반적인 상황들에서 사용할만한 클래스들도 몇가지 지원 하는데

MonthDay 클래스는 Month와 Day가 짝을 이루고 있는것으로 생일을 표현할때 유용하다.
YearMonth 클래스는 신용카드의 유효기간 처럼 월과 달로 구성된 것에 사용하는데 적합하다.

Java 8의 JDBC는 새로 추가된 타입들을 지원 하지만 JDBC API의 공개적인 변경점은 없다. 기존에 존재 했던 generic setObject, getObject로 충분히 커버 가능하다.
아래 ANSI SQL의 타입이나 vendor들이 정의한 클래스는 아래 처럼 매핑된다.

ANSI SQLJava SE 8
DATELocalDate
TIMELocalTime
TIMESTAMPLocalDateTime
TIME WITH TIMEZONEOffsetTime
TIMESTAMP WITH TIMEZONEOffsetDateTime

결론
Java SE 8은 java.time 패키지에 포함된 새로운 날짜 와 시간 API를 장착했고 이를 통해 개발자에게 기존에 비해 훨씬 더 훌륭한 안정성과 기능을 제공한다.
새로운 API는 다양한 사용사례를 포함하고 폭넓게 사용될 수 있도록 잘 구성되어 있다.