Java8 In Action - 4. 새로운 날짜와 시간 API


Java8 In Action 을 읽고 정리한 내용이다. 자바8에서 날짜와 시간 문제를 개선하는 새로운 날짜와 시간 API를 제공하는 새로운 기능에 대해서 설명한다.

Localdate, LocalTime, Instant, Duration, Period

java.time 패키지는 Localdate, LocalTime, LocalDateTime, Instant, Duration, Period 등 새로운 클래스를 제공한다.

LocaDate와 LocalTime 사용

LocalDate 인스턴스는 시간을 제외한 날짜를 표현하는 불변 객체다. 정적 팩토리 메서드는 ofLocalDate 인스턴스를 만들 수 있다.

DateTimeExamples.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class DateTimeExamples {
public static void main(String[] args) {
useLocalDate();
}

private static void useLocalDate() {
LocalDate today = LocalDate.now(); // 시스템의 현재 날짜 정보
System.out.println("today : " + today);

LocalDate date = LocalDate.of(2019, 06, 21);
System.out.println("date : " + date);

int year = date.getYear();
System.out.println("year : " + year);

Month month = date.getMonth();
System.out.println("month : " + month);

int day = date.getDayOfMonth();
System.out.println("day : " + day);

DayOfWeek dayOfWeek = date.getDayOfWeek();
System.out.println("dayOfWeek : " + dayOfWeek);

int len = date.lengthOfMonth(); // 6월의 일수
System.out.println("len : " + len);

boolean leap = date.isLeapYear(); // 윤년이 아님
System.out.println("leap : " + leap);
}
}

위에 코드에서 보여주는 것처럼 LocalDate 인스턴스는 년도, 달, 요일 등을 반환하는 메서드를 제공한다.

결과 화면

1
2
3
4
5
6
7
8
today : 2019-06-23
date : 2019-06-21
year : 2019
month : JUNE
day : 21
dayOfWeek : FRIDAY
len : 30
leap : false

get 메서드에 TemporalField를 이용해서 LocalDate값 읽기

DateTimeExamples.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DateTimeExamples {
public static void main(String[] args) {
useTempLocalDate();
}

private static void useTempLocalDate() {
LocalDate date = LocalDate.of(2019, 06, 21);
System.out.println("date : " + date);

int year = date.get(ChronoField.YEAR);
System.out.println("year : " + year);

int month = date.get(ChronoField.MONTH_OF_YEAR);
System.out.println("month : " + month);

int day = date.get(ChronoField.DAY_OF_MONTH);
System.out.println("year : " + year);
}
}

결과 화면

1
2
3
4
date : 2019-06-21
year : 2019
month : 6
year : 2019

LocalTime 만들고 값 읽기

DateTimeExamples.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class DateTimeExamples {
public static void main(String[] args) {
useLocalTime();
}

private static void useLocalTime() {
LocalTime time = LocalTime.of(13, 45, 20);
System.out.println("time : " + time);

int hour = time.getHour();
System.out.println("hour : " + hour);

int minute = time.getMinute();
System.out.println("minute : " + minute);

int second = time.getSecond();
System.out.println("second : " + second);
}
}

결과 화면

1
2
3
4
time : 13:45:20
hour : 13
minute : 45
second : 20

날짜와 시간 문자열로 LocalDate와 LocalTime의 인스턴스를 만드는 방법

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DateTimeExamples {
public static void main(String[] args) {
useLocalParse();
}

private static void useLocalParse() {
LocalDate date = LocalDate.parse("2019-06-20");
System.out.println("date : " + date);

LocalTime time = LocalTime.parse("13:45:20");
System.out.println("time : " + time);
}
}

결과 화면

1
2
date : 2019-06-20
time : 13:45:20

날짜와 시간 조합

LocalDateTimeLocalDateLocalTime을 쌍으로 갖는 복합 클래스다.

LocalDatetimeExamples.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LocalDatetimeExamples {
public static void main(String[] args) {
useLocalDateTime();
}

private static void useLocalDateTime() {
LocalDateTime dt1 = LocalDateTime.of(2019, Month.MARCH, 23, 20, 48, 11);
System.out.println("dt1 : " + dt1);
System.out.println("dt1.toLocalDate : " + dt1.toLocalDate());
System.out.println("dt1.toLocalTime : " + dt1.toLocalTime());

LocalDate date = LocalDate.of(2019, 06, 21);
LocalTime time = LocalTime.of(13, 45, 20);
LocalDateTime dt2 = LocalDateTime.of(date, time);
System.out.println("dt2 : " + dt2);

LocalDateTime dt3 = date.atTime(10, 30, 25);
System.out.println("dt3 : " + dt3);

LocalDateTime dt4 = date.atTime(time);
System.out.println("dt4 : " + dt4);

LocalDateTime dt5 = time.atDate(date);
System.out.println("dt5 : " + dt5);
}
}

결과 화면

1
2
3
4
5
6
7
dt1 : 2019-03-23T20:48:11
dt1.toLocalDate : 2019-03-23
dt1.toLocalTime : 20:48:11
dt2 : 2019-06-21T13:45:20
dt3 : 2019-06-21T10:30:25
dt4 : 2019-06-21T13:45:20
dt5 : 2019-06-21T13:45:20

Duration와 Period 정의

Duration 클래스의 정적 팩토리 메서드 between으로 두 시간 객체 사이의 지속시간을 만들 수 있다. Period 클래스 팩토리 메서드 between을 이용하면 두 LocalDate의 차이를 확인할 수 있다.

DurationAndPeriodExamples.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class DurationAndPeriodExamples 
public static void main(String[] args) {
useDurationAndPeroid();
}

private static void useDurationAndPeroid() {
LocalTime time = LocalTime.of(13, 45, 20);

Instant instant = Instant.ofEpochSecond(44 * 365 * 86400);
Instant now = Instant.now();

Duration d1 = Duration.between(LocalTime.of(13, 45, 10), time);
Duration d2 = Duration.between(instant, now);
System.out.println("d1.getSeconds : " + d1.getSeconds());
System.out.println("d2.getSeconds : " + d2.getSeconds());

Duration threeMinutes = Duration.ofMinutes(3);
System.out.println("threeMinutes : " + threeMinutes);

Duration threeMinutes2 = Duration.of(3, ChronoUnit.MINUTES);
System.out.println("threeMinutes2 : " + threeMinutes2);

Period tendays = Period.ofDays(10);
System.out.println("tendays : " + tendays);

Period threeWeeks = Period.ofWeeks(3);
System.out.println("threeWeeks : " + threeWeeks);

Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
System.out.println("twoYearsSixMonthsOneDay : " + twoYearsSixMonthsOneDay);
}
}

결과 화면

1
2
3
4
5
6
7
d1.getSeconds : 10
d2.getSeconds : 173709219
threeMinutes : PT3M
threeMinutes2 : PT3M
tendays : P10D
threeWeeks : P21D
twoYearsSixMonthsOneDay : P2Y6M1D

간격을 표현하는 날짜와 시간 클래스의 공통 메서드

메서드 정적 설명
between 두시간 사이의 간격을 생성함.
from 시간 단위로 간격을 생성함
of 주어진 구성 요소에서 간격 인스턴스를 생성함.
parse 문자열을 파싱해서 간격 인스턴스를 생성함.
addTo 아니요 현재값의 복사본을 생성한 다음에 지정된 Temporal 객체에 추가함
get 아니요 현재 간격 정보값을 읽음.
isNegative 아니요 간격이 음수인지 확인함
isZero 아니요 간격이 0인지 확인함.
minus 아니요 현재값에서 주어진 시간을 뺀 복사본을 생성함.
multipliedBy 아니요 현재값에 주어진 값을 곱한 복사본을 생성함.
negated 아니요 주어진 값의 부호를 반전한 복사본을 생성함.
plus 아니요 현재값에 주어진 시간을 더한 복사본을 생성함.
subtractFrom 아니요 지정된 Temporal 객체에서 간격을 뺌.

날짜 조정, 파싱, 포메팅

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

절대적인 방식으로 LocalDate의 속성 바꾸기

WithAttributeExamples.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class WithAttributeExamples {
public static void main(String[] args) {
userAbsoluteWithAttribute();
}

private static void userAbsoluteWithAttribute() {
LocalDate date1 = LocalDate.of(2019, 06, 23);
System.out.println("date1 : " + date1);

LocalDate date2 = date1.withYear(2018);
System.out.println("date2 : " + date2);

LocalDate date3 = date2.withDayOfMonth(30);
System.out.println("date3 : " + date3);

LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 7);
System.out.println("date4 : " + date4);
}
}

결과 화면

1
2
3
4
date1 : 2019-06-23
date2 : 2018-06-23
date3 : 2018-06-30
date4 : 2018-07-30

상대적인 방식으로 LocalDate의 속성 바꾸기

WithAttributeExamples.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class WithAttributeExamples {
public static void main(String[] args) {
userRelativeWithAttribute();
}

private static void userRelativeWithAttribute() {
LocalDate date1 = LocalDate.of(2019, 06, 23);
System.out.println("date1 : " + date1);

LocalDate date2 = date1.plusWeeks(1);
System.out.println("date2 : " + date2);

LocalDate date3 = date2.minusWeeks(3);
System.out.println("date3 : " + date3);

LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS);
System.out.println("date4 : " + date4);
}
}

결과 화면

1
2
3
4
date1 : 2019-06-23
date2 : 2019-06-30
date3 : 2019-06-09
date4 : 2019-12-09

특정 시점을 표현하는 날짜 시간 클래스의 공통 메서드

메서드 정적 설명
from 주어진 Temporal 객체를 이용해서 클래스의 인스턴스를 생성함.
now 시스템 시계로 Temporal 객체를 생성함.
of 주어진 구성 요소에서 Temporal 객체의 인스턴스를 생성함.
parse 문자열을 파싱해서 Temporal 객체를 생성함.
atOffset 아니요 시간대 오프셋과 Temporal 객체를 합침.
atZone 아니요 시간대와 Temporal 객체를 합침.
foramt 아니요 지정도니 포매터를 이용해서 temporal 객체를 문자열로 변환함(Instant는 지원하지 않음.)
get 아니요 Temporal 객체의 상태를 읽음.
minus 아니요 특정 시간을 뺀 Temporal 객체의 복사본을 생성함.
plus 아니요 특정 시간을 더한 Temporal 객체의 복사본을 생성함.
with 아니요 일부 상태를 바꾼 Temporal 객체의 복사본을 생성함.

TemporalAdjusters 사용하기

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

AdjusterExamples.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AdjusterExamples {
public static void main(String[] args) {
useTemporalAdjuster();
}

private static void useTemporalAdjuster() {
LocalDate date1 = LocalDate.of(2019, 06, 21);
System.out.println("date1 : " + date1);

LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY));
System.out.println("date2 : " + date2);

LocalDate date3 = date2.with(lastDayOfMonth());
System.out.println("date3 : " + date3);
}
}

결과 화면

1
2
3
date1 : 2019-06-21
date2 : 2019-06-23
date3 : 2019-06-30

TemporalAdjusters 클래스의 팩토리 메서드(API 레퍼런스 참조)

메서드 설명
dayOfWeekInMonth ‘3월의 둘째 화요일’처럼 서수 요일에 해당하는 날짜를 반환하는 TemporalAdjuster를 반환함.
firstDayofMonth 현재 달의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환함.
firstDayOfNextMonth 다음 달의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환함.
firstDayOfNextYear 내년의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환함.
firstDayOfYear 올해의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환함.
firstInMonth ‘3월의 첫 번째 화요일’처럼 현재 달의 첫 번째 요일에 해당하는 날짜를 반환하는 TemporalAdjuster를 반환함.
lastDayOfMonth 현재 달의 마지막 날짜를 반환하는 TemporalAdjuster를 반환함.
lastDayOfNexMonth 다음 달의 마지막 날짜를 반환하는 TemporalAdjuster를 반환함.
lastDayOfYear 올해의 마지막 날짜를 반환하는 TemporalAdjuster를 반환함.
lastInMonth ‘3월의 마지막 화요일’처럼 현재 달의 마지막 요일에 해당하는 날짜를 반환하는 TemporalAdjuster를 반환함.
next 현재 날짜 이후로 지정한 요일이 처음으로 나타는 날짜를 반환하는 TemporalAdjuster를 반환함(현재 날짜는 포함하지 않음.)
previous 현재 날짜 이후로 역으로 날짜를 거슬러 올라가며 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환함(현재 날짜는 포함하지 않음.
nextOrSame 현재 날짜 이후로 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환함(현재 날짜도 포함.)
previousOrSame 현재 날짜 이후로 역으로 날짜를 거슬러 올라가며 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환함(현재 날짜도 포함.)

커스텀 TemporalAdjuster 구현

CustomTemporalAdjister.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class CustomTemporalAdjister {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2019, 06, 23);
date = date.with(nextOrSame(DayOfWeek.SUNDAY));
System.out.println(date);
date = date.with(lastDayOfMonth());
System.out.println(date);

date = date.with(new NextWorkingDay());
System.out.println(date);
date = date.with(nextOrSame(DayOfWeek.FRIDAY));
System.out.println(date);
date = date.with(new NextWorkingDay());
System.out.println(date);

date = date.with(nextOrSame(DayOfWeek.FRIDAY));
System.out.println(date);

// 람다 표현식
date = date.with(temporal -> {
DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int dayToAdd = 1;
if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
});
System.out.println(date);
}

private static class NextWorkingDay implements TemporalAdjuster {
@Override
public Temporal adjustInto(Temporal temporal) {
DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
int dayToAdd = 1; // 하루 추가
if (dow == DayOfWeek.FRIDAY) dayToAdd = 3; // 오늘이 금요일이면 3일 추가
if (dow == DayOfWeek.SATURDAY) dayToAdd = 2; // 토요일이면 2일 추가
return temporal.plus(dayToAdd, ChronoUnit.DAYS);
}
}
}

결과 화면

1
2
3
4
5
6
7
2019-06-23
2019-06-30
2019-07-01
2019-07-05
2019-07-08
2019-07-12
2019-07-15

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

DateTimeFomatter를 이용해서 날짜나 시간을 특정 형식의 문자열로 만들 수 있다.

DateFormatterExamples.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class DateFormatterExamples {
public static void main(String[] args) {
useDateFormatter();
}

private static void useDateFormatter() {
LocalDate date = LocalDate.of(2019, 6, 23);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter koreaFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.KOREA);

System.out.println(date.format(DateTimeFormatter.ISO_LOCAL_DATE));
System.out.println(date.format(formatter));
System.out.println(date.format(koreaFormatter));

DateTimeFormatter complexFormatter = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH)
.appendLiteral(". ")
.appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ")
.appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.KOREA);

System.out.println(date.format(complexFormatter));
}
}

결과 화면

1
2
3
4
2019-06-23
23/06/2019
23. 62019
23. 62019

소스코드

참조