JAVA8 에 추가된 새로운 날짜 & 시간 API.
자바에서 시간을 다루기 위한 클래스가 존재합니다.
JAVA8 IN ACTION 에 따르면,
자바 1.0 에서부터 Date 클래스를 지원했지만 0부터 시작한 달, 애매한 offset, 자체적인 시간대의 부재 등 부실한 설계가 있었고, 자바 1.1 에서는 호환성을 깨트리지 않으면서 조금 더 유용하게 사용할 수 있게 설계한 Calendar 가 등장했습니다.
Calendar 에서 Date 의 개선사항이 존재했었지만 0부터 시작하는 달 등 애매한 내용은 아직 남아있었고, 제일 큰 문제는 Date 와 Calendar 가 동시에 존재함으로써 자바개발자들에게 혼란을 야기시켰다고 합니다.
아래는 기존 시간 관련 클래스를 사용하기 위한 방법입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Date 클래스 사용 Date date = new Date(); { System.out.println(date.getTime()); // 다른 getXXX 는 Deprecated 되었습니다. } // Calendar 사용. Calendar calendar = Calendar.getInstance(); { calendar.get(Calendar.YEAR); // 년도. calendar.get(Calendar.MONTH); // 0 부터 시작하는 애매한 달 } | cs |
JAVA8 부터는 오라클에서 새로운 시간 관리 API 를 내놓았으며, 이는 JAVA8 의 다른 API (Stream, Optional 등)와 같이 Builder 패턴을 사용하여 사용성을 높였다는 것에 초점을 맞춰볼 수 있습니다.
이번 포스팅에서는 핵심이 되는 부분을 중점으로 다뤄볼 생각이며, 나머지는 메소드명이나 API 문서를 보면 쉽게 사용할 수 있을 것이라 생각합니다.
1. LocalDate, LocalTime, LocalDateTime
종종 우리는 기존 Calendar 나 Date 에서 날짜로부터 시간(시,분,초)을 제거하여 데이터처리를 하는 경우가 빈번하였었습니다. 그러나 JAVA API 에서는 날짜와 시간을 분리하여 관리할 수 있으며, 혹은 원할 경우 합쳐서 사용할 수도 있습니다.
아래는 새 자바 시간 관리 클래스들의 사용법입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // 2017년 1월 31일 표현 LocalDate localDate = LocalDate.of(2017, 1, 31); // 1~12 로 관리됨에 주목. 객체인 Month 로 관리되는 것에 한번 더 주목 { // 달 출력 Month month = localDate.getMonth(); System.out.println(month.getValue()); } // 10시 5분 40초 표현 LocalTime time = LocalTime.of(10, 5, 40); // 날짜와 시간의 조합 { LocalDateTime dateTime = LocalDateTime.of(localDate, time); System.out.println(dateTime.format(DateTimeFormatter.ISO_DATE_TIME)); } // Print Result // 1 // 2017-01-31T10:05:40 | cs |
개념적인 날짜와 시간을 객체로 분리하였으며, 이를 조립할 수 있다는 것이 매우 인상적입니다. 또한 기존 시간 클래스들과 달리 달 출력이 1부터 시작된다는 것이 마음에 드는군요. ㅡㅡ^
2. 날짜 조정
날짜에 대해서 특정정보를 출력하는 것과 더불어 날짜 데이터를 조정할 수 있습니다.
예를들어 "3일 전 혹은 후의 날짜는?" 과 같이 특정 날짜개념을 연산할 수 있고, 입력 또한 할 수 있습니다.
날짜를 입력을 하기 위해서는 withXXX 메소드를 연산을 하기 위해서는 plusXXX, minusXXX 등의 메소드를 사용할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 2017년 1월 31일 표현 LocalDate localDate = LocalDate.of(2017, 1, 31); // 2016 년 1월 5일로 세팅. LocalDate withDate = localDate.withYear(2016).withDayOfMonth(5); System.out.println(withDate.format(DateTimeFormatter.ISO_DATE)); // 2017 년 1월 31일의 2일 뒤는? LocalDate plusDays = localDate.plusDays(2); System.out.println(plusDays.format(DateTimeFormatter.ISO_DATE)); // 2017 년 1월 31일의 3일 전은? LocalDate minusDays = localDate.minusDays(2); System.out.println(minusDays.format(DateTimeFormatter.ISO_DATE)); // PRINT RESULT // 2016-01-05 // 2017-02-02 // 2017-01-29 | cs |
앞서 수행했던 간단한 연산 이외에도 특정 전략에 맞춘 날짜 조정도 가능합니다.
예를들어 "현재 달의 첫 번째 날짜는?", "현재 달의 마지막 날짜는?", "다음주 수요일은" 과 같은 질의처럼 말이죠.
with 메소드를 통해 전략적인 날짜조정이 가능합니다.
전략 인터페이스인 TempralAdjusters 를 작성해주면 되죠. ㅡㅡ^
1 2 3 4 | @FunctionalInterface public interface TemporalAdjuster { Temporal adjustInfo(Temporal temporal); } | cs |
예제를 한번 만들어 보죠.
보통 사람들은 평일에 일을 하지만, 제 사수 형오리의 경우 목요일에 사정이 있어서 토요일에 대체 근무를 하고 있습니다. 즉 형오리의 쉬는 날은 목요일과 일요일입니다. 형오리의 다음 근무일을 구하는 전략을 구해보죠.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | TemporalAdjuster 형오리의_근무일_전략 = temporal -> { // week 객체 생성. DayOfWeek week = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK)); // 형오리의 쉬는 날의 전날 Collection<DayOfWeek> hollyDays = Arrays.asList(DayOfWeek.WEDNESDAY, DayOfWeek.SATURDAY); return temporal.plus(hollyDays.contains(week) ? 2 : 1, ChronoUnit.DAYS); }; IntStream.range(0, 10).boxed().reduce(LocalDate.of(2017, 3, 6), (date, num) -> { LocalDate nextDay = date.with(형오리의_근무일_전략); Optional.of(nextDay). map(localDate -> localDate.format(DateTimeFormatter.ofPattern("yyyy. MM. dd (E)").withLocale(Locale.KOREA))). ifPresent(System.out::println); return nextDay; }, (d1, d2) -> d1); // PRINT RESULT // 2017. 03. 07 (화) // 2017. 03. 08 (수) // 2017. 03. 10 (금) // 2017. 03. 11 (토) // 2017. 03. 13 (월) // 2017. 03. 14 (화) // 2017. 03. 15 (수) // 2017. 03. 17 (금) // 2017. 03. 18 (토) // 2017. 03. 20 (월) | cs |
하지만 모든 전략을 구현해야하는 것은 아닙니다. 이미 JAVA8 에서는 많이 사용할 법한 전략을 팩토리 메소드로 제공하며, TemporalAdjusters 클래스에서 확인해 볼 수 있습니다. (예를들면, 현재 달의 마지막 날짜라던지.. 등 ㅡㅡ^)
3. Duration, Period
각 객체의 시간 및 날짜의 간격을 알고 싶은 경우가 존재하며, 이를 위해 JAVA8 에서는 Duration, Period 클래스를 제공합니다.
Duration 은 시간단위로 간격을 표현하며, 앞서 언급한 LocalTime, LocalDateTime, Instant(기계를 위한 시간객체) 를 이용할 수 있습니다. (LocalDate 는 시간단위로 표현할 수 없기 때문에 Duration 을 사용할 수 없습니다.)
Period 는 날짜단위의 간격을 표현하며, LocalDate 를 사용할 수 있습니다. 뒤의 나올 내용인 시간대 적용 시, 섬머타임을 바탕으로 간격을 표현하려면 Period 를 사용해야합니다.
또한 of 메소드를 사용하여, 기타의 임의 간격을 표현할 수 있습니다.
1 2 3 4 5 6 7 8 | // 10:00 ~ 23:20 간격 Duration duration = Duration.between(LocalTime.of(10, 00), LocalTime.of(23, 20)); // 2016.1.5 ~ 2018.1.20 간격 Period period = Period.between(LocalDate.of(2016, 1, 5), LocalDate.of(2018, 1, 20)); // 5일 간격 Period fiveDays = Period.ofDays(5); | cs |
4. 시간의 파싱과 포맷팅
기존 SimpleDateFormat 과 같이 문자열에서 날짜를 parse 나 날짜 객체를 format 하여 출력하는 기능 역시 수행할 수 있습니다.
새 API 의 등장과 함께 DateFormatter 라는 팩토리 클래스에서 포맷팅 객체를 생산할 수 있으며, 많이 사용할 법한 ISO_LOCAL_DATE (yyyy-MM-dd) 같은 패턴은 바로 사용할 수 있습니다.
1 2 3 4 5 6 | LocalDate date = LocalDate.parse("2017-04-07", DateTimeFormatter.ISO_LOCAL_DATE); Optional.of(date).map(d -> d.format(DateTimeFormatter.ofPattern("yyyy. MM. dd"))).ifPresent(System.out::println); // PRINT RESULT // 2017. 04. 07 | cs |
5. 특정 시간대 적용
만드는 제품이 세계적이라면, 특정 지역의 시간대를 사용하고 싶을 경우가 있습니다.
기존에는 TimeZone 클래스가 그 역할을 했으며, 새 API 로는 ZoneId 클래스가 등장했습니다.
ZoneId 를 사용하면, 시간대 변경 시 썸머타임과 같은 복잡한 문제에 대해 고려하지 않아도 됩니다.
사용법은 지역ID 를 통해 객체를 생성할 수 있으며, LocalDate 군 클래스들을 ZonedDate 군 클래스로 랩핑할 수 있습니다. (즉 로컬 시간을 특정 시간대로 변환한다는 이야기이며, 아래 사진은 Local-Zoned 간의 관계를 나타낸 사진입니다. )
[출처 : JAVA8 IN ACTION]
예제는 아래와 같이 사용할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | ZoneId zoneId = ZoneId.of("Europe/Jersey"); LocalDateTime date = LocalDateTime.now(); ZonedDateTime zonedDateTime = date.atZone(zoneId); System.out.println(date.format(DateTimeFormatter.ISO_DATE_TIME)); System.out.println(zonedDateTime.format(DateTimeFormatter.ISO_DATE_TIME)); System.out.println(zonedDateTime.format(DateTimeFormatter.ISO_INSTANT)); // Print Result // 2017-04-05T00:47:21.346 // 2017-04-05T00:47:21.346+01:00[Europe/Jersey] // 2017-04-04T23:47:21.346Z | cs |
새로운 JAVA 날짜&시간 API 는 하위 호환성을 위해 Calendar, Date 를 fromXX 등으로 지원합니다. 즉 이전 작성된 코드들을 크게 변경하지 않아도 됩니다.
이번 포스팅을 하기 전에 Android 에서 사용할 Date, Calendar 를 이용한 빌더를 만든적이 있었는데요. (JAVA8 지원이 안되기 때문에....)
어떤면에서는 쓰기 익숙한면도 있지만, JAVA8 의 API 의 설계에 비해 많이 약소함을 느끼고 있습니다. (그들은 많은 시간 고민해서 배포한 API 겠죠. TimeBuilder 역시 조금 더 손을 봐야겠다는 생각이 듭니다. ㅡㅡ^)
이번 포스팅을 하며 각 API 의 내부를 들여다 보게되니, 더 노력해야겠다는 생각이 드는 초보 프로그래머였습니다.
|
'개발이야기 > 함수형 방법론' 카테고리의 다른 글
[JAVA8 포스팅 끝] 용어정리 및 키워드. (0) | 2017.04.11 |
---|---|
CompleteableFuture 를 이용한 비동기 처리 조합 (0) | 2017.03.24 |
CompleteableFuture 를 이용한 비동기 처리 (0) | 2017.03.23 |
Null 대신 Optional! (0) | 2017.03.14 |
디폴트 메소드와 다중상속 (0) | 2017.03.13 |