프로그래밍 놀이터/안드로이드, Java

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

돼지왕 왕돼지 2019. 1. 1. 12:47
반응형

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


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

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

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



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 표준 시스템을 준수하지 않는 캘린더 시스템도 사용할 수 있다.




반응형