본문 바로가기
Book/모던 자바 인 액션

[모던 자바 인 액션] Chapter 12. 새로운 날짜와 시간 API

by ghan2 2025. 1. 12.

 

💡 이 장의 내용
- 자바 8에서 새로운 날짜와 시간 라이브러리를 제공하는 이유
- 사람이나 기계가 이해할 수 있는 날짜와 시간 표현 방법
- 시간의 양 정의하기
- 날짜 조작, 포매팅, 파싱
- 시간대와 캘린더 다루기

 

이번 장은 제목부터 기대가 된다. 그동안 자바로 날짜 정보를 처리하면서 애를 먹었던 적이 많다. 

 

들어가며..

자바 1.0에서는 java.util.Date 클래스 하나로 날짜와 시간 관련 기능을 제공했다. 

결과가 직관적이지 않았고 스트링으로 반환되는 문자열을 추가로 활용하기가 어렵다.

 

12.1 LocalDate, LocalTime, Instant, Duration, Period 클래스

 

12.1.1 LocalDate와 LocalTime의 사용

  • LocalDate

- 시간을 제외한 날짜를 표현하는 불변 객체

LocalDate date = LocalDate.of(2025, 1, 6);

int year = date.getYear();
Month month = date.getMonth(); // JANUARY
int day = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek(); // MONDAY
int len = date.lengthOfMonth();
boolean leap = date.isLeapYear(); // 윤년인지 판별

- int year = date.get(ChronoField.YEAR); 이렇게 열거자 요소를 이용할 수도 있지만 가독성을 위해 위처럼 사용한다.

 

  • LocalTime

- 마찬가지로 시간, 분, 초에 대한  메서드가 구현되어 있다.

LocalTime time = LocalTime.of(13, 45, 20);

int hour = time.getHour();
int minute = time.getMinute();
int second = time.getSecond();

 

12.1.2 날짜와 시간 조합

- LocalDateTime은 날짜와 시간을 모두 표현할 수 있으며 조합할 수도 있다.

// 2017-09-21T13:45:20
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(tiem);
LocalDateTime dt5 = time.atDate(date);

 

12.1.3 Instant 클래스: 기계의 날짜와 시간

- 기계적인 관점에서 시간을 표현하는 방법이다.

- Instant 클래스는 나노초(10억분의 1초)의 정밀도를 제공한다.

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초)

 

12.1.4 Duration과 Period 정의

두 시간 객체 사이의 지속시간을 만들어본다. 두 개의 LocalTime, 두 개의 LocalDateTime, 또는 두 개의 Instant로 Duration을 만들 수 있다. 

Duration d1 = Duration.between(time1, time2); 
Duration d1 = Duration.between(dateTime1, dateTime2);
Duration d2 = Duration.between(instant1, instant2);

LocalDateTime은 사람이 사용하도록, Instant는 기계가 사용하도록 만들어진 클래스로 두 인스턴스는 서로 혼합할 수 없다. 

또한 Duration 클래스는 초와 나노초로 시간 단위를 표현해서 between 메서드에 LocalDate를 전달할 수 없다. 

 

년, 월, 일로 시간을 표현할 때는 Period를 쓰면 된다. 

Period tenDays = Period.between(LocalDate.of(2017, 9, 11), LocalDate.of(2017, 9, 21));

마지막으로 Duration과 Period 클래스는 자신의 인스턴스를 만들 수 있도록 다양한 팩토리 메서드를 제공한다. 

 

12.2 날짜 조정, 파싱, 포매팅

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

다음 코드들은 기존 객체를 바꾸지 않고 새로운 객체를 반환한다.

LocalDate date1 = LocalDate.of(2017, 9, 21);
LocalDate date2 = date1.withYear(2011);                    // 2011-09-21
LocalDate date3 = date2.withDayOfMonth(25);                // 2011-09-25
LocalDate date4 = date3.with(ChronField.MONTH_OF_YEAR, 2); // 2011-02-25

with와 get 메서드는 날짜와 시간 API의 모든 클래스가 구현하는 Temporal 인터페이스에 정의되어 있다. 정확히 표현하자면 이 두 메서드는 Temporal 객체의 필드값을 읽거나 고칠 수 있다. 

 

12.2.1 TemporalAdjusters 사용하기

지금까지 살펴본 날짜 조정 기능 외에 좀 더 복잡한 날짜 조정 기능이 필요하다면 여기서 제공하는 정적 팩토리 메서드로 이들 기능을 이용할 수 있다. 

LocalDate date1 = LocalDate.of(2014, 3, 18);
LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY)); // 2014-03-23
LocalDate date3 = date2.with(lastDayOfMonth()); 		    // 2014-03-31

 

이처럼 TemporalAdjuster를 이용하면 좀 더 복잡한 날짜 조정 기능을 직관적으로 해결할 수 있다. 이 외에도 필요한 기능이 정의되어 있지 않을 때는 커스텀으로 쉽게 구현할 수 있다. 함수형 인터페이스다 !

Public class NextWorkingDay implements TemporalAdjuster {
	@Override
    public Temporal adjustInto(Temporal temporal) {
    	DayOfWeek dow = DayOfWeek.of(temporal.get(ChronField.DAY_OF_WEEK));
        
        int dayToAdd = 1;
        if (dow == DayOfWeek.FRIDAY) dayToAdd = 3;
        else if (dow == DayOfWeek.SATURDAY) dayToAdd = 2;
        
        return temporal.plus(dayToAdd, ChronUnit.DAYS);
    }
}

 

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

날짜와 시간 관련 작업에서 포매팅과 파싱은 서로 떨어질 수 없는 관계일 것이다. 정적 팩토리 메서드와 상수를 이용해서 손쉽게 포매터를 만들 수 있다. 

LocalDate date = LocalDate.of(2014, 3, 18);
String s1 = date.format(DateFormatter.BASIC_ISO_DATE); // 20140318
String s2 = date.format(DateFormatter.ISO_LOCAL_DATE); // 2014-03-18

이렇게 상수를 이용해서 날짜를 표현할 수 있고,

DateTimeFormatter = formatter = DateTimeFormatter.ofPattern("dd/MM/yyy");
LocalDate date1 = LocalDate.of(2014, 3, 18);
String formattedDate = date1.format(formatter);
LocalDate date2 = LocalDate.parse(formattedDate, formatter);

패턴에 해당하는 문자열을 만들수도 있다.