DateCalendar가 가진 문제점을 해결하기 위해서 JDK.18부터 java.time패키지가 추가되었다.

이안에는 총 4개의 하위 패키지로 구성되어 있다.

  • java.time
    • 날짜와 시간을 다루는데 필요한 핵심 클래스들을 제공
  • java.timechrono
    • 표준(ISO)이 아닌 달력 시스템을 위한 클래스들을 제공
  • java.time.temporal
    • 날짜와 시간을 파싱하고, 형식화하기 위한 클래스들을 제공
  • java.time.zone
    • 시간대와 관련된 클래스들을 제공

Java.time패키지의 핵심 클래스

java.time패키지의 특징으로는 날짜와 시간을 별도의 클래스로 분리해 놓았다.

그래서 다음과 같이 사용한다.

  • 시간 : LocalTime클래스
    • 시간 - 시간 (기간) : Duration클래스
  • 날짜 : LocalDate클래스
    • 날짜 - 날짜 (기간) : Period클래스
  • 시간 + 날짜 : LocalDateTime클래스
  • 시간대 + 시간 + 날짜 : ZonedDateTime

이러한 클래스들을 사용하기 위해 객체를 생성해야하는데 생성하는 법은 다음과 같다.

LocalDate date = LocalDate.now(); // new를 사용하지 않음

LocalDate date = LocalDate.of(2021,09,18);

이런식으로 now()of()를 사용해서 객체를 생성할 수 있다. LocalDate를 예로 들었지만 다른 클래스들도 이런방식으로 생성하면 된다.

이러한 now()of()static메서드이다.

인터페이스의 종류

Temporal과 TemporalAmount

Temporal은 보통 날짜와 시간을 위한것이라고 생각하면 좋다.

  • Temporal, TemporalAccessor, TemporalField 를 구현한 클래스
    • LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant ...
  • TemporalAmount를 구현한 클래스
    • Period, Duration

TemporalUnit과 TemporalField

  • TemporalUnit: 날짜와 시간의 단위를 정의해 놓은 인터페이스, 열거형 ChronoUnit으로 구성되어 있다.
  • TemporalField : 년, 월, 일 등 날짜와 시간의 필드를 정의해 놓은 것, 이것도 열거형 ChronoField가 이 인터페이스를 구성하였다.

다음과 같은 예시를 보면 이해가 빠르다. 다음 예시는 특정 필드의 값을 얻기위한 매서드인 get()과 날짜와 시간을 빼거나 더하는 plus()에 대한 정의 이다.

int get(TemporalField field)
LocalDate plus(long amountToAdd, TemporalUnit, unit);

이를 TemporalField인지 TemporalUnit에서 사용할 수 있는 열거형인지 판단하는 방법은 다음과 같다.

boolean isSupported(TemporalField field);
boolean isSupported(TemporalUnit unit);

LocalDate와 LocalTime

LocalDateLocalTime은 날짜와 시간을 나타낸다.

이러한 값들을 생성하는 of()now()메서드는 static메서드로 특히 of()에는 다양한 매개변수가 오버로딩 되어있다.

LocalTime을 예로 들면 시간, 분, 초 뿐만이 아니라 나노초도 포함이 된다.

또한, 이들의 특징으로는 LocalTime을 예로들자면 만 단위로 지정해서 넣어줄 수 도 있다.

LocalTime time = LocalTime.ofSecondDay(86399); // 23시 59분 59초

또한, 시간과 표를 나타내는 것을 여러가지 방법이 있을 텐데 parse()메서드를 통해서 문자열을 날짜로 변환할 수 있다.

LocalDate date = LocalDate.parse("1999-03-16"); // 1999년 3월 16일
LocalTime time = LocalTime.parse("23:59:59); // 23시 59분 59초

get메서드

보통 Java에서는 특정 필드를 가져올 때는 get() 특정필드를 수정하거나 값을 변경할 때는 set()을 사용한다.

물론 LocalDateLocalTime에도 각 시간, 분, 초 이러한 단위들을 가져오는 get()함수들이 다량 존재한다.

인텔리제이나, 이클립스같은 IDE의 기능을 사용하여 살펴보자.

몇개만 소개하자면 다음과 같다.

LocalDate date = LocalDate.now();

date.getYear() // int형 년도 반환
date.getMonthValue() // Month형 월 반환
date.getMonth() // int형 월 반환
....

LocalTime time = LocalTime.now();

time.getHour()// int형 시간 반환
time.getMinute() // int형 분 반환
time.getSecond() // int형 초 반환
time.getNano() // int형 나노초 반환

지금은 get()만 사용했지만 기간이 너무 많거나 int형으로 표현이 안되는 값들은 getLong()을 사용하여 반환받는 것이 맞다.

ChronoField

이러한 get()을 하려할 때 메서드들의 매개변수로 사용할 수 있는 필드의 목록이다.

이는의 예시들을 다음과 같이 있다.

ERA // 시대
YEAR_OF_ERA , YEAR // 년
DAY_OF_WEEK // 요일 (1: 월요일, 2: 화요일)

MICRO_OF_SECOND * // 백만분의 일초
...

이러한 필드가 여러개가 있다. 사용할 때 주의해야할 점은

  • LocalDate는 날짜인데 사용하기 초를 나타내는 필드를 사용할거나 이러면 사용할 수 없다.
  • *이 표시되어 있는 필드는 int타입을 넘어가는 필드 이므로 getLong()을 사용해야한다.

필드의 값 변경하는법

필드의 값을 변경하기 위한 메서드로는 with(), plus(), minus()들이 존재한다.

날짜와 시간에서 특정 필드 값을 변경하려면, 다음과 같이 with로 시작하는 메서드를 사용하면 된다.

이러한 메서드들은 보통 새로운 객체를 생성해서 반환므로 대입연산자를 사용해야한다.

date = date.withYear(2000); // 년도를 2000년으로 변경한다.
time = time.withHour(12); // 시간을 12시로 변경

truncatedTo()메서드

이 메서드는 LocalTime에만 존재하며 LocalDate()는 존재하지 않는다.

왜냐하면 truncatedTo()는 짖ㅇ된 것보다 작은 단위의 필드를 모두 0으로 만드는 것이다.

LocalTime time = LocalTime.of(18, 12, 19); // 18시 12분 19초
time = time.turncatedTo(ChronoUnit.HOURS); // 18시 00분 00초가 된다.

이러한 기능이기 때문에 LocalDate()에서 만약 년도를 기준으로한다면 월,일이 0이되는데

0인 월, 일은 없기 때문이다.

날짜와 시간의 비교

LocalDateLocaltime의 날짜, 시간을 비교할 때는 compareTo()를 이용하면 되지만 그것보다 편리하게 비교할 수 있는 메서드 들이 존재한다.

boolean isAfter (ChronoLocalDate other); 
boolean isBefore (ChronoLocalDate other);
boolean isEqual (ChronoLocalDate other);

이중 isEqual메서드는 LocalDate에만 존재한다.

또한, isEqual은 오직 날짜만 비교한다. 한마디로 2018년 12월 19일이나 2019년 12월 19일은 같다는 것이다.

equals()는 모든 필드가 일치해야한다. 그러므로 2018년 12월 19일은 2019년 12월 19일은 다르다.

Instant

instant는 에포크 타임으로부터 경과된 시간을 나노초 단위로 표현한다.

이는 우리가 보기는 어렵다. (ex 1970-01-01 00:00:00 UTC)

하지만 단일 진법으로만 다루기 때문에 계산이 편하다.

생성하는 방법은 두가지 방법이있다. now()를 사용하는 것과 ofEpochSecond()를 사용하는 것과 같다.

Instant now = Instant.now(); 
Instant now = Instant.ofEpochSecond(now.getEposchSeocnd());
Instant now = Instant.ofEpochSecond(now.getEpochSecond(), now.getNano());

이러한 InstantDate로 변환되거 반대의경우고 다음과 같은 메서들ㄹ 통해 변환할 수 있다.

from(Instant instant); // Instant -> Date
toInstant() // Date -> Instant

LocalDateTime과 ZonedDateTime

LocalDateTimeZonedDateTime은 무언가 둘다 합쳐놓은 것이다.

  • LocalDateTime : LocalDate + LocalTime
  • ZonedDateTime : LoclaDateTime + 시간대

이렇게 구성이 되어있지만 이를 operand들을 합쳐서 결과를 생성할 있다.

물론 그냥 바로 생성해도 되지만 예를들자면 LocalDateTime으로 바꾸려고하는데 LocalDate는 현재 생성이 되있다면

LocalDate date = LocalDate.of(2018,12,19);

LocalDateTime dateTime = date.atTime(18,12,19); 

이런 방식으로도 생성할 수 있고 물론 반대도 존재하고 혹은 아무것도 없을 때, 아니면 둘다 있을 때 도 생성이 가능하다 여러가지 방법이 있으니까 참고하자

LocalDate date = LocalDate.of(2018, 12, 19);
LocalTime time = LocalTime.of(99, 03, 16);

LocalDateTime dt = LocalDateTime.of(date, time);
LocalDateTime dt1 = date.atTime(time);
LocalDateTime dt2 = time.atDate(date);
LocalDateTime dt3 = date.atTime(12, 8, 14);
LocalDateTime dt4 = time.atDate(LocalDate.of(2018, 8, 14));
LocalDateTime dt5 = date.atStartOfDay();

반대로 LocalDateTimeLocalDate또는 LocalTime으로 변환할 수 있다.

LocalDateTime dt = LoclaDateTime.of(2018,12,19,99,3,16);
LocalDate date = dt.toLocalDate();
LocalTime time = dt.toLocalTime();

이를 LocalDateTime으로 ZonedDateTime으로 만들 수 있다.

왜냐하면 LocalDateTime + 시간대가 ZonedDateTime이기 때문이다.

LocalDateTime dt = LoclaDateTime.of(2018,12,19,12,3,16);

ZoneId zid = ZoneId.of("Asia/Seoul");
ZonedDateTime zdt = dt.atZone(zid); // 2018-12-19T12:03:16+09:00[Asia/Seoul]

이런식으로 atZone을 사용하여 시간대 정보를 넣어서 만들 수 있다.

이러한 ZonedDateTime을 이용하여 특정 시간대의 시간을 알기 위해서는 다음과 같이 하면된다.

ZoneId nyId = ZoneId.of("America/New_York");
ZonedDateTime nyTime = ZonedDateTime.now().withZoneSameInstant(nyId);

TemporalAdjusters

이를 사용하면 특정 날짜 계산을 사용할 수 있다.

plus() , minus()를 사용해서도 계산 할 수 있지만 특정년도의 3주차 월요일 이런것들을 물어보면 계산하기 복잡하다 이러한 TemporalAdjusters클래스를 용하여 편하게 계산 할 수 있다.

LocalDate today = LocalDate.now();
LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));

위와 같은 방식들을 사용해서 계산할 수 있다. 사용할 수 있는 것들은 다음것들 등이 있다.

firstDayOfNextYear() // 다음해의 첫 날
lastDayForYear() // 올 해의 마지막 날

firstInMonth (DayofWeek dayOfWeek) // 이번 달의 첫 번째 ? 요일
next (DayofWeek dayOfWeek) // 다음 ?요일(당일 미포함)

...

이러한 다양한 메소드들이 존재한다.

TemporalAdjusters직접 구현하기

이러한 TemporalAdjusters를 직접 구현할 수도 있다.

TemporalAdjuster인터페이스를 사용하면 쉽게 구현할 수 있다.

이 클래스는 다음과 같은 추상 메서드 하나만 정의되어 있고, 이 메서드만 구현하면 된다.

@FuntionalInterface
public interface TemporalAdjuster{
    Temporal adjustInto(Temporal temporal);
}

이를 오버라이딩해서 자유롭게 구현하면 된다.

Period와 Duration

Period는 날짜와의 차이를 나타내고

Duration은 시간의 차이를 계산한다.

어떤 특정의 두 날짜와 시간을 구별하기 위해서는 between()이라는 메소드를 사용하면 편하다.

LocalDate date1 = LocalDate.of(2018,12,19);
LocalDate date2 = LocalDate.now();

Period pe = Period.between(date1, date2);

DurationPeriod에서 특정 필드의 값을 얻기위해서는 LocalDateLocalTime과 마찬가지로 get()을 사용한다.

하지만 이러한 값들을 Unit이나 Field를 가져올 때는 불안하거나 한정되어있다

그렇기 때문에 이를 안전하게 가져오기 위해서는 LocalTime이나 LocalDate로 변환해서 사용하는 것이 좋다.

이러한 PeriodDurationLocalTimeLocalDate처럼

of()with()메서드를 사용할 수 있고 또한, plus()처럼 사칙연산이나 isAfter()처럼 비교연산을 하거나 다른단위로 변환할 수 있는 메서드들이 존재하니 참고하면 좋을 것 같다.

파싱과 포맷

여기서의 파싱은 날짜와 시간을 원하는 형식으로 출력하고 해석하는 방법이다.

내가 원하는 타입으로 format()을 사용하여 형식을 정해줄 수도 있다.

이러한 종류는 다양하니 java api를 찾아보는 것도 좋을 것같다

문자열을 날짜와 시간으로 파싱하기

문자열을 날짜 또는 시간으로 변환하기 위해서는 static메서드인 parse()를 사용하면 가능하다.

이는 오버로딩된 메소드가 여러개가 존재한다. 예제를 보자

Local date = LocalDate.parse("2016-01-02", DateTimeFormatter.ISO_LOCAL_DATE);

DateTimeFormatter에 저장된 형식을 사용하여 정의된 형식으로 출력할 수도 있다.

또한, ofPattern()을 사용하여 파싱을 할 수도 있다.

ofPattern을 사용하면 원하는 형식에 따라 날짜 시간 분을 읽어올 수 있다.

DateTimeFormatter pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

Refernece

  • 자바의 정석

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

[Java/Study] 쓰레드 (1)  (0) 2021.09.24
[Java/Study] 지네릭 타입  (0) 2021.09.19
[Java/Study] 예외처리  (0) 2021.09.14
[Java/Study] 내부 클래스  (0) 2021.09.11
[Java/Study] 인터페이스  (0) 2021.09.11

+ Recent posts