java.util.Date 클래스 문제점
- 특정 시점을 날짜가 아닌 밀리초 단위로 표현한다
- 1900년을 기준으로 하는 오프셋, 0에서 시작하는 달 인덱스등 모호한 설계
- toString 으로 반환되는 문자열을 활용하기 어렵다.
- JVM 기본시간대 CET 중앙유럽시간대를 사용하므로 자체적 시간대 정보가 없음.
- 가변클래스
java.util.Calendar 클래스 문제점
- 달의 인덱스는 0에서 시작
- DateFormat 기능이 없음.
- 가변 클래스.
- DateFormat은 스레드에 안전하지 않으므로 두 스레드가 동시 하나의 포매터로 날짜 파싱할때 결과가 예기치 못함.
12.1. LocalDate, LocalTime, Instant, Duration, Period 클래스
1 - LocalDate와 LocalTime 사용
LocalDate 인스턴스는 시간을 제외한 날짜를 표현하는 불변 객체이고 어떤 시간대 정보도 포함하지 않는다.
정적 팩토리 메서드 of 로 LocalDate 인스턴스 생성한다.
LocalDate date = LocalDate.of(2020,6,11); // 2020-06-11
int year = date.getYear(); // 2020
Month month = date.getMonth(); // JUNE
int day = date.getDayOfMonth(); // 11
DayOfWeek dow = date.getDayOfWeek(); // THURSDAY
int len = date.lengthOfMonth(); // 30
boolean leap = date.isLeapYear(); // false
현재날짜정보
LocalDate today = LocalDate.now();
get 메서드에 TemporalField를 전달해서 정보를 얻는 방법
-> TemporalField는 시간관련 객체에서 어떤 필드의 값에 접근할지 정의하는 인터페이스.
ChronoField의 열거자 요소를 이용해 정보를 얻음.
int year = data.get(ChronoField.YEAR);
int month = data.get(ChronoField.MONTH_OF_YEAR);
int day = data.get(ChronoField.DAY_OF_MONTH);
// 내장 메서드 이용
int year = date.getYear();
int month = date.getMonthValue();
int day = date.getDayOfMonth();
// LocalTime 만들고 값 읽기
LocalTime time = Localtime.of(13,45,20);
int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();
//날짜와 시간 문자열로 날짜, 시간 인스턴스 만들기
LocalDate date = LocalDate.parse("2017-09-21");
LocalTime time = LocalTime.parse(13:45:20);
2 - 날짜와 시간 조합
LocalDateTime은 LocalDate와 LocalTime을 쌍으로 갖는 복합 클래스이다.
// LocalDateTime을 직접만들거나 조합하는 방법
LocalDateTime dt1 = LocalDateTime.of(2017, Month.SEPTEMBER, 21,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);
// 추출
LocalDateTime date1 = dt1.toLocalDate();
LocalDateTime time1 = dt1.toLocalTime();
3 - Instant 클래스 : 기계의 날짜와 시간
java.time.Instant 클래스에서는 기계적인 관점에서 시간을 표현한다.
Unix Epoch Time을 기준으로 시간을 초로 표현. 나노초(10억분의 1초)의 정밀도를 제공.
Instant.ofEpochSecond(2, 1_000_000_000); // 2초 이후의 1억 나노초
Instant.ofEpochSecond(4, -1_000_000_000); // 2초 이전의 1억 나노초
// 사람이 읽을수 있는 시간정보를 제공하지 않으므로
// UnsupportedTemporalTypeException 예외 발생가능.
4 - Duration과 Period 정의
Temporal 인터페이스는 특정시간을 모델링하는 객체의 값을 어떻게 읽고 조작할지 정의한다.
Duration 클래스의 정적팩토리 메서드 between으로 두 시간객체의 지속시간을 만들 수 있다.
Duration d1 = Duration.between(time1, time2);
Duration d1 = Duration.between(dateTime1, dateTime2);
Duration d2 = Duration.between(instant1, instant2);
// instant객체와 dateTime 객체는 혼용 불가능.
// Period 클래스의 팩토리 메서드 between을 이용해 두 LocalDate의 차이 확인.
Period tenDays = Period.between(LocalDate.of(2017,9,11), LocalDate(2017,9,21));
// Duration과 Period 클래스가 제공하는 다양한 팩토리 메서드.
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 날짜 조정, 파싱, 포매팅
바뀐속성을 포함하는 새로운 객체를 반환하는 메서드.
-> 객체를 바꾸는 것이 아니라 필드를 갱신한 복사본을 만든다. 함수형 갱신.
LocalDate date1 = LocalDate.of(2017,9,21); // 2017-09-21
LocalDate date2 = date1.withYear(2011); // 2011-09-21
LocalDate date3 = date2.withDayOfMonth(25); // 2011-09-25
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR,2); // 2011-02-25
LocalDate date2_1 = date1.plusWeeks(1); // 2017-09-28
LocalDate date3_1 = date2_1.minusYears(6); // 2011-09-28
LocalDate date4_1 = date3_1.plus(6, ChronoUnit.MONTHS); // 2012-03-28
plus, minus 메서드도 Temporal 인터페이스에 정의되어 있다.
ChronoUnit 열거형은 TemporalUnit 인터페이스를 쉽게 활용할 수 있는 구현을 제공한다.
LocalDate, LocalTime, LocalDateTime, Instant 등 날짜와 시간을 표현하는 클래스의 공통메서드
메서드 | 정적 | 설명 |
from | o | 주어진 Temporal 객체를 이용해 클래스의 인스턴스 생성 |
now | o | 시스템 시계로 Temporal 객체를 생성 |
of | o | 주어진 구성 요소에서 Temporal 객체의 인스턴스 생성 |
parse | o | 문자열을 파싱해서 Temporal 객체를 생성 |
atOffset | x | 시간대 오프셋과 Temporal 객체를 합침 |
atZone | x | 시간대 오프셋과 Temporal 객체를 합침 |
format | x | 지정된 포매터를 이용해 Temporal 객체를 문자열로 변환(Instant 지원 x) |
get | x | Temporal 객체의 상태를 읽음 |
minus | x | 특정시간을 뺀 Temporal 객체의 복사본 생성 |
plus | x | 특정시간을 더한 Temporal 객체의 복사본 생성 |
with | x | 일부 상태를 바꾼 Temporal 객체의 복사본 생성 |
1 - TemporalAdjusters 사용하기
다음주 돌아오는 일요일, 어떤 달의 마지말 날을 구하려면??
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());
TemporalAdjuster 커스텀 구현을 간단하게 만들수도 있다.
-> 하나의 메서드만 정의하므로 함수형 인터페이스 이다.
TemporalAdjuster 인터페이스를 UnaryOperator<Temporal>과 같은 형식으로 간주.
2 - 날짜와 시간 객체 출력과 파싱
포매팅과 파싱 전용 패키지 java.time.format
DateTimeFormatter의 정적팩토리 메서드 이용해 쉽게 포매팅 할수 있고
parse메서드를 이용해 문자열을 날짜 객체로 만들 수 있다.
LocalDate date = LocalDate.of(2020,6,28);
String d1 = date.format(DateTimeFormatter.BASIC_ISO_DATE); // 20200628
String d2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2020-06-28
LocalDate date1 = LocalDate.parse("20200628", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("20200628", DateTimeFormatter.ISO_LOCAL_DATE);
특정 패턴으로 포매터를 만들어 쓸수도 있다.
Locale로 지역화된 포매터를 만들 수 있게 오버로드 메서드도 제공한다.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2020,6,28);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate date3 = LocalDate.of(2020,3,18);
String formattedDate1 = date3.format(italianFormatter); // 28. marzo 2020
LocalDate date4 = LocalDate.parse(formattedDate1, italianFormatter);
DateTimeFormatterBuilder클래스로 복합적인 포매터를 정의해 제어 가능하다
프로그램 적으로 포매터를 만들수 있다.
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral("")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
12.3 다양한 시간대와 캘린더 활용방법
기본의 java.util.TimeZone 을 대체할 수 있는 java.time.ZoneId 클래스.
서머타임(DST) 같은 사항이 자동으로 처리.
1 - 시간대 사용하기
표준시간이 같은 지역을 묶어 time zone 규칙 집합 정의.
https://www.iana.org/time-zones 참고
getRules()이용해 규정을 획득한다.
ZoneId romeZone = ZoneId.of("Europe/Rome"); // {지역}/{도시}
LocalDate date = LocalDate.of(2020, Month.JUNE, 28);
ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
LocalDateTime datetime = LocalDateTime.of(2020, Month.JUNE, 28, 13, 45);
ZoneDateTime zdt2 = dateTime.atZone(romeZone);
Instant instant = Instant.now();
ZoneDateTime zdt3 = instant.atZone(romeZone);
//ZoneId를 이용해 LocalDateTime을 Instant 로
Instant instant1 = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
ZonedDateTime 개념
LocalDate | LocalTime | ZoneId |
LocateDateTime | ||
ZonedDateTime |
2 - UTC/Greenwich 기준의 고정 오프셋
UTC 협정 세계시 / GMT 그리니치 표준시 를 기준으로 시간대를 표현하기도 한다.
ZoneId의 서브클래스인 ZoneOffset 클래스로 기간값 차이 표현 가능하다.
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
LocalDateTime date = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(date, newYorkOffset);
3 - 대안 캘린더 시스템 사용하기
자바8에서 추가된 4개의 캘린터 시스템
ThaiBuddhistDate, MinguoDate, JapaneseDate, HijrahDate
프로그램 입출력을 지역화하는 상황을 제외하고는
모든 데이터 저장, 조작, 비지니스 규칙해석 등의 작업에서 LocalDate를 사용해야 한다.
- 자바 8이전에 util.Date 클래스 관련해서 불일치점 가변성, 어설픈 오프셋, 기본값, 잘못된 이름 등 설계결함 존재.
- 새로운 날짜 시간 API 에서 날짜와 시간 객체는 불변.
- 새로운 API는 사람 (LocalDateTime) 기계(Instant) 가 사용하도록 두가지 표현방식을 제공함.
- 날짜와 시간객체를 절대적인 방법과 상대적인 방법으로 처리할수 있고,
기존 인스턴스를 변환하지 않게 처리 결과로 새로운 인스턴스가 생성. - TemporalAdjuster를 이용해 복잡한 동작을 수행, 자신만의 커스텀 날짜 변환 기능을 정의 가능.
- 날짜와 시간 객체를 특정 포맷으로 출력하고 파싱하는 포매터 정의 가능.
패턴을 이용해 프로그램식으로 포매터 만들 수 있음. - 지역 상대적인 시간대 또는 UTC/GMT 기분의 오프셋을 이용해 시간대를 정의 가능하고,
이 시간대를 날짜와 시간객체에 적용해 지역화 가능. - ISO-8601 표준 시스템이 아닌 다양한 캘린더 시스템도 지원함
'IT Book Summary > ModernJavaInAction' 카테고리의 다른 글
Chapter 14 자바 모듈 시스템 (0) | 2020.07.12 |
---|---|
Chapter 13 디폴트 메서드 (0) | 2020.07.04 |
Chapter 11 null 대신 Optional 클래스 (0) | 2020.05.31 |
10장 람다를 이용한 도메인 전용언어 (0) | 2020.04.18 |
Chapter 9 리팩터링, 테스팅, 디버깅 (0) | 2020.04.05 |