[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 표준 시스템을 준수하지 않는 캘린더 시스템도 사용할 수 있다.
'프로그래밍 놀이터 > 안드로이드, Java' 카테고리의 다른 글
[Java8 In Action] #14 함수형 프로그래밍 기법 (0) | 2019.01.03 |
---|---|
[Java8 In Action] #13 함수형 관점으로 생각하기 (0) | 2019.01.02 |
[Java8 In Action] #11 CompletableFuture: 조합할 수 있는 비동기 프로그래밍 (0) | 2018.12.31 |
[Java8 In Action] #10 null 대신 Optional (0) | 2018.12.30 |
[Java8 In Action] #9 디폴트 메서드 (0) | 2018.12.29 |
댓글