자바 인 액션 실전 요약

Ch 12. 새로운 날짜와 시간 API

 

주니어인 내가 당장 알아야하는 챕터만으로 정리했다.

병렬 처리와 프로그래밍의 역사적 흐름과 같은 내용은 제외했다.

최대한 책의 내용을 그대로 요약하려 노력했고 내 의견은 기울여서 표현했다.

예제 코드는 내 프로젝트에서 따오거나 직접 작성한 코드들이다.

 

Chapter 12

새로운 날짜와 시간 API

 

자바 8 이전 버전에서 제공하는 java.util.Date 클래스는 여러 결함이 존재했다.

 

java.time 패키지는 

LocalDate, LocalTime, LocalDateTime, Instant, Duration, Period 와 같은 새로운 클래스를 제공한다.

 

다음은 LocalDate와 LocalTime의 간단한 예시이다.

LocalDate date = LocalDate.of(2017, 9, 21); // 2017년 9월 21일
int year = date.getYear(); // 2017년
DayOfWeek dow = date.getDayOfWeek(); // 목요일
boolean leap = date.isLeapYear(); // 윤년이 아니다.

 

LocalTime time = LocalTime.of(13, 45, 20); // 13:45:20
int hour = time.getHour(); // 13시

 

LocalDate date = LocalDate.parse("2017-09-21"); // 2017년 9월 21일
LocalTime time = LocalTime.parse("13:45:20");

 

LocalDateTime은 LocalDate와 LocalTime의 합이라고 생각하면 된다.

LocalDateTime dateTime1 = LocalDateTime.of(2017, Month.SEPTEMBER, 21, 13, 45, 20);
LocalDateTime dateTime2 = LocalDateTime.of(date, time);
LocalDateTime dateTime3 = date.atTime(13, 45, 20);
LocalDateTime dateTime4 = time.atDate(date);

 

Instant class

사람은 주, 날짜, 시간, 분으로 날짜와 시간을 계산한다.

하지만 기계는 연속된 시간에서 특정 지점을 하나의 큰 수로 표현하는 것이 자연스러운 시간 표현 방법이다.

java.time.Instant 클래스는 이와 같은 관점에서 시간을 표현한다.

유닉스 에포크 시간(Unix epoch time: 1970년 1월 1일 0시 0분 0초 UTC)를 기준으로 특정 지점까지의 시간을 초로 표현한다.

Instant 클래스는 나노초(10억분의 1초)의 정밀도를 제공한다.

 

예시

// 1970-01-01T00:00:03Z
Instant.ofEpochSecond(3)

 

Duration과 Period class

이번에는 두 시간 객체 사이의 지속시간을 만들어보자.

LocalTime time1 = LocalTime.of(13, 45, 20);
LocalTime time2 = LocalTime.of(14, 55, 20);
Duration duration = Duration.between(time1, time2);
Duration threeMinutes = Duration.ofMinutes(3);

Period period = Period.ofDays(10);

System.out.println(period);
System.out.println(duration);
System.out.println(threeMinutes);

// 위 출력 결과
// P10D
// PT1H10M
// PT3M

 

날짜와 시간 객체 출력과 파싱

날짜와 시간 관련 작업에 포매팅과 파싱은 서로 떨어질 수 없는 관계이다.

java.time.format.DateTimeFormatter 클래스를 살펴보면, 다음의 두 상수가 정의되어 있다.

public static final DateTimeFormatter ISO_LOCAL_DATE;
    static {
        ISO_LOCAL_DATE = new DateTimeFormatterBuilder()
                .appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
                .appendLiteral('-')
                .appendValue(MONTH_OF_YEAR, 2)
                .appendLiteral('-')
                .appendValue(DAY_OF_MONTH, 2)
                .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
    }
public static final DateTimeFormatter BASIC_ISO_DATE;
    static {
        BASIC_ISO_DATE = new DateTimeFormatterBuilder()
                .parseCaseInsensitive()
                .appendValue(YEAR, 4)
                .appendValue(MONTH_OF_YEAR, 2)
                .appendValue(DAY_OF_MONTH, 2)
                .optionalStart()
                .parseLenient()
                .appendOffset("+HHMMss", "Z")
                .parseStrict()
                .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
    }

 

이 상수들을 활용하면 다음과 같다.

LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE);

System.out.println(s1);
System.out.println(s2);

// 위 출력 결과
// 20140318
// 2014-03-18

 

가끔 사용한 적이 있는, 패턴에 맞게 날짜와 시간 출력하기는 다음과 같다.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);

System.out.println(date1);
System.out.println(formattedDate);
System.out.println(date2);

// 위 출력 결과
// 2014-03-18
// 18/03/2014
// 2014-03-18

 

시간대까지 저장하는 ZoneId, ZonedDateTime

우리는 서울, 도쿄와 같은 시간대를 설정하거나 서머타임(Daylight Saving Time, DST)을 처리해야할 수도 있다.

이러한 요소들을 처리할 수 있게 만들어주는 ZoneId와 ZonedDateTime에 대해서 알아보자.

 

표준 시간이 같은 지역을 묶어서 시간대 규칙 집합을 정의한다.

지역 ID는 '{지역}/{도시}' 형식으로 이루어 진다.

ZoneId romeZone = ZoneId.of("Europe/Rome");
ZoneId zoneId = TimeZone.getDefault().toZoneId();

System.out.println(romeZone);
System.out.println(zoneId);

// 위 출력 결과
// Europe/Rome
// Asia/Seoul

 

ZoneId는 LocalDate, LocalTime, LocalDateTime과 같이 ZonedDateTime 인스턴스로 변환할 수 있다.

LocalDate date = LocalDate.of(2014, 3, 18);
ZonedDateTime zonedDateTime1 = date.atStartOfDay(romeZone);
LocalDateTime localDateTime = LocalDateTime.of(2014, 3, 18, 14, 45);
ZonedDateTime zonedDateTime2 = localDateTime.atZone(romeZone);

System.out.println(zonedDateTime1);
System.out.println(zonedDateTime2);

// 위 출력 결과
// 2014-03-18T00:00+01:00[Europe/Rome]
// 2014-03-18T14:45+01:00[Europe/Rome]

 

앞서 나온 클래스들의 관계를 대략적으로 표현하면 다음과 같다.

 

추가 설명 요약

새로운 날짜, 시간 API는 모두 불변하다.

날짜와 시간 객체를 절대적인 방법과 상대적인 방법으로 처리할 수 있다.

기존 인스턴스를 변환하지 않도록 새로운 인스턴스가 생성된다.