티스토리 뷰

반응형

제가 이전에 자주 사용했던 클래스는 자바에서 기본적으로 제공해주는 Date 클래스를 주로 사용했었습니다. 보통의 경우 new Date().getMonth() 등과 같이 사용하곤 했었습니다. 헌데, Naver D2 글 하나를 본 후로 Date 클래스의 사용을 자중하기로 하였습니다.

Date 클래스의 단점을 한번 나열해보겠습니다.

1. 애매한 월 계산

컴퓨터의 기초상 0부터 시작하기 때문에 JANUARY(1월)이 0부터 시작합니다.

/**
 * Value of the {@link #MONTH} field indicating the
 * first month of the year in the Gregorian and Julian calendars.
 */
public final static int JANUARY = 0;

/**
 * Value of the {@link #MONTH} field indicating the
 * second month of the year in the Gregorian and Julian calendars.
 */
public final static int FEBRUARY = 1;

/**
 * Value of the {@link #MONTH} field indicating the
 * third month of the year in the Gregorian and Julian calendars.
 */
public final static int MARCH = 2;

사람이 아는 월수로 환산하려면 1을 더해야합니다. 이 부분은 다소 이상합니다.

2. 불변 객체가 아님

자바의 기본 날짜 클래스는 immutable(불변) 객체가 아닙니다. Date와 Calendar를 혼용해서 사용하고 다른 코드에서 공유해서 사용한다면 어느 한 쪽에서 변경된 값이 다른 부분에 영향을 줄 수 있습니다. 

Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, 5 - 1);

3. 그 외에 애매한 계산법, 일관성 없는 요일 상수 등

Calendar.get(Calendar.DAY_OF_WEEK) 함수에서 반환된 요일은 int 값인데 일요일이 1로 표현됩니다. 하지만 calendar.getTime() 메서드로 Date 객체를 얻어와 Date.getDay() 메서드로 요일을 구하면 일요일은 0입니다. 즉, 두 개의 클래스 사이에 요일 지정값에 일관성이 없습니다. 

@Test
@SuppressWarnings("deprecation")
public void shouldGetDayOfWeek() {  
    Calendar calendar = Calendar.getInstance();
    calendar.set(2014, Calendar.JANUARY, 1);

    int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
    assertThat(dayOfWeek).isEqualTo(Calendar.WEDNESDAY);
    assertThat(dayOfWeek).isEqualTo(4);
    Date theDate = calendar.getTime();
    assertThat(theDate.getDay()).isEqualTo(3);
}

그리고 연산을 진행할 때도 int값을 차감하는 것이 문제라고 볼 수 있습니다. 

calendar.add(Calendar.SECOND, 3);

그래서 이 부분을 개선하고 싶었기 때문에 글 제목에 적힌 3개의 클래스(LocalDate, LocalTime, LocalDateTime)를 적용해보기로 하였습니다.

먼저 클래스 명에서도 나와있다시피 LocalDate는 날짜와 관련된 함수를, LocalTime은 시간과 관련된 함수 정보를 가지고 있고 LocalDateTime은 이 두개를 모두 포괄하는 역할을 하고 있습니다. 경우에 따라 날짜값만 필요한 DTO의 경우는 LocalDate을 가지고 있으면 되겠습니다. 

(모든 클래스를 인터페이스 형태로 바꾼 것일뿐 실제 소스는 이렇게 생기지 않았습니다.)

LocalDate.class

public interface LocalDate {
   public static LocalDate now(); // 현재 날짜 값을 가져옵니다.
   public static LocalDate now(ZoneId zone); // 해당 TimeZone의 값의 날짜값을 가져옵니다.
   public static LocalDate of(int year, int month, int dayOfMonth); // 해당 날짜의 클래스를 가져옵니다.
   public static LocalDate parse(CharSequence text); // 문자열 값을 클래스로 변환해줍니다.
   public static long getLong(TemporalFile field); // new Date().getTime() 과 같은 역할을 하는 unix timestamp 값을 가져옵니다.
   
   // 순서대로 년/월/일 값을 가져옵니다.
   public int getYear();
   public int getMonth();
   public int getDayOfMonth();
   
   // 각각 현재 나온 클래스값에 년/월/일 값을 증감해줍니다.
   public LocalDate plusYears(long yearsToAdd);
   public LocalDate plusMonths(long monthsToAdd);
   public LocalDate plusDays(long daysToAdd);
   public LocalDate minusYears(long yearsToSubtract);
   public LocalDate minusMonths(long monthsToSubtract);
   public LocalDate minusDays(long daysToSubtract);
   
   public Stream<LocalDate> datesUntil(LocalDate endExclusive); // 해당 일자로부터 loop를 생성할 수 있도록 Stream 객체를 제공해줍니다.
   
   // 제일 많이씀
   public String format(DateTimeFormatter formatter); // 년월일 양식을 특정 포맷으로 반환해줍니다. 
   
   ...
}

LocalTime.class

public interface LocalTime {
   public static LocalTime now(); // 현재 시간 값을 가진 로컬타임 클래스를 가져옵니다.
   public static LocalTime now(ZoneId zone); // 해당 TimeZone의 로컬타임 클래스를 가져옵니다.
   
   // 시분 또는 시분초 값을 가진 클래스를 가져옵니다.
   public static LocalTime of(int hour, int minute);
   public static LocalTime of(int hour, int minute, int second);
   
   public static LocalTime parse(CharSequence text, DateTimeFormatter formatter); // 문자열을 특정 formatter의 규격에 맞게 클래스로 변환해줍니다.
   
   public long getLong(TemporalField field); // new Date().getTime()과 같은 unix timestamp 값을 가져옵니다.
   
   // 여기도 시분초를 증감할 수 있는 메소드가 있습니다.
   public LocalTime plusHours(long hoursToAdd);
   public LocalTime plusMinutes(long minutesToAdd);
   public LocalTime plusSeconds(long secondsToAdd);
   public LocalTime minusHours(long hoursToSubtract);
   public LocalTime minusMinutes(long minutesToSubtract);
   public LocalTime minusSeconds(long secondsToSubtract);
   
   // 시간을 비교합니다.
   public boolean isAfter(LocalTime other); 
   public boolean isBefore(LocalTime other);
   
   // format을 가진 문자열로 변환합니다.
   public String format(DateTimeFormatter formatter);
   
   ...
}

LocalDateTime.class

아래에 있는 메소드들은 위와 구조가 비슷합니다. 

public interface LocalDateTime {
   public static LocalDateTime now();
   public static LocalDateTime now(ZoneId zone);
   
   public static LocalDateTime of(int year, Month month, int dayOfMonth, 
      int hour, int minute, int second);
      
   public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter);
   
   public long getLong(TemporalField field);
   
   public int getYear();
   public int getMonthValue();
   public Month getMonth();
   public int getDayOfMonth();
   public int getDayOfYear();
   public DayOfWeek getDayOfWeek();
   public int getHour();
   public int getMinute();
   public int getSecond();
   public LocalDateTime plus(long amountToAdd, TemporalUnit unit);
   public LocalDateTime minus(long amountToSubtract, TemporalUnit unit);
}

그리고 아래 있는 메소드들은 LocalDate, LocalTime으로 변환해줍니다.

public LocalDate toLocalDate();
public LocalTime toLocalTime();

사용 방법

1. 시간 기본 get

// 2021년 6월 22일 16시 30분 25초 생성 (화요일)
LocalDateTime ldt = LocalDateTime.of(2021, Month.JUNE, 22, 16, 30, 25);

ldt.getYear(); // 2021
ldt.getMonth(); // Month.JUNE
ldt.getMonthValue(); // 6
ldt.getDayOfWeek(); // TUESDAY
ldt.getDayOfMonth(); // 22
ldt.getHour(); // 16
ldt.getMinute(); // 30
ldt.getSecond(); // 25

2. 변환

// 2021년 6월 22일 16시 30분 25초 생성 (화요일)
LocalDateTime ldt = LocalDateTime.of(2021, Month.JUNE, 22, 16, 30, 25);

// 2021년 6월 22일
LocalDate date = ldt.toLocalDate();

// 16시 30분 25초
LocalTime time = ldt.toLocalTime();

// 문자열 변환
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = ldt.format(dateTimeFormatter); // 2021-06-22 16:30:25

// long 변환
ldt.getLong(); // 1624379425000

3. 증감

// 2021년 6월 22일 16시 30분 25초 생성 (화요일)
LocalDateTime ldt = LocalDateTime.of(2021, Month.JUNE, 22, 16, 30, 25);

// 2021년 6월 22일
LocalDate date = ldt.toLocalDate();
date.plusDays(3) // 2021년 6월 25일
   .getDayOfMonth(); // 25

// 16시 30분 25초
LocalTime time = ldt.toLocalTime();
time.minusMinutes(11) // 16시 19분 25초
   .getMinute(); // 19
   
// plus(), minus() 쓰기
ldt.plus(1, ChronoUnit.MONTH); // 2021년 7월 22일
ldt.minus(7, ChronoUnit.HOURS); // 9시 30분 25초

4. 비교

// 2021년 6월 22일 16시 30분 25초 생성 (화요일)
LocalDateTime ldt = LocalDateTime.of(2021, Month.JUNE, 22, 16, 30, 25);
LocalDateTime ldt2 = LocalDateTime.of(2021, Month.JUNE, 22, 16, 30, 25);

ldt.isAfter(LocalDateTime.now()); // false, 현재가 미래이므로
ldt.isBefore(LocalDateTime.now()); // true
ldt.isEqual(LocalDateTime.now()); // false
ldt.isEqual(ldt2); // true

// 2021년 6월 19일
LocalDate date = ldt.toLocalDate().minusDays(3);
ldt.isAfter(date); // true

Stream<LocalDate> streams = ldt.toLocalDate().datesUntil(LocalDate.of(2021, Month.JULY, 1));
     streams.forEach(date -> {
            System.out.println(date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
     });

// 결과
2021-06-22
2021-06-23
2021-06-24
2021-06-25
2021-06-26
2021-06-27
2021-06-28
2021-06-29
2021-06-30
2021-07-01

 

반응형
댓글
공지사항