java 1.8과 date

java 1.8 에서는 시간과 관련한 새로운 클래스를 제공합니다.

  • LocalDate
  • LocalTime
  • Instant
  • Duration

Date 클래스의 문제점

기존에 존재했던 Date java.util.date는 개발을 어렵게 하는 여러 문제점들이 존재했습니다.

코드의 모호함

Date firstDayOf2002 = new Date(122, 0, 1);
System.out.println(firstDayOf2002); // Sat Jan 01 00:00:00 KST 2022

위 코드는 2022-01-01 의 인스턴스를 생성합니다. 년도의 경우 1900년을 기준으로 차이를 의미하며, 월의 경우는 0부터 시작하는 것을 확인할 수 있습니다. 출력시 보이는 결과값은 단순한 Date가 아닌 timestamp, timezone등을 함께 포함 하는 것을 확인할 수 있습니다.

값이 아닌 날짜로서의 기능 부족

Date now = new Date();                                                      
Date tomorrow = new Date(now.getYear(), now.getMonth(), now.getDate());     

Date impossibleDate = new Date(122, 1, 40);   //... (1)
Date tomorrow = new Date(impossibleDate.getYear(), impossibleDate.getMonth(), impossibleDate.getDate() + 1);

날짜를 계산하려면 위와 같은 형태로 코드를 작성합니다. 그러나 (1)과 같이 잘못된 날짜라 할지라도 instance가 생성되며 자동으로 해당 날짜에서 연산한 값(1월 40일은 2월 10로)으로 반환하게 됩니다. 만약, 주말 또는 윤달등의 특수한 상황까지 고려한다면 계산은 더욱더 복잡해질 것 입니다.

불변성을 고려하지 못한 클래스 설계

Date now = new Date();

Calendar calendar = Calendar.getInstance();
calendar.setTime(impossibleDate);
calendar.add(Calendar.DATE, 1);
Date tomorrow = calendar.getTime();

이러한 상황을 고려하여 java1.1 에선 Calendar클래스가 등장했습니다. 그러나 월의 offset이 0으로 시작한다는 기존의 문제점은 고쳐지지 않았으며, 가장 치명적인 부분은 thread-safe 하지 않는다는 것 입니다.

public final void setTime(Date date) {
  setTimeInMillis(date.getTime());
}

public void setTimeInMillis(long millis) {
  // If we don't need to recalculate the calendar field values,
  // do nothing.
  if (time == millis && isTimeSet && areFieldsSet && areAllFieldsSet
      && (zone instanceof ZoneInfo) && !((ZoneInfo)zone).isDirty()) {
      return;
  }
  time = millis;
  isTimeSet = true;
  areFieldsSet = false;
  computeFields();
  areAllFieldsSet = areFieldsSet = true;
}

위 코드는 Date클래스의 일부입니다. Date객체를 반환하는 setTime()함수는 setTimeInMillis()를 호출합니다. 이때 time, isTimeSet 등은 내부변수로 접근 및 변경이 자유롭기에(syncronized, volatile 등이 선언되어 있지 않다) 멀티 스레드 환경에선 문제가 될 가능성이 매우 높습니다.

LocalDate, LocalDateTime 그리고 LocalTime

클래스명에 직관적으로 사용합니다. 날짜외 시간등의 정보가 추가로 필요하다면 LocalDateTime 을 사용하면 됩니다. jdbc의 date 또는 datetime 과도 매핑이 정상적으로 처리되며 위에서 언급한 모든 문제를 해결하였습니다.

LocalDate now = LocalDate.now();
LocalDate tomorrow = now.plusDays(1);

LocalDate firstDayOf2002 = LocalDate.parse("2022-01-01");
firstDayOf2002.getDayOfYear();
firstDayOf2002.getDayOfMonth();
firstDayOf2002.getDayOfWeek();
firstDayOf2002.getDayOfMonth();
firstDayOf2002.isLeapYear();

LocalDate impossibleDay = LocalDate.parse("2022-01-40");    //...(1)
  • (1): DateTimeParseException이 발생

TemporalAdjusters

달력 관련 계산을 언어 차원에서 제공하는 일종의 유틸 클래스 입니다.

LocalDate now = LocalDate.now();
System.out.println(now.with(TemporalAdjusters.firstDayOfMonth()));
System.out.println(now.with(TemporalAdjusters.lastDayOfMonth()));
System.out.println(now.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)));

구체적인 내용 및 다른 함수들은 TemporalAdjusters에서 확인할 수 있습니다.

Instant, OffsetDateTime, ZonedDateTime

timezone의 정보가 추가된 클래스들이며 기본적으로 nano seconds까지 포함합니다.

Instant instant = Instant.now();
System.out.println(instant);        // 2022-04-12T08:05:27.446589Z

OffsetDateTime offsetDateTime = OffsetDateTime.now();
System.out.println(offsetDateTime);  //2022-04-12T17:05:27.473756+09:00

ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);  // 2022-04-12T17:05:27.473351+09:00[Asia/Seoul]
  • Instant: UTC를 기준으로 표현
  • OffsetDateTime: UTC를 기준으로 offset을 표현
  • ZonedDateTime: UTC를 기준으로 offset과 Timezone 까지 표현

카테고리:

업데이트:

댓글남기기