[Java] 날짜 포멧 라이브러리 그리고 어노테이션(@DateTimeFormat vs @JsonFormat) 정리

devdo·2022년 11월 14일
0

Java

목록 보기
53/56
post-thumbnail

자바8 Date와 Time API

자바 8에 새로운 날짜와 시간 API가 생긴 이유

  • 그전까지 사용하던 java.util.Date 클래스는 mutable하기 때문에 thead safe하지 않다.
  • 클래스 이름이 명확하지 않다. Date인데 시간까지 다룬다.
  • 버그 발생할 여지가 많다. (타입 안정성이 없고, 월이 0부터 시작한다거나..)
  • 날짜 시간 처리가 복잡한 애플리케이션에서는 보통 Joda Time을 쓰곤했다.

자바 8에서 제공하는 Date-Time API
JSR-310 스팩의 구현체를 제공한다.

주요 API

  • 기계용 시간 (machine time)인류용 시간(human time)으로 나눌 수 있다.

  • 기계용 시간은 EPOCK (1970년 1월 1일 0시 0분 0초)부터 현재까지의 타임스탬프를 표현한다.

  • 인류용 시간은 우리가 흔히 사용하는 연,월,일,시,분,초 등을 표현한다.

  • 타임스탬프는 Instant를 사용한다.

  • 특정 날짜(LocalDate), 시간(LocalTime), 일시(LocalDateTime)를 사용할 수 있다.

  • 기간을 표현할 때는 Duration (시간 기반)과 Period (날짜 기반)를 사용할 수 있다.

  • DateTimeFormatter를 사용해서 일시를 특정한 문자열로 포매팅할 수 있다.

  • 참고


자바8 이전에 Date API와 포멧 라이브러리 SimpleDateFormat 그리고 Calendlar를 살펴보자.

SimpleDateFormat

: String <-> Date

// String <-> Date 
Date nowDate = new Date();
System.out.println("포맷 지정 전 : " + nowDate);

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy년 MM월 dd일"); 
//원하는 데이터 포맷 지정
String strNowDate = simpleDateFormat.format(nowDate); 
System.out.println("포맷 지정 후 : " + strNowDate);
// 일반 현재 날짜 및 시간
//long 타입으로 System.currentTimeMillis() 데이터를 받아야합니다
long time = System.currentTimeMillis();
SimpleDateFormat dayTime = new SimpleDateFormat("yyyy.MM.dd hh:mm:ss E요일");
String str = dayTime.format(new Date(time));

// 24시 형태 현재 날짜 및 시간 
long time = System.currentTimeMillis(); 
SimpleDateFormat dayTime = new SimpleDateFormat("yyyy.MM.dd kk:mm:ss E요일");
String str = dayTime.format(new Date(time));

Calendar 활용

: String에서 <-> Calendar

SimpleDateFormat -> Calendar 클래스 -> String 를 이용하여 타입 변환을 할 수 있다.

✔ 주의사항)

년, 월, 일 -1 해줘야 한다!


날짜 계산

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DateAdd {
	public static void main(String[] args) throws Exception{
    
		String date = "20200801";
        
		//1년 후 날짜
		String addYear  = AddDate(date, 1, 0, 0);
        
		//1달 후 날짜
		String addMonth = AddDate(date, 0, 1, 0);
        
		//1일 후 날짜
		String addDay   = AddDate(date, 0, 0, 1);
        
		System.out.println(addYear);  //20210801
		System.out.println(addMonth); //20200901
		System.out.println(addDay);   //20200802
	}
	private static String AddDate(String strDate, int year, int month, int day) throws Exception {
		
        SimpleDateFormat dtFormat = new SimpleDateFormat("yyyyMMdd");
        
		Calendar cal = Calendar.getInstance();
        
		Date dt = dtFormat.parse(strDate);
        
		cal.setTime(dt);
        
		cal.add(Calendar.YEAR,  year);
		cal.add(Calendar.MONTH, month);
		cal.add(Calendar.DATE,  day);
        
		return dtFormat.format(cal.getTime());
	}
}

위의 예제와 같이 Calendar 객체의 add() 메서드를 사용하면 원하는 날짜를 계산할 수 있다.

만약 특정일 기준으로 원하는 날짜만큼 빼려면원하는 만큼의 년, 월, 일에 -(마이너스)를 붙여 add 메서드 파라미터에 세팅한다.

//year 년 전
cal.add(Calendar.YEAR,  -year);
//month 월 전
cal.add(Calendar.MONTH, -month);
//month 일 전
cal.add(Calendar.DATE,  -day);

Or

//위의 메소드에 적용하면 다음과 같이 파라메터를 세팅하면된다.
//일년 전
AddDate(date, -1, 0, 0);
//한달 전
AddDate(date, 0, -1, 0);
//하루 전
AddDate(date, 0, 0, -1);

활용 예시
https://github.com/mooh2jj/board_mybatis/blob/master/src/main/java/com/example/board_springboot/common/scheduler/FileCheckTask.java

    private String getFolderYesterDay() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DATE, -1);
        String str = sdf.format(cal.getTime());
        return str.replace("-", File.separator);
    }

LocalDateTime

: String <-> LocalDateTime

Request: Parsing 메서드 구현(String)

  • 최신 방식(업데이트)
    // String -> LocalDateTime
    // @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    // @JsonFormat -> POST 방식일때!
    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    @PastOrPresent // 현재까지 입력가능
    private LocalDateTime startAt;

    // @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
    @PastOrPresent // 현재까지 입력가능
    private LocalDateTime endAt;
  • 구 방식
   private String startDate;
   private String endDate;

    // Stirng -> LocalDateTime
    public LocalDateTime getStartDate() {
        if (StringUtils.hasText(startDate)) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
            return LocalDateTime.parse(startDate + "T00:00:00", formatter);
        }
        return null;
    }

    public LocalDateTime getEndDate() {
        if (StringUtils.hasText(endDate)) {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
            return LocalDateTime.parse(endDate + "T23:59:59", formatter);
        }
        return null;
    }

Response: T 붙이는 건 머야?

간혹 시간 필드에서 응답시 이런 값을 붙이면서 보내주는 경우가 있다.
이부분은 Date와 Time 구분값이라 생각해주면 된다!
단순 띄어쓰기에서 더 확실한 구분값을 짓기 위해서다.

pattern = "yyyy-MM-dd'T'HH:mm:ss"

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "Asia/Seoul")
    private LocalDateTime createdAt;

@PastOrPresent or @FutureOrPresent 는 머야?

이 어노테이션이 적용된 필드의 값은 현재 또는 과거(or미래)의 시점에 해당되는 날짜 또는 시간이어야 하는 validation이다!

@PastOrPresent 
private LocalDateTime startTime;

유효기간 체크(isBefore() & isAfter())

LocalDate a = LocalDateTime.of(2012, 6, 30, 12, 00);
LocalDate b = LocalDateTime.of(2012, 7, 1, 12, 00);
a.isBefore(b) == true   // b 이전이냐?
a.isBefore(a) == false
b.isBefore(a) == false
// 2. 쿠폰 유효기간 체크
 if(now.isBefore(formatterLocalDateTime(couponDto.getBeginDt())) ||
 	now.isAfter(formatterLocalDateTime(couponDto.getEndDt()))) {
           return CouponDto.CouponDetails.builder()
                  .couponStatus(CouponDto.CouponStatus.UNABLE)
                  .build();
      }

between (startDay: 00:00:00 ~ endDay: 23:59:59)

간격을 두고 사이에 있는 조회하고 싶으면 startDay에 00:00:00, endDay에 23:59:59 를 두고 설정해주어야 한다. 설정해주는 코드는 다음과 같다.

    public LocalDateTime getParsedSearchDate1() {
        return LocalDateTime.parse(searchDate1 + " 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    public LocalDateTime getParsedSearchDate2() {
        return LocalDateTime.parse(searchDate2 + " 23:59:59", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

Date <-> LocalDateTime

참고 - https://hianna.tistory.com/613

Date를 LocalDate 또는 LocalDateTime 객체로 변환하기 위해서 다음의 3단계를 거칩니다.

1) Date -> Instant
2) Instant -> ZonedDateTime
3) ZonedDateTime -> LocalDate, LocalDateTime

        // 날짜 변환
        // Date -> LocalDateTime
        Date date = new Date();

        LocalDateTime dateTime = date.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();

        System.out.println(dateTime);   // 2022-12-09T20:28:36.436

        // LocalDateTime -> Date
        LocalDateTime localDateTime = LocalDateTime.now();

        Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
        Date date1 = Date.from(instant);

        System.out.println(date1);      // Fri Dec 09 20:28:36 KST 2022

@JsonFormat vs @DateTimeFormat

스프링에서 어노테이션(@JsonFormat, @DateTimeFormat)을 이용한 JSON 객체 Date타입을 String으로 직렬화하는 어노테이션들이다. 둘이 비슷한 기능인데, 이번 기회에 정리해 볼려고 한다.

사용 설명에 앞서 @JsonFormat, @DateTimeFormat에 대해 정확한 정보를 찾다가 아래에 포스팅을 찾아는데 설명이 잘 되어있어서 들어가서 한번 정독하고 사용하면 더 도움이 될 듯 하다.

[참고] https://jojoldu.tistory.com/361

차이는?

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startDate;

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime endDate;
  • 서버 Get 요청 @ModelAttribute에는 @DateTimeFormat 사용
  • 서버 Post 요청 @RequestBody에는 JSON 객체를 @DateTimeFormat과 @JsonFormat 로 모두 사용 가능
  • 2개의 어노테이션 모두가 있으면 선언 시에는 @JsonFormat이 먼저 적용
  • 서버 응답 시 ResponseBody에서는 JSON으로 직렬화하는 @JsonFormat 만 적용
    Spring MVC에서는 @DateTimeFormat, Spring REST API시에는 @JsonFormat을 사용해야 되는 것 같은데?

의존성은?

Spring Boot 2.0에서는 JSR 310이 의존성 기본적으로 되어있음 그래서 아래와 같이 따로 의존성을 추가 할 필요가 없다.
compile('com.fasterxml.jackson.datatype:jackson-datatype-jsr310')


정리

@JsonFormat은 Jackson의 어노테이션 > 우선순위가 더 높아
@DateTimeFormat은 Spring의 어노테이션

  • (GET) @DateTimeFormat
  • (POST) @DateTimeFormat or @JsonFormat 사용하자


참고

날짜 어노테이션

profile
배운 것을 기록합니다.

0개의 댓글