모던 자바 인 액션 12장

LocalDate, LocalTime, LocalDateTime 알아보자

LocalDate & LocalTime & LocalDateTime

LocalDate (feat. java8 doc)

사실상 LocalDate 와 LocalTime은 대동소이한 점이 많기에, LocalTime과 LocalDateTime은 생략한다.(Java Doc도 특별히 차이점에 대해 기술한 사항이 없음….)

LocalTime만의 특징

LocalDate의 시간 버전이고…..ㅋ 초의 단위시간이 ‘nanosecond’이다.

LocalDateTime만의 특징

뭐 그런거 없다. 그냥 LocalDate + LocalTime인 spec임….
  • Immutable한 객체이며, 별도의 ‘시간’ 정보는 저장 & 표현하지 않는다. (타임존 포함)
  • value-based 클래스이므로, ==, hashcode, synchronization 으로 인한 비교는 예측 불가능한 결과를 가지므로, 객체값 비교를 하고자 할 때에는 equals 메소드를 사용해라!

조금 더 아는 척을 하고 싶다면 : ISO-8601캘린더 시스템을 이용한다고 한다.

인스턴스 생성을 하고 싶으면 다음과 같이 코드를 작성해 주면 된다. (of메소드 이용)

LocalDate date = LocalDate.of(2019,8,23); // 2019-08-23
LocaalDate now = LocalDate.now();
int year = date.getYear();
Monty month = date.getMonth();
int day = date.getDayOfmonth();
 
 
int year = date.get(ChronoField.YEAR);
//...........이하 생략

String을 받아 파싱하는 기능도 지원해 줌

LocalDate date = LocalDate.parse("2019-08-23");
  • 정형화된 YYYY-MM-DD 포멧 뿐만 아니라, DateTimeFormatter를 arg로 하여 정형화되어있지 않은 날짜 포멧팅의 경우로 처리도 가능.
    • 포멧팅이 맞지 않는 경우, DateTimeParseException 을 발생시킴. (implements RuntimeException)

Instant에 대해 알아보자

  • LocalDateTime은 사람 관점에서 보기 편한 시간단위(년,월,일,시,분,초)
  • Instant는 컴퓨터 관점에서 보기 편한 시간단위.
    • 익히 알고 있는 Unix timestamp. 기준시간 : 1970/01/01 00:00:00
  • 나노초 단위까지 저장

인스턴스 생성 방법은 다음과 같다

Instant.ofEpochSecond(3); // 1970/01/01 00:00:03
Instant.ofEpochSecond(4, 1000000000); // 1970/01/01 00:00:05 <-- 기준시간에서 4초를 더한 후, 1억 나노초를 더하여 5초

//human readable한 값을 얻고 싶으면
int day = Instant.now().get(ChronoField.DAY_OF_MONTH);
  • 나노초 단위 spec까지 저장하기 위해서는, 현재 primitive data type의 long 변수 하나만으로는 시간 정보를 다 저장할 수가 없음
    • private final long seconds;
    • private final int nanos;
  • 이렇게 초/나노초 단위를 각각 long, int 타입으로 저장하여, 시간의 표현 범위는 무한대에 가깝게 늘렸다.
    • -1000000000-01-01T00:00Z 부터, 1000000000-12-31T23:59:59.999999999Z 까지. (기원전 10억년 ~ 서기 10억년)
  • 기타… 윤년과 같은 개념으로 지구의 자전시간과 초 단위의 보정을 위한 spec이 Time-scale라는 항목으로 문서에 기술 되어있으니 뭔가 더 아는 척을 하고 싶다면 참고하시면 될 듯

Duration과 Period에 대해 알아보자

  • 특정 시간이 아닌, ‘기간’ 내지는 지속시간 단위를 표현하고 싶은 경우에는 Duration & Period를 이용한다
  • Temporal 인터페이스를 구현한 LocalDate,LocalTime,LocalDateTime,Instant 객체의 기간 정보를 관리하게 해 주는 클래스이다.

Duration

  • Instant 클래스와 같은 저장 방식을 갖고 있다. long 변수 하나만으로는 시간 정보를 다 저장할 수 없기에, long(초) & int(나노초) 두개의 인스턴스 변수를 내장한 채로 기간을 표현한다.
    • 주의사항 : 그래서 ‘초’ 정보를 갖고 있는 Temporal 객체만 사용 가능

사용법은 다음과 같다.

Duration d1 = Duration.between(time1, time2);    // LocalTime
Duration d2 = Duration.between(dateTime1, dateTime2);  // LocalDateTime
Duration d3 = Duration.between(instant1, instant2);  // Instant
 
 
Duration d4 = Duration.between(date1, date2);  // LocalDate --> throws Exception
Duration d5 = Duration.between(time1, instant2) // throws Exception
  • 분명 Duration의 between 메서드는 Temporal 인터페이스를 구현한 객체를 파라미터로 받는데,
    파라미터로 LocalDate 를 넣는 경우 Exception을 throw한다.
  • LocalDate는 왜 안되는가?
    • Date에는 초 단위가 없기 때문. Duration은 ‘초’ 정보를 갖고 있는 Temporal 객체만 사용 가능하다고 하였다!
  • 그럼 반쪽짜리 기간 관리인 것 아닌가?
    • Period를 쓰자.

Period

  • Duration의 ‘날짜 이상 단위’ 버전
  • Duration이 시,분,초 단위의 기간을 표현하였다면, Period는 날짜 이상의 단위의 기간을 표현한다.
Period p1 = Period.between(LocalDate.of(2020,2,28), LocalDate.of(2020,3,1));  // 2 (윤년까지 계산해 준다!)

날짜조정, 파싱, 포매팅

  • LocalDate등과 같은 값을 조정하고자 할 때(년,월,일 변경)
    • withXXXX 메소드를 통해 LocalDate를 변경할 수 있다.
      • 하지만, 여태 학습한 객체는 다 immutable하기에, 객체의 값이 변경되는 것이 아니라 새로운 객체를 생성하여 값을 리턴해 주는 구조이므로 사용시에 참고.
LocalDate date1 = LocalDate.of(2019,8,23);  // 2019-08-23
LocalDate date2 = date1.withYear(2011);    // 2011-08-23
LocalDate date3 = date2.withDayOfMonth(25); // 2011-08-25
 
 
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 2); //2011-02-25

주의사항

상기 샘플코드들의 date1,date2,date3을 assign하는 연산이 죄다 immutable한 객체를 내뱉는 구조이다. 사용시 유의하자.

따라서……..

LocalDate date = LocalDate.of(2019,8,23);
date.withYear(2011);
 
 
sysout(date);    // 2019-08-23. 2011-08-23 이 출력되지 않는다!

TemporalAdjusters 에 대해 알아보자

특정 날짜가 아니라, ‘이번달 마지막 날’, 내지는, ‘6월의 첫 수요일 날’ 과 같은, 계산식으로는 조금 복잡하게 생각을 해야 하는 상황에서 유용하게 쓰일 수 있다.

메소드 리스트들은 다음과 같다

https://docs.oracle.com/javase/8/docs/api/java/time/temporal/TemporalAdjusters.html

사용법은 다음과 같다

LocalDate localDate = LocalDate.of(2019,8,23);
LocalDate getFirstDay = localDate.with(TemporalAdjusters.firstDayOfMonth()); // 2019-08-01
LocalDate lastDayOfYear = localDate.with(TemporalAdjusters.lastDayOfYear()); // 2019-12-31

(굳이 갖다붙여)사내 시스템에서 유용하게 사용할 수 있는 영역

  • 배송도착안내 시스템
  • 금 or 토요일 주문시, 영업일이 아닌 토요일이나 일요일의 경우 배송이 도착하지 않음.
  • TemporalAdjusters 를 이용하면, 주문을 한 날 D-Day를 기준으로 소요되는 영업일만 계산하여 배송도착시점을 알 수가 있음.
public class ExpectedDeliveredDay implements TemporalAdjuster {
 
    private final int nextDayShipDueHour = 17;
    /**
     * 17시 이후는 익일 발송, 17시 이전은 당일 발송
     */
    @Override
    public Temporal adjustInto(Temporal temporal) {
 
        int shipPeriod = 1; //배송소요일은 발송일 기준으로 다음날(+1일)
 
        //발송일 정의
        DayOfWeek shipDayOfWeek = DayOfWeek.of(temporal.get(ChronoField.DAY_OF_WEEK)); //기본은 당일 발송
        if (temporal.get(ChronoField.HOUR_OF_DAY) > nextDayShipDueHour) { //17시를 넘길 시 익일 발송
            shipDayOfWeek = shipDayOfWeek.plus(1);
        }
        //배송일이 토 -> 이틀이 더해져 월요일 발송
        //배송일이 일 -> 하루가 더해져 월요일 발송
        if (shipDayOfWeek == DayOfWeek.SATURDAY || shipDayOfWeek == DayOfWeek.SUNDAY) {
            shipDayOfWeek = DayOfWeek.MONDAY;
        }
 
        Temporal shipTemporalDay = temporal.with(TemporalAdjusters.nextOrSame(shipDayOfWeek));
        return shipTemporalDay.plus(shipPeriod, ChronoUnit.DAYS);
    }
}

DateTimeFormatter에 대해 알아보자

 Temporal을 상속받아 사용하는 LocalDateTime류의 날짜 표시를 특정 포메팅에 의거, 이쁘게 포메팅 해 주는 클래스

사용법

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
LocalDate dt = LocalDate.of(2019,8,23);
String result = dt.format(formatter); // 20190823  LocaalDate to String
LocalDate.parse(result, formatter); // String to LocalDate

약간 옛날 냄새가 킁킁킁 나면 요렇게 builder패턴으로 조금 세련되게도 만들 수 있음.ㅇㅇ

DateTimeFormatter myFormatter = new DateTimeFormatterBuilder()
        .appendText(ChronoField.YEAR)
        .appendLiteral("  ")
        .appendText(ChronoField.MONTH_OF_YEAR)
        .appendLiteral(". ")
        .appendText(ChronoField.DAY_OF_MONTH)
        .parseCaseInsensitive()
        .toFormatter()
 
 
LocalDate dt = LocalDate.of(2019,8,23);
 
 
dt.format(myFormatter);       // 2019 8월. 23

Leave a Comment