java8에서의 날짜/시간 API (LcalDate/LocalTime)

반응형
728x90
반응형

LocalDate

LocalDate 인스턴스는 시간을 제외한 날짜를 표현하는 불변 객체이다. 어떤 시간대 정보도 포함하지 않는다. 우리는 정적 팩토리 메서드 of로 LoalDate 인스턴스를 만들 수 있다.

LocalDate date = LocalDate.of(2017, 9, 21);

int year = date.getYear(); // 2017
Month month = date.getMonth(); // SEPTEMBER
int day = date.getDayOfMonth(); // 21

 

또한 팩토리 메서드 now를 사용하여 현재 날짜 정보를 얻을 수 있다.

LocalDate today = LocalDate.now();

 

다른 방법으로는, get 메서드에 TemporalField를 전달해서 정보를 얻는 방법도 있다. TemporalFiled는 시간 관련 객체에서 어떤 필드의 값에 접근할지 정의하는 인터페이스다. 열거자 ChronoField는 TemporalField 인터페이스를 정의하므로 ChronoFiled의 열거자 요소를 이용해서 원하는 정보를 쉽게 얻을 수 있다.

LocalDate date = LocalDate.of(2017, 9, 21);

int year = date.get(ChronoField.YEAR);
int month = date.get(ChronoField.MONTH_OF_YEAR);
int day = date.get(ChronoField.DAY_OF_MONTH);

 

문자열로 LocalDate의 인스턴스를 만드는 방법도 있다. 만약 문자열을 파싱할 수 없을 때 parse 메서드는 DateTimeParseException을 일으킨다.

LocalDate time = LocalDate.parse("2017-09-21");

 

 

LocalTime

LocalTime 클래스로 13:45:20 과 같은 시간을 표현할 수 있다. 2가지 정적 메서드 of로 LocalTime 인스턴스를 만들 수 있다. 시간과 분을 인수로 받는 of 메서드와 시간과 분, 초를 인수로 받는 of 메서드가 있다.

 

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

int hour = time.getHour(); // 13
int minute = time.getMinute(); // 45
int second  = time.getSecond(); // 20

 

문자열로 LocalTime의 인스턴스를 만드는 방법도 있다. 만약 문자열을 파싱할 수 없을 때 parse 메서드는 DateTimeParseException을 일으킨다.

 

LocalTime time = LocalTime.parse("13:45:20");

 

parse 메서드에 DateTimeFormatter 를 전달할 수도 있다. DateTimeFormatter의 인스턴스는 날짜, 시간 객체의 형식을 지정한다.

 

 

 

날짜와 시간 조합

LocalDateTime은 LocalDate와 LocalTime을 쌍으로 갖는 복합 클래스이다. 즉, LocalDateTime은 날짜와 시간을 모두 표현할 수 있으며 직접 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);

 

LocalDate의 atTime 메서드에 시간을 제공하거나 LocalTime의 atDate 메서드에 날짜를 제공해서 LocalDateTime을 만드는 방법도 있다. LocalDateTime의 toLocalDate나 toLocalTime 메서드로 LocalDate나 LocalTime 인스턴스를 추출할 수 있다.

 

LocalDateTime dt1 = LocaldateTime.of(2017, Month.SEPTEMBER, 21, 13, 45, 20);

LocalDate date1 = dt1.toLocalDate();
LocalTime time1 = dt1.toLocalTime();

 

 

 

Instant 클래스

사람은 보통 주, 날짜, 시간, 분으로 날짜와 시간을 계산한다. 하지만 기계에서는 연속된 시간에서 특정 지점을 하나의 큰 수로 표현하는 것이 가장 자연스러운 시간 표현 방법이다. 새로운 java.util.Instant 클래스에서는 이와 같은 기계적인 관점에서 시간을 표현한다. 팩토리 메서드 ofEpochSecond에 초를 넘겨줘서 Instant 클래스 인스턴스를 만들 수 있다. Istant 클래스는 나노초의 정밀도를 제공하고, 오버로드된 ofEpochSecond 메서드 버전에서는 두번째 인수를 이용해서 나노초 단위로 시간을 보정할 수 있다.

 

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억 나노초

 

LocalDate 등을 포함하여 사람이 읽을 수 있는 날짜, 시간 클래스에서 그랬던 것처럼 Instant 클래스도 사람이 확인할 수 있도록 시간을 표현해주는 정적 팩토리 메서드 now를 제공한다. 하지만 Instant는 기계 전용의 유틸리티라는 점을 기억해야한다. Instant는 초와 나노초 정보를 포함하기 때문에 사람이 읽을 수 있는 시간 정보를 제공하지 않는다. 그렇기 때문에 아래 코드는 UnSupportedTemporalTypeException을 발생시킨다.

 

int day = Instant.now().get(ChronoField.DAY_OF_MONTH)

 

 

Duration

Duration 클래스의 정적 팩토리 메서드 between으로 두 시간 객체 사이의 지속시간을 만들 수 있다. 아래 코드처럼 2개의 LocalTime, 2개의 LocalDateTime, 2개의 Instant로 Duration을 만들 수 있다.

 

Duration d1 = Duration.between(time1, time2); // 2개의 LocalTime
Duration d1 = Duration.between(dateTime1, dateTime2); // 2개의 LocalDateTime
DUration d2 = Duration.between(instant1, instant2); // 2개의 Instant (기계)

 

LocalDateTime은 사람이 사용하도록, Instant는 기계가 사용하도록 만들어진 클래스로 이 2개의 인스턴스를 사용하여 Duration은 만들 수 없다. Duration 클래스는 초와 나노초 시간 단위를 표현하므로 between 메서드에 LocalDate를 전달할 수 없다.

 

두 시간 객체를 사용하지 않고도 우리는 Duation을 만들 수 있다.

 

Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);

 

 

Period

우리는 위에서 Duration 클래스로는 LocalDate를 전달할 수 없다고 배웠다. 우리는 Period 클래스의 팩토리 메서드 between을 사용해여 두 LocalDate의 차이를 확인할 수 있다.

 

Duration threeMinutes = Duration.ofMinutes(3);
Duration threeMinutes = Duration.of(3, ChronoUnit.MINUTES);

 

두 시간 객체를 사용하지 않고도 우리는 Period를 만들 수 있다.

 

Period tenDays = Period.ofDays(10);
Period threeWeeks = Period.ofWeeks(3);
Period twoYearsSixMonthOneDay = Period.of(2, 6, 1);

 

 

withAttribute 메서드

우리는 withAttribute 메서드로 기존의 LocalDate를 바꾼 버전을 직접 간단하게 만들 수 있다. 바뀐 속성을 포함하는 새로운 객체를 반환하는 메서드로, 모든 메서드는 기존 객체를 바꾸는 것이 아니다.

LocalDate date1 = LocalDate.of(2017, 9, 21); // 2017-09-21
LcoalDate 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-02

 

마지막 행에서의 with 메서드는 이전에 우리가 사용했던 get과 쌍을 이룬다.

 

LocalDate date = LocalDate.of(2017, 9, 21);

int year = date.get(ChronoField.YEAR);

 

이 두 메서드는 날짜와 시간 API의 모든 클래스가 구현하는 Temporal 인터페이스에 정의되어있다. Temporal 인터페이스는 LocalDate, LocalTime, LocalDateTime, Instant 처럼 특정 시간을 정의한다. get과 with 메서드로 Temporal 객체의 필드 값을 읽거나 고칠 수 있다. 어떤 Temporal 객체가 지정된 필드를 지원하지 않으면 UnsupportedTemporalTypeException이 발생한다.

 

 

plus, minus 메서드

우리는 지정된 시간을 추가하거나 뺄 수 있다.

 

LocalDate date1 = LocalDate.of(2017, 9, 21); // 2017-09-21 
LocalDate date2 = date1.plusWeeks(1); // 2017-09-28
LocalDate date3 = date2.minusYears(6); // 2011-09-28
LocalDate date5 = date3.plus(6, ChronoUnit.MONTHS); // 2012-03-28

 

plus, minus 메서드는 Temporal 인터페이스에 정의되어있다. Temporal을 특정 시간만큼 앞 뒤로 이동시킬 수 있고, 해당 메서드 인수에 숫자와 TemporalUnit을 활용할 수 있다.

 

 

TemporalAdjusters 사용하기

우리는 예를들어 다음주 일요일, 돌아오는 평일, 어떤 달의 마지막 날 등 좀더 복잡한 날짜 조정 기능이 필요할 것이다. 이때 오버로드된 버전의 with 메서드에 좀더 다양한 동작을 수행할 수 있도록 하는 기능을 제공하는 TemporalAdjusters를 전달할 수 있다. TemporalAdjusters에서 정의하는 정적 팩토리 메서드로 이들 기능을 이용할 수 있다.

 

import static java.time.temporal.TemporalAdjusters.*;

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

`nextOrSame` 메서드처럼 TemporalAdjusters 클래스의 팩토리 메서드에는 많은 메서드들이 있다. 이를 사용하면 좀 더 복잡한 날짜 조정 기능을 직관적으로 해결할 수 있다. 또한 필요한 기능이 정의되어 있지 않을 때는 커스텀 TemporalAdjuster 구현을 만들 수 있다. TemporalAdjuster 인터페이스는 아래 코드처럼 하나의 메서드만 정의한다. 그러므로 함수형 인터페이스다.

```java
@FuntionalInterface
public interface TemporalAdjuster {
  Temporal adjustInfo(Temporal temporal);
}

 

커스텀 TemporalAdjuster 구현의 예시를 보자.

 

public class nextWorkingDay implements TemporalAdjuster {
  @Override
  public Temporal adjustInto(Temporal temporal) {
    DayOfWeek dow = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK));
    int dayToAdd = 1;
    if (...)
    ..
    return temporal.plus(dayToAdd, ChronoUnit.DYAS);
  }
}

 

 

java.time.format.DateTimeFormatter

포매팅과 파싱 전용 패키지인 java.time.format 이 추가되었다. 이 패키지에서 가장 중요한 클래스는 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는 스레드에서 안전하게 사용할 수 있다.

 

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

 

DateTimeFormmater 클래스는 특정 패턴으로 포매터를 만들 수 있는 정적 팩토리 메서드도 제공한다. LocalDate의 format 메서드는 요청 형식의 패턴에 해당하는 문자열을 생성한다. 그리고 정적 메서드 parse는 같은 formatter을 적용해서 생성된 문자열을 파싱하여 다시 문자열을 생성한다.

 

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

 

우린 ofPattern 메서드를 사용하여 Locale로 포매터를 만들 수 있다. 지역화된 시간을 표시할 수 있다는 말과 같다. DateTimeFormatter 클래스로 복합적인 포매터를 정의해서 좀더 세부적인 포매터를 제어할 수 있다. DateTimeFormatter 클래스로 대소문자를 구분하는 파싱, 패딩 등을 활용할 수 있다. 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 클래스의 시간대 표현

ZoneId romeZone = ZoneId.of("Europe/Rome");

 

ZoneRules 클래스에는 약 40개 정도의 시간대가 있는데 우리는 ZoneId의 getRules()를 이용해서 해당 시간대의 규정을 획득할 수 있다. 위 예제처럼 우리는 지역 ID로 특정 ZoneId를 구분한다.

 

ZoneId zoneId = TimeZone.getDefault().toZoneId();

 

ZoneId 객체를 얻은 다음 LocalDate, LocalDateTime, Instant를 이용해서 ZonedDateTime 인스턴스로 변환할 수 있다. ZonedDateTime은 지정한 시간대에 상대적인 시점을 표현한다.

 

LocalDate date = LocalDate.of(2014, Month.MARCH, 18);

ZonedDateTime zdt1 = date.atStartOfDay(romeZone);
LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);

 

아래 코드처럼 ZoneId를 이용해서 LocalDateTime을 Instant로 바꾸는 방법도 있다.

 

Instant instant = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant, romeZone);

 

기존의 Date 클래스를 처리하는 코드를 사용하는 경우에는 Instant로 작업하는 것이 유리하기 때문에 위 코드를 사용해야할 때가 올 수 있다.

 

 

UTC/GMT

UTC(Universal Time Coordinated)는 협정 시계시이고 GMT(GreenWich Mean TIme)은 그리니치 표준시를 기준으로 시간대를 표현한다. 예를 들어 뉴옥은 런던보자 5시간이 느리다라고 표현할 수 있다. ZoneId의 서브클래스인 ZoneOffset 클래스로 시간값의 차이를 표현할 수 있다.

 

LocalDateTime dateTime = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45);
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(date, newYorkOffset);

 

반응형

'Coding > Java' 카테고리의 다른 글

JAVA8 스트림 예제로 익숙해지기  (0) 2020.11.28
자바 List의 null 체크 (with isEmpty())  (0) 2020.11.07
자바8의 default 메서드 등장  (0) 2020.11.06
의존 객체 주입의 사용 이유  (0) 2020.11.06
제네릭 메서드  (0) 2020.11.06

Designed by JB FACTORY