자바에서 시간을 다루기 위한 클래스가 존재합니다.


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(2017131);
 
// 1~12 로 관리됨에 주목. 객체인 Month 로 관리되는 것에 한번 더 주목
{
    // 달 출력
    Month month = localDate.getMonth();
    System.out.println(month.getValue());
}
 
// 10시 5분 40초 표현
LocalTime time = LocalTime.of(10540);
 
// 날짜와 시간의 조합
{
    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(2017131);
 
// 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(010).boxed().reduce(LocalDate.of(201736), (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(1000), LocalTime.of(2320));
 
// 2016.1.5 ~ 2018.1.20 간격
Period period = Period.between(LocalDate.of(201615), LocalDate.of(2018120));
 
// 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 의 내부를 들여다 보게되니, 더 노력해야겠다는 생각이 드는 초보 프로그래머였습니다.


자바 8 인 액션
국내도서
저자 : 라울-게이브리얼 우르마(RAOUL-GABRIEL URMA),마리오 푸스코(MARIO FUSCO),앨런 마이크로프트(ALAN MYCROFT) / 우정은역
출판 : 한빛미디어 2015.04.01
상세보기







반응형
Posted by N'