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); //...........이하 생략
- enum 객체인 ChronoField를 arg로 하여 날짜값 반환을 받는 것도 가능함. (상기 코드블럭 참조)
- ChronoField implements TemporalField
- https://docs.oracle.com/javase/8/docs/api/java/time/LocalTime.html#get-java.time.temporal.TemporalField-
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하기에, 객체의 값이 변경되는 것이 아니라 새로운 객체를 생성하여 값을 리턴해 주는 구조이므로 사용시에 참고.
- withXXXX 메소드를 통해 LocalDate를 변경할 수 있다.
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

