본문 바로가기
Development/Java

자바 날짜 관련 코딩 시 Date와 Calendar를 쓰지 마세요!

by Nahwasa 2023. 2. 6.

 

결론 : 날짜 관련 코드짤 때 Date, Calendar 쓰지 마세요!

 

Date(jdk 1.0), Calendar(jdk 1.1) 클래스의 문제점

- 불변 객체가 아님.

setter가 존재하므로 Calendar나 Date 객체가 여러 객체에서 공유되면 한 곳에서 바꾼 값이 다른 곳에 영향을 미칠 수 있음.

 

- int 상수 필드의 남용.

CalendarSECOND 같은 상수 필드때문에, 여기에 Calendar.JUNE 같은 엉뚱한게 들어가도 컴파일 시점에 확인할 방법이 없음.

 

- 헷갈리는 월 지정.

Date 클래스에서 1월을 0부터 표현하며, Calendar에서도 마찬가지. 따라서 1582년 10월 4일은 다음과 같이 작성해야 하며 당연히 휴먼에러가 많이 나옴.

calendar.set(1582, 9, 4);

 

- 일관성 없는 요일 상수

Date와 Calendar 두 클래스 사이에 요일 지정값에 일관성이 없음.

calendar.set(2014, Calendar.JANUARY, 1);

calendar.get(Calendar.DAY_OF_WEEK); // 4 (=Calendar.WEDNESDAY)
calendar.getTime().getDay(); // 3 (getTime()시 Date 객체 획득 후 getDay() 호출)

 

- Date와 Calendar의 불편한 역할 분담

jdk 1.0에서는 Date가 날짜 연산을 지원하는 유일한 클래스였음. jdk 1.1에서 Calendar가 추가되면서 Date의 많은 기능이 deprecated되었지만, 날짜 연산을 위해 Calendar객체를 생성하고 다시 Calendar에서 Date를 생성하는 등 불필요한 중간 객체가 생성됨.

// 흔히 "자바 yyyymmdd" 처럼 검색하면 구글 상단 검색에 뜨는 자료들의 형태

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar calendar = Calendar.getInstance(); //Calendar
String today = sdf.format(calendar.getTime());// getTime()의 결과는 Date 객체

 

- 등등

시간대의 ID를 “Asia/Seoul” 대신 “Seoul/Asia”로 해도 오류가 발생하지 않음.

java.sql.Date클래스는 상위 클래스인 java.util.Date와 클래스명이 동일함.

 


 

기존 Date, Calendar 대체 라이브러리들 (java 8 이전)

- joda-time, Time and Money Code Library, CalendarDate, date4j 등

- 클린코드 16장에 나온 SerialDate도 이런 것 중 하나.

- 이하 jdk8에서 개선된 날짜 API는 joda-time에 가장 많은 영향을 받음.

- 현재 jdk8 이상을 쓴다면 얘네도 쓸 이유 없음.

 


 

jdk 8 에서 개선된 날짜 API

- 전부 불변 객체.

 

- 종류

  • [prefix]Date : 날짜정보 (2023-02-05)
  • [prefix]Time : 시간정보 (20:46:12)
  • [prefix]DateTime : 날짜+시간 (2023-02-05 20:46:12)

 

- [prefix]

  • Local : 타임존 개념이 필요없는 날짜 정보. 일반적으로 사용.
  • Offset : (Local+ZoneOffset) UTC 기준으로 시간 표현. 우리나라면 UTC +09:00. DB나 네트워크 통신 적합
  • Zoned : (Offset+ZoneRegion) Timezone (Asia/Seoul) 포함. Asia/Seoul과 Asia/Tokyo 모두 UT+9. DST(Daylight Saving Time; 일광 절약 시간제) 정보 포함. 글로벌 서비스에 적합.
  • prefix는 예를들어서 [prefix]Date는 LocalDate, OffsetDate, ZonedDate가 있음을 나타냄.

 

- 사용 예시

/**
 * LocalDate
 */
// 현재 날짜 기준(now) 생성
LocalDate localDate = LocalDate.now();  // 2023-02-05 (현재 날짜)
localDate.getYear();    // 2023
localDate.getMonthValue();  // 2
localDate.getDayOfMonth();  // 5

// 원하는 날짜 기준 생성
LocalDate.of(2023, 2, 5);  // 2023-02-05 (원하는 날짜 넣기)

// String 형태의 날짜 기준 생성
LocalDate.parse("2023-02-05");
LocalDate.parse("20230205", DateTimeFormatter.BASIC_ISO_DATE);  // BASIC_ISO_DATE은 날짜쪽에 대해서만 사용 가능.

// 원하는 포맷으로 String 출력
LocalDate.of(2023, 2, 5).format(DateTimeFormatter.ISO_LOCAL_DATE);  // 2023-02-05 (LocalDate toString 시 default 형태)
LocalDate.of(2023, 2, 5).format(DateTimeFormatter.BASIC_ISO_DATE);  // 20230205
LocalDate.of(2023, 2, 5).format(DateTimeFormatter.ofPattern("yyMMdd"));  // 230205

/**
 * LocalTime
 */
// 현재 시각 기준 생성
LocalTime localTime = LocalTime.now();
localTime.getHour();    // 22
localTime.getMinute();  // 29
localTime.getSecond();  // 46
localTime.getNano();    // 663539100

// 원하는 시각 기준 생성
localTime.of(22, 32);   // 22:32
localTime.of(22, 32, 10); // 22:32:10
localTime.of(22, 32, 10, 1234);  // 22:32:10.000001234

// String 형태의 날짜 기준 생성
LocalTime.parse("22:32:10.000001234");  // 22:32:10.000001234

// 원하는 포맷으로 String 출력 (BASIC_ISO_DATE 포맷은 사용 불가)
LocalTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME);
LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));  // 22:37:32

/**
 * LocalDateTime
 */
// 현재 날짜 기준(now) 생성
LocalDateTime localDateTime = LocalDateTime.now();  // 2023-02-05T22:38:32.033365200
localDateTime.getYear();        // 2023
localDateTime.getMonthValue();  // 2
localDateTime.getDayOfMonth();  // 5
localDateTime.getHour();        // 22
localDateTime.getMinute();      // 38
localDateTime.getSecond();      // 32
localDateTime.getNano();        // 33365200

// 원하는 날짜 기준 생성
LocalDateTime.of(2023, 2, 5, 22, 38, 32);  // 2023-02-05T22:38:32

// String 형태의 날짜 기준 생성
LocalDateTime.parse("2023-02-05T22:38:32"); // 2023-02-05T22:38:32
LocalDateTime.parse("2023-02-05 22:38:32", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); // 2023-02-05T22:38:32

// 원하는 포맷으로 String 출력
LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);  // 2023-02-05T22:44:05.5575867
LocalDateTime.now().format(DateTimeFormatter.BASIC_ISO_DATE);  // 20230205
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));  // 2023-02-05 22:44:29

/**
 * 날짜 관련 연산
 */
LocalDate.now().plusDays(1).getDayOfWeek();  // MONDAY (now()는 2023-02-05로 일요일임.)
LocalTime.now().minusHours(1).getHour();     // 21 (현재 오후 10시임)
LocalDateTime.now().minusYears(5);  //2018-02-05T22:51:04.753285700

LocalDate.now().isAfter(LocalDate.parse("2023-02-04"));  // true (현재는 2023-02-05)
LocalDateTime.now().isBefore(LocalDateTime.now().plusDays(1)); // true

 

- 스프링부트에서 사용 예시

@RestController
@RequestMapping("/status")
public class StatusCheckController {

    @GetMapping
    public ResponseEntity<String> serverStatusCheck(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
        return ResponseEntity.ok(date.format(DateTimeFormatter.BASIC_ISO_DATE));
    }
}

 

// Repository (data jpa) 에서 start~end에 가입된 Member 리스트 리턴
List<Member> findByRegisterDateBetween(LocalDate start, LocalDate end);

 


 

references

https://d2.naver.com/helloworld/645609

https://jeong-pro.tistory.com/163

https://www.baeldung.com/java-zoneddatetime-offsetdatetime

https://sujl95.tistory.com/86

https://www.daleseo.com/java8-local-date-time/

 

댓글