본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[Java8 In Action] #12 새로운 날짜와 시간 API

by 돼지왕왕돼지 2019. 1. 1.

[Java8 In Action] #12 새로운 날짜와 시간 API


Java8 In Action 내용을 보며 정리한 내용입니다.

정리자는 기존에 Java8 을 한차례 rough 하게 공부한 적이 있고, Kotlin 역시 공부한 적이 있습니다.

위의 prerequisite 가 있는 상태에서 추가적인 내용만 정리한 내용이므로, 제대로 공부를 하고 싶다면 책을 구매해서 보길 권장합니다! 


1970, atZone, BASIC_ISO_DATE, Between, ChronoLocalDate, Chronology, custom temporaladjuster, DateTimeFormatter, datetimeformatter locale, datetimeformatter thread safe, DateTimeFormatterBuilder, Daylight saving time, dayOfWeekInMonth, DST, Duration, firstDayOfMonth, firstDayOfNextMonth, firstDayOfNextYear, firstDayOfYear, firstInMonth, format, getRules, GMT, HijrahDate, IANA Time Zone, Instant, ISO-8601, ISO_LOCAL_DATE, JapaneseDate, java8 date api, java8 in action, java8 time api, lastDayOfMonth, lastDayOfNextMonth, lastDayOfYear, lastInMonth, LocalDate, LocalDateTime, localtime, lunar month, MinguoDate, minusYears, next, nextOrSame, NOW, ofDateAdjuster, ofDays, ofEpochSecond, ofInstant, ofMinutes, ofPattern, ofWeeks, parse, parseCaseInsensitive, period, Plus, plusWeeks, previous, previousOrSame, Temporal, Temporal interface, TemporalAdjusters, ThaiBuddhistDate, timezone, toInstant, UnsupportedTempolraTypeException, UTC, With, withAttribute, withDayOfMonth, withYear, ZoneDateTime, ZoneId, ZoneOffset, ZoneRules, 불변 객체, 불변 클래스, 서머타임, 에포크 시간, 이슬람력, 태음력


12.1. LocalDate, LocalTime, Instant, Duration, Period


12.1.1. LocalDate 와 LocalTime 사용


-

LocalDate 는 시간을 제외한 날짜를 표현하는 불변 객체.

이 녀석은 어떤 시간대 정보도 포함하지 않는다.



-

정적 팩토리 메소드 of 로 LocalDate 인스턴스를 만들 수 있다.

LocalDate date = LocalDate.of(2014, 3, 18);
int year = date.getYear();
Month month = date.getMonth(); // MARCH
int day = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek(); // TUESDAY
int len = date.lengthOfMonth(); // 31 ( 3월의 달수 )
boolean leap = date.isLeapYear(); // false ( 윤년 아님 )



-

팩토리 메서드 now 는 시스템 시계의 정보를 이용해서 현재 날짜 정보를 얻는다.

LocalDate today = LocalDate.now();



-

아래와 같이 get 을 이용해 정보를 얻을 수도 있다.

int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);



-

시간은 LocalTime 클래스로 표현할 수 있다.

LocalTime time = LocalTime.of(13, 45, 20);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();



-

parse 를 이용하는 방법도 있다.

LocalDate date = LocalDate.parse(“2014-03-18”);
LocalTime time = LocalTime.parse(“13:45:20”);


parse 메서드에 DateTimeFormatter 를 전달할 수도 있다.

java.util.DateFormat 클래스를 대체하는 클래스다.




12.1.2. 날짜와 시간 조합


-

LocalDateTime 은 LocalDate 와 LocalTime 을 쌍으로 갖는 복합 클래스.

LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(13, 45, 20);
LocalDateTime dt4 = date.atTime(time);
LocalDateTime dt5 = time.atDate(date);



-

LocalDate date = dt1.toLocalDate();
LocalTime time = dt1.toLocalTime();




12.1.3. Instant: 기계의 날짜와 시간


-

Instant 클래스는 유닉스 에포크 시간(1970년 1월 1일 0시 0분 0초 UTC)을 기준으로 특정 지점까지의 시간을 초로 표현한다

Instant 클래스는 나노초의 정밀도를 제공한다.


// 아래 4개는 같은 Instant
Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
Instant.ofEpochSecond(2, 1_000_000_000); // 2초 이후의 1억 나노초(1초)
Instant.ofEpochSecond(4, -1_000_000_000); // 4초 이전의 1억 나노초(1초)


-

Instant 클래스도 사람이 확인할 수 있도록 시간을 표시해주는 정적 팩토리 메서드 now 를 제공한다.

Instant 는 기계 전용 유틸리티.




12.1.4. Duration 과 Period 정의


-

Instant, LocalDate, LocalTime, LocalDateTime 모두 Temporal interface 를 구현한다.

Temporal 인터페이스는 특정 시간을 모델링하는 객체의 값을 어떻게 읽고 조작할지 정의한다.



-

Duration d1 = Duration.between(time1, time2);
Duration d1 = Duration.between(dateTim1, dateTime2);
Duration d2 = Duration.between(instant1, instant2);



-

LocalDateTime 은 사람이 사용하도록, Instant 는 기계가 사용하도록 만들어진 클래스로 두 인스턴스는 서로 혼합할 수 없다.

Duration 클래스는 초와 나노초로 시간 단위를 표현하므로 between 메서드에 LocalDate 를 전달할 수 없다.



-

년, 월, 일로 시간을 표현할 때는 Period 클래스를 사용한다.

Period tenDays = Period.between(LocalDate.of(2014, 3, 8), LocalDate.of(2014, 3, 18));



-

Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);

Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);


-

새로 도입된 날짜 시간 클래스들은 모두 불변이다.

불변 클래스는 함수형 프로그래밍 그리고 스레드 안전성과 도메인 모델의 일관성을 유지하는 데 좋은 특징이 있다.






12.2. 날짜 조정, 파싱, 포매팅


-

withAttribute 메서드로 기존 LocalDate 를 바꾼 버전을 직접 간단하게 만들 수 있다.

LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.withYear(2011);
LocalDate date3 = date2.withDayOfMonth(25);
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);



-

with method 를 사용할 경우 field 를 지원하지 않는 경우 UnsupportedTempolraTypeException 이 발생한다.



-

LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.plusWeeks(1);
LocalDate date3 = date2.minusYears(3);
LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);




12.2.1. TemporalAdjusters 사용하기


-

다음 주 일요일, 돌아오는 평일, 어떤 달의 마지막 날 등 복잡한 날짜 조정 기능이 필요할 때가 있다.

이때는 오버로드된 버전의 with 메서드에 좀 더 다양한 동작을 수행할 수 있도록 하는 기능을 제공하는 TemporalAdjuster 를 전달하는 방법으로 해결 할 수 있다.

import static java.time.temporal.TemporalAdjusters.*;

LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY));
LocalDate date3 = date2.with(lastDayOfMonth());


-

TemporalAdjusters 클래스의 팩토리 메서드는 아래와 같다.


dayOfWeekInMonth : '3월의 둘째 화요일’ 느낌

firstDayOfMonth

firstDayOfNextMonth

firstDayOfNextYear

firstDayOfYear

firstInMonth : ‘3월의 첫 번째 화요일’ 느낌

lastDayOfMonth

lastDayOfNextMonth

lastDayOfYear

lastInMonth : ‘3월의 마지막 화요일’ 느낌

next

previous

nextOrSame

previousOrSame



-

TemporalAdjuster 를 custom 으로 만들 수도 있다.

@FunctionalInterface
public interface TemporalAdjuster{
    Temporal adjustInto(Temporal temporal);
}

TemporalAdjuster 를 표현식으로 정의하고 싶다면, TemporalAdjusters.ofDateAdjuster( temporal -> … ); 를 사용하는 것이 좋다.





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


-

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

DateTimeFormatter 가 새로 도입되었는데, 정적 팩토리 메서드와 상수를 이용해서 손쉽게 포매터를 만들 수 있다.

DateTimeFormatter 는 BASIC_ISO_DATE 와 ISO_LOCAL_DATE 등의 상수를 미리 정의하고 있다.

LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20140318
String s2 = date.format(DateTimeFormatter.ISO_LCAOL_DATE); // 2014-03-18

LocalDate date1 = LocalDate.parse(“20140318”, DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse(“2014-03-18”, DateTimeFormatter.ISO_LOCAL_DATE);



-

기준 DateFormat 클래스와 달리 모든 DateTimeFormatter 는 스레드 안전하다.



-

특정 패턴으로 포매터를 만들 수 있는 정적 팩토리 메서드도 제공된다.

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);



-

Locale 이 적용된 포매터도 사용할 수 있다.

DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern(“d. MMMM yyyy”, Locale.ITALIAN); LocalDate date1 = LocalDate.of(2014, 3, 18); String formattedDate = date.format(italianFormatter); // 18. marzo 2014 LocalDate date2 = LocalDate.parse(formattedDate, italianFormatter);



-

DateTimeFormatterBuilder 클래스로 복합적인 포매터를 정의해서 좀 더 세부적으로 포매터를 제어할 수 있다.

대소문자를 구분하는 파싱, 관대한 규칙을 적용하는 파싱, 패딩 등등의 고려를 할 수 있다.

DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
    .appendText(ChronoField.DAT_OF_MONTH)
    .appendLiteral(“. “)
    .appendText(ChronoField.MONTH_OF_YEAR)
    .appendLiteral(“ “)
    .appendText(ChronoField.YEAR)
    .parseCaseInsensitive()
    .toFormatter(Locale.ITALIAN);





12.3. 다양한 시간대와 캘린더 활용 방법


-

새로운 날짜와 시간 API 의 큰 편리함 중 하나는 시간대를 간단하게 처리할 수 있다는 점이다.

TimeZone 을 대체할 수 있는 ZoneId 클래스가 있다.

새로운 클래스를 이용하면 서머타임(Daylight Saving Time, DST) 와 같은 복잡한 사항이 자동으로 처리된다.

ZoneId 는 불변 클래스다.


표준 시간이 같은 지역을 묶어서 시간대로 규정한다.

ZoneRules 클래스에는 약 40개 정도의 시간대가 있다.

ZoneId 의 getRules() 를 이용해서 해당 시간대의 규정을 획득할 수 있다.

ZoneId romeZone = ZoneId.of(“Europe/Rome”);


지역 ID 는 ‘{지역}/{도시}’ 형식으로 이루어지며 IANA Time Zone DB 에서 제공하는 지역 집합 정보를 사용한다.



-

TimeZone 객체를 ZoneId 객체로 변환할 수 있다.

ZoneId zoneId = TimeZone.getDefault().toZoneId();


그리고 ZoneId 가 있다면 LocalDate, LocalDateTime, Instant 를 ZoneDateTime 인스턴스로 변환할 수 있다.

LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
ZoneDateTime zdt1 = date.atStartOfDay(romeZone);

LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
ZonedDateTime zdt2 = dateTime.atZone(romeZone);

Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(romeZone);



-

ZoneId 를 이용해서 LocalDateTime 을 Instant 로 바꾸는 방법도 있다. ( 그 반대도 )

LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCh, 18, 13, 45);
Instant instantFromDateTime = dateTime.toInstant(romeZone);

Instant instant = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);




12.3.1. UTC/GMT 기준의 고정 오프셋


-

아래의 코드는 “뉴욕은 런던보다 5시간 느리다” 를 표현한 것이다.

ZoneOffset newYorkOffset = ZoneOffset.of(“-05:00”);

ZoneOffset 은 ZoneId 이기는 하나, 위와 같은 방식의 사용은 서머타임을 제대로 처리할 수 없기 때문에 권장하지 않는 방식이다.




12.3.2. 대안 캘린더 시스템 사용하기


-

ISO-8601 캘린더 시스템은 실질적으로 전 세계에서 통용된다.

하지만 자바 8에서는 추가로 4개의 캘린더 시스템을 제공한다.

ThaiBuddhistDate, MinguoDate, JapaneseDate, HijrahDate.


위 4개의 클래스와 LocalDate 클래스는 ChronoLocalDate 인터페이스를 구현하는데, 이는 임의의 연대기에서 특정 날짜를 표현할 수 있는 기능을 제공하는 인터페이스다.


LocalDate 를 이용해서 이들 4개의 클래스 중 하나의 인스턴스를 만들 수 있다.

LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
JapaneseDate japaneseDate = JapaneseDate.from(date);



-

특정 Locale 과 Locale 에 대한 날짜 인스턴스로 캘린더 시스템을 만드는 방법도 있다.

Chronology 는 캘린더 시스템을 의미한다.

Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN);
ChronoLocalDate now = japaneseChronology.dateNow();


그러나 날짜와 시간 API 설계자는 ChronoLocalDate 보다는 LocalDate 를 사용하라고 권고한다.

예를 들어 개발자는 1년이 12개월로 이루어져 있으며 1달은 31일 이하이거나, 최소한 1년은 정해진 수의 달로 이루어졌을 것이라고 가정할 수 있다.

하지만 이와 같은 가정은 특히 멀티캘린더 시스템에서는 적용되지 않는다.

따라서 프로그램의 입출력을 지역화하는 상황을 제외하고는 모든 데이터 저장, 조작, 비즈니스 규칙 해석 등의 작업에서 LocalDate 를 사용해야 한다.



-

이슬람력


자바 8 에 추가된 새로운 캘린더 중 HijrahDate(이슬람력) 가 가장 복잡한데 이슬람력에는 변형(variant)이 있기 때문이다.

Hijrah 캘린더 시스템은 태음월(lunar month)에 기초한다.

새로운 달(month)을 결정할 때 새로운 달(moon)을 세계 어디에서나 볼 수 있는지 아니면 사우디아라비아에서 처음으로 새로운 달을 볼 수 있는지 등의 변형 방법을 결정하는 메서드가 있다.

withVariant 메서드로 원하는 변형 방법을 선택할 수 있다.


자세한 것은 생략한다....






12.4. 요약


-

자바 8 이전 버전에서 제공하는 기존의 java.util.Date 클래스와 관련 클래스에서는 여러 불일치점들과 가변성, 어설픈 오프셋, 기본값, 잘못된 이름 결정 등의 설계 결함이 존재했다.



-

새로운 날짜와 시간 API 에서 날짜와 시간 객체는 모두 불변이다.



-

새로운 API 는 각각 사람과 기계가 편리하게 날짜와 시간 정보를 관리할 수 있도록 두 가지 표현 방식을 제공한다.



-

날짜와 시간 객체를 절대적인 방법과 상대적인 방법으로 처리할 수 있으며 기존 인스턴스를 변환하지 않도록 처리 결과로 새로운 인스턴스가 생성된다.



-

TemporalAdjuster 를 이용하면 단순히 값을 바꾸는 것 이상의 복잡한 동작을 수행할 수 있으며, 자신만의 커스텀 날짜 변환 기능을 정의할 수 있다.



-

날짜와 시간 객체를 특정 포맷으로 출력하고 파싱하는 포매터를 정의할 수 있다.

패턴을 이용하거나 프로그램으로 포매터를 만들 수 있으며 포매터는 스레드 안정성을 보장한다.



-

특정 지역/장소에 상대적인 시간대 또는 UTC/GMT 기준의 오프셋을 이용해서 시간대를 정의할 수 있으며 이 시간대를 날짜와 시간 객체에 적용해서 지역화할 수 있다.



-

ISO-8601 표준 시스템을 준수하지 않는 캘린더 시스템도 사용할 수 있다.




댓글0