생각해보기
모던 자바 인 액션 -10- 본문
새로운 날짜와 시간 API
이전의 Date 와 Calendar 문제점
- DataFormat 같은 일부 기능은 Date 클래스에만 작동했다.
- DateFormat은 스레드에 안전하지 않다. 즉 , 두 스레드가 동시에 하나의 포매터로 날짜를 파싱할 때 예기친 못한 결과가 일어날 수 있다. (DateFormat : 언어의 종류와 독립적으로 날짜와 시간의 형식을 조절하고 파싱할때 사용)
- Data와 Calendar 모두 가변 클래스이다. 가변 클래스라는 설계 때문에 유지보수가 어려워 졌다.
따라서 자바8에서는 java.time 에 새로운 날짜와 시간 API를 제공하였다.
LocalDate와 LocalTime
LocalDate 인스턴스는 시간을 제외한 날짜를 표현하는 불변 객체이다. LocalDate 객체는 어떤 시간대 정보도 포함하지 않는다
LocalDate date = LocalDate.of(2019,9,12); // Output: 2019-09-12
int year = date.getYear(); // Output : 2019
Month month = date.getMonth(); // Output : SEPTEMBER // 9월
int day = date.getDayOfMonth(); // Output : 12
DayOfWeek dow = date.getDayOfWeek(); // Output : THURSDAY // 해당 요일
int len = date.lengthOfMonth(); // Output : 30 // 9월의 일 수
boolean leap = date.isLeapYear(); // Output : false // 윤년이 아님
// 팩토리 매서드 now()는 시스템 시계의 정보를 이용해서 현재 날짜 정보를 얻는다.
LocalDate currentDate = LocalDate.now();
get 메서드에 TemporalField를 전달해서 정보를 얻는 방법도 있다. TemporalField는 시간 관련 객체에서 어떤 필드의 값에 접근할지 정의하는 인터페이스다. 열거자 ChronField는 TemporalField 인터페이스를 정의하므로 ChronoField의 열거자 요소를 이용해서 원하는 정보를 쉽게 얻을 수 있다
int year_c = date.get(ChronoField.YEAR);
int month_c = date.get(ChronoField.MONTH_OF_YEAR);
int day_c = date.get(ChronoField.DAY_OF_MONTH);
시간은 LocalTime 클래스로 표현할 수 있다. 시간과 분을 인수로 받는 of 와 시간, 분, 초를 인수로 받는 of가 있다. LocalDate 처럼 LocalTime은 getter 메서드 제공한다
LocalTime hourAndMinute = LocalTime.of(13, 4); // output :13:04
LocalTime hourAndMinuteAndSecond = LocalTime.of(13, 4,5); // output : 13:04:05
// getter 메서드 제공
int hour = hourAndMinuteAndSecond.getHour(); // 13
int minute = hourAndMinuteAndSecond.getMinute(); // 4
int second = hourAndMinuteAndSecond.getSecond(); // 5
DateTimeFormatter를 전달할 수도 있다. 문자열을 LocalDate나 LocalTime으로 파싱할 수 없을 때 DateTimeParseException을 일으킨다.
LocalDate data_p = LocalDate.parse("2017-05-03");
LocalTime time_p = LocalTime.parse("10:33:44");
LocalDateTime
LocalDateTime은 LocalDate와 LocalTime을 쌍으로 갖는 복합 클래스다. 즉 날짜와 시간을 모두 표현할 수 있다.
// of 방법
LocalDateTime dateTime = LocalDateTime.of(2020, Month.DECEMBER, 22, 13, 45, 20);
LocalDateTime dateTime_p = LocalDateTime.of(date_p, time_p);
// LocalDate에 atTime 메서드에 시간을 제공방법
LocalDateTime dateTime_t = date_p.atTime(13, 45, 20);
// LocalTime에 atDate메서드에 날짜를 제공방법
LocalDateTime localDateTime_d = time_p.atDate(date_p);
// toLocalDate, toLocalTime 메서드로 LocalDate, LocalTime 인스턴스 추출 가능하다.
LocalDate date_to = dateTime.toLocalDate();
LocalTime time_to = dateTime.toLocalTime();
Instant 클래스
새로운 java.time.Instant 클래스에서는 기계적인 관점에서 시간을 표현한다. Instant는 Unix time(Unix epoch time)을 기준으로 특정 지점까지의 시간을 초로 표현한다. ofEpochSecond 함수에서 두번째 인수를 이용해서 나노초 단위로 시간을 보정할 수 있다. Intstant는 기계 전용의 유틸리티이며 사람이 읽을 수 있는 시간 정보를 제공하지 않는다.
* Unix time은 시스템에서 날짜와 시간의 흐름을 나타낼 때 기준을 삼는 시간을 의미한다. (1970년 1월 1일 0시 0분 0초 UTC)
Instant.ofEpochSecond(3);
Instant.ofEpochSecond(3, 0);
Instant.ofEpochSecond(2, 1_000_000_000); // 2초 이후의 1억 나노초
Instant.ofEpochSecond(4, -1_000_000_000); // 4초 이전의 1억 나노초
날짜 시간 공통 메서드
메서드 | 설명 |
from | 주어진 Temporal 객체를 이용해서 클래스의 인스턴스를 생성함 |
now | 시스템 시계로 Temporal 객체를 생성함 |
of | 주어진 구성 요소에서 Temporal 객체의 인스턴스를 생성함 |
parse | 문자열을 파싱해 Temporal 객체를 생성함 |
atOffset | 시간대 오프셋과 Temporal 객체를 합침 |
atZone | 시간대 오프셋과 Temporal 객체를 합침 |
format | 지정된 포매터를 이용해서 Temporal 객체를 문자열로 변환함(Instant 지원 안함) |
get | Temporal 객체의 상태를 읽음 |
minus | 특정 시간을 뺀 Temporal 객체의 복사본을 생성함 |
plus | 특정 시간을 더한 Temporal 객체의 복사본을 생성함 |
with | 일부 상태를 바꾼 Temporal 객체의 복사본을 생성함 |
Duration과 Period
Duration 클래스의 정적 팩토리 메서드 between으로 객체 사이의 지속시간을 만들 수 있다. (LocalDate 전달 불가)
Period 클래스의 팩토리 메서드 between을 이용하면 두 LocalDate의 차이를 확인 할 수 있다.
Duration과 Period 클래스의 공통 매서드
매서드 | 설명 |
between | 두 시간 사이의 간격을 생성함 |
from | 시간/요일 단위로 간격을 생성함 |
of | 주어진 구성 요소에서 간격 인스턴스를 생성함 |
parse | 문자열을 파싱해서 간격 인스턴스를 생성함 |
addTo | 현재값의 복사본을 생성한 다음에 지정된 Temporal 객체에 간격을 추가함 |
get | 현재 간격 정보값을 읽음 |
isNegative | 간격이 음수인지 확인함 |
isZero | 간격이 0인지 확인함 |
minus | 현재값에서 주어진 값을 뺀 복사본을 생성함 |
multipliedBy | 현재값에서 주어진 값을 곱한 복사본을 생성함 |
negated | 주어진 값의 부호를 반전한 복사본을 생성함 |
plus | 현재값에 주어진 시간을 더한 복사본을 생성함 |
subtractFrom | 지정된 Temporal 객체에서 간격을 뺌 |
TemporalAdjustres
복잡한 날짜 조정 기능을 제공한다. TemporalAdjustres 통해 날짜와 시간을 다양한 상황에서 사용할 수 있다.
매서드 | 설명 |
dayOfWeekInMonth | 서수 요일에 해당하는 날짜를 반환하는 TemporalAdjuster 반환함 (음수 사용시 끝부터 계산) |
firstDayOfMonth | 현재 달의 첫 번째 날짜를 반환하는 TemporalAdjuster 반환함 |
firstDayOfNextMonth | 다음 달의 첫 번째 날짜를 반환하는 TemporalAdjuster 반환함 |
firstDayOfNextYear | 내년의 첫 번째 날짜를 반환하는 TemporalAdjuster 반환함 |
firstDayOfYear | 올해의 첫 번째 날짜를 반환하는 TemporalAdjuster 반환함 |
firstInMonth | 현재 달의 첫 번째 요일에 해당하는 날짜를 반환 |
lastDayOfMonth | 현재 달의 마지막 날짜를 반환하는 TemporalAdjuster 반환함 |
lastDayOfNextMonth | 다음 달의 마지막 날짜를 반환하는 TemporalAdjuster 반환함 |
lastDayOfNextYear | 내년의 마지막 날짜를 반환하는 TemporalAdjuster 반환함 |
lastDayOfYear | 올해의 마지막 날짜를 반환하는 TemporalAdjuster 반환함 |
lastInMonth | 현재 달의 마지막 요일에 해당하는 날짜를 반환 |
... | ... 더 많은 기능은 TemporalAdjustres 참조할 것 |
java.time.format.DateTimeFormatter
포매팅과 파싱 전용 패키지인 java.time.format 이 추가되었으며, 모든 DateTimeFormmater는 스레드에서 안전하게 사용할 수 있다.
이 패키지에서 가장 중요한 클래스는 DateTimeFormatter이다. 정적 팩토리 메서드와 상수를 이용해서 손쉽게 formatter을 만들 수 있다. DateTimeFormatter 클래스는 BASIC_ISO_DATE(YYYYMMDD)와 ISO_LOCAL_DATE(YYYY-MM-DD) 등의 상수를 미리 정의하고 있다. 우리는 이 클래스를 이용해서 날짜와 시간을 특정 형식의 문자열로 만들 수 있다.
LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateTimeFormatter.BASIC_ISO_BASIC); // 20140318
String s2 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2014-03-18
반대로 날짜나 시간을 표현하는 문자열을 파싱해서 날짜 객체를 다시 만들 수 있다. 날짜와 시간 API에서 특정 시점이나 간격을 표현하는 모든 클래스의 팩토리 메서드 parse를 이용해서 문자열을 날짜 객체로 만들 수 있다.
LocalDate date1 = LocalDate.parse("20140318", DateTimeFormmater.BASIC_ISO_DATE);
LocalDate date2 = LocalDate.parse("2014-03-18", DateTimeFormatter.ISO_LOCAL_DATE);
DateTimeFormmater 클래스는 특정 패턴으로 포매터를 만들 수 있는 정적 팩토리 메서드도 제공한다. LocalDate의 format 메서드는 요청 형식의 패턴에 해당하는 문자열을 생성한다. 그리고 정적 메서드 parse는 같은 formatter을 적용해서 생성된 문자열을 파싱하여 다시 문자열을 생성한다.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formatterDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formatterDate, formatter);
우린 ofPattern 메서드를 사용하여 Locale로 포매터를 만들 수 있다. 지역화된 시간을 표시할 수 있다는 말과 같다. DateTimeFormatter 클래스로 대소문자를 구분하는 파싱, 패딩 등을 활용할 수 있다.
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locald.ITALIAN);
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formatterDate = date1.format(italianFormatter);
LocalDate date2 = LocalDate.parse(formatterDate, formatter);
DataTimeFormatterBuilder 클래스로 복합적인 포매터를 정의해서 좀 더 세부적으로 포메터를 제어할 수 있다. DateTimeFormatterBuilder 클래스로 대소문자를 구분하는 파싱, 패딩 등을 활용할 수 있다.
DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.ITALIAN);
java.time.ZoneId 클래스의 시간대 표현
ZoneRules 클래스에는 약 40개 정도의 시간대가 있는데 우리는 ZoneId의 getRules()를 이용해서 해당 시간대의 규정을 획득할 수 있다. 우리는 지역 ID로 특정 ZoneId를 구분한다.
// 지역 ID는 지역/도시 형식으로 이루어지며 IANA Time Zone Database에서 제공하는 지역 집합 정보를 사용한다
ZoneId romeZone = ZoneId.of("Europe/Rome");
ZoneId 객체를 얻은 다음 LocalDate, LocalDateTime, Instant를 이용해서 ZonedDateTime 인스턴스로 변환할 수 있다. ZonedDateTime은 지정한 시간대에 상대적인 시점을 표현한다.
// TimeZone => ZoneId
ZoneId zoneId = TimeZone.getDefault().toZoneId();
// ZoneId를 얻어 ZonedDataTime 인스턴스로 변환가능
LocalDate date = LocalDate.of(2014, Month.MARCH, 18);
ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
ZonedDateTime zdt2 = dateTime.atZone(romeZone);
아래 코드처럼 ZoneId를 이용해서 Instant를 LocalDateTime을 바꾸는 방법도 있다.
Instant instant = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);
* ZonedDataTime 개념
'자바' 카테고리의 다른 글
모던 자바 인 액션 -12- (0) | 2021.12.20 |
---|---|
모던 자바 인 액션 -11- (0) | 2021.12.20 |
모던 자바 인 액션 -9- (0) | 2021.12.19 |
모던 자바 인 액션 -8- (0) | 2021.12.17 |
모던 자바 인 액션 -7- (0) | 2021.12.17 |