> ## Content Index
> Fetch the complete content index at: https://www.popit.kr/llms.txt
> Use this file to discover other available public pages before exploring further.

# REST API 날짜/시간 표현 정하기
- URL: https://www.popit.kr/rest-api-datetime-representation/
- Published: 2018-03-27T23:00:00.000Z
- Updated: 2018-03-29T01:39:40.000Z
- Author: 조 광희
- Tags: #소프트웨어, REST API, Spring Boot, #아키텍처 및 설계, Spring, ISO8601

2달전 제 뒤에 앉아았는 개발자님께서 왜 알아보기 어렵게 날짜를 숫자로 사용해야하냐고 의문을 가진 적이 있다. API를 처음 구성할 때 참고했던 API가 사용했다는 이유로, spring 400에러 응답에서도 long 형태의 시간표현을 한 사례, Timezone과 DB처리에서 장점이 있다는 이유로 적용했던 Unix Time형태의 날짜 표현방식이 문제가 될 수도 있다는 생각이 들었다. 그렇게 불편하다면 한번 바꿔볼까? 하는 생각과 함께 고민을 시작했다.

![clock](https://www.popit.kr/content/images/wp/2018/03/clock.jpeg)

API 설계를 위해 REST에 대해 학습하는 과정에서 우리는 URI, Method, Http Status 에만 집중한다. 하지만 API를 개발하고 사용하는 목표는 결국 데이터를 주고 받는데 있기 때문에 데이터가 담기는 Parameter, Request/Response Body의 형태를 정하고 Interface로 약속해야 한다.

- 문자열
- 숫자
- 논리(Boolean)
- *날짜 / 시간*

보통 데이터를 표현하기 위해서는 위의 4가지 형태가 필요하고 그 중에서 날짜와 시간은 프로그래밍 언어마다 그 구성형태가 다르다. json 응답에서 이 날짜와 시간의 표현방법을 고민하고 적용방안에 대해 이야기하고자 한다.

---

## Unix Time vs ISO 8601

![timestamp](https://www.popit.kr/content/images/wp/2018/03/timestamp-600x210.png)

API를 위한 시간표현에서 가장 인기있는 것은 [Unix Time](https://en.wikipedia.org/wiki/Unix%5Ftime?ref=popit.kr)과 [ISO-8601](https://en.wikipedia.org/wiki/ISO%5F8601?ref=popit.kr)이고 이 외에도 다양한 방법이 존재하지만 자신들만의 형태를 가질 것이 아니라면 의미가 없기 때문에 이 두가지만 고려했다.

### 표현방식에 따른 가독성과 변환

Unix Time은 **1970-01-01 UTC 에서의 경과시간을 초(or 밀리초\[1\])로 환산해서 숫자**로 나타내고 ISO 8601은 **사람이 인식할 수 있는 문자**로 나타낸다.  

```js
{
  "unix_time":1521739975,
  "unix_time_milisecond":1521739975123,
  "iso-8601":"2018-03-22T17:32:55+09:00",
  "iso-8601_timestamp":"2018-03-22T17:32:55.123+09:00"
}
```

Unix Time은 사람이 인지할 수 있는 형식이 아니기 때문에 바로 이해하기 어려우나 ISO 8601은 즉시 인지가 가능하다. 반면 프로그램 상에서는 각 언어와 환경마다 대부분 가지고 있는 Date 처리에서 unix time은 즉시 변환이 가능지만 ISO 8601은 언어와 버전에 따라서 지원되지 않는 경우가 존재한다.  
javascript를 예로 [ECMAScript 5](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global%5FObjects/Date/parse?ref=popit.kr)부터 Date에서 RFC2822 또는 ISO 8601를 지원하는데 IE9 미만에서는 ECMAScript 5를 하지 못하기 때문에 바벨과 같은 트랜스파일러를 사용하는 것과 같은 주의가 필요하다. 그나저나 아직도 IE7,8을 대응하냐고 하면..

유저테스트와 문서화에서도 이 가독성이 영향을 주는데, 동료인 김창묵님의 도움으로 Swagger에 날짜변환 Tool을 임베드해 그 단점을 보완하며 사용하고 있지만 사실 입력할 때마다 불편함을 느낀다.

![swagger](https://www.popit.kr/content/images/wp/2018/03/swagger-600x329.png)

### 타임존과 정확성

국내에서만 제공하는 서비스를 하고 있다면 그다지 중요한 부분이 아니지만 region에 따라서 시간대가 다른 서비스를 하고 있을 경우 ISO는 약점으로 작용할 수 있다.  

```js
{
  "local":"2018-03-23T13:10:10",
  "utc":"2018-03-23T13:10:10Z",
  "utc_offset":"2018-03-23T13:10:10+09:00"
}
```

Unix Time의 경우 UTC로부터의 Offset이기 때문에 타임존과 상관없이 일정한 값을 가지므로 그 표현과 사용에 큰 제약이 없다. IOS 8601은 위와 같이 명시하지 않으면 로컬타임으로 인식하는데 **다른 시간대를 사용하는 지역에 동시에 서비스를 하거나 서머타임을 적용하는 경우 시간에 대한 혼동**이 올 수 있다. 입력받은 시간을 다른 타임존으로 인식해서 사용해야 하는 경우에도 단순한 덧셈, 뺄셈이지만 시간 확인에 대한 어려움을 경험하게 된다.

### 제한된 정보표현

시간 정보가 필요없고 날짜만 사용하는 경우 Unix Time은 표현법이 애매해진다. 무조건 시간의 정보를 포함해서 사용해야하기 때문에 이것이 날짜만 포함된 데이터인지 시간과 함께 있는 데이터인지 구분할 수 없다. ISO는 시간영역을 제거하면 되기 때문에 년/월/일, 년/월, 년 등의 제한된 정보를 간편하게 표현하는데 좋다.  

```js
{
  "unix_time_date":1521763200000,
  "iso_8601_date":"2018-03-23",
  "iso_8601_month":"2018-03",
  "iso_8601_year":"2018"
}
```

---

## Spring Boot 에서 사용방법

그래서 그 두가지를 실제로 개발하려면 뭘 어떻게 해야할까? 개발환경에 대한 영향을 보기 위해 Spring Boot를 기반으로 두 방식의 사용방법을 확인했다.  
기본적으로 LocalDate를 사용해서 json 응답하면 지나치게 친절하고 장황한 왜 이런 응답을 할까 싶은 응답을 보게 된다. 이 응답과 Parameter로 시간을 받을 때 처리를 바꿔보자.  

```js
{
  "localDate":{
    "year":2018,
    "month":"JANUARY",
    "era":"CE",
    "dayOfYear":1,
    "dayOfWeek":"MONDAY",
    "leapYear":true,
    "dayOfMonth":1,
    "monthValue":1,
    "chronology":{
      "id":"ISO",
      "calendarType":"iso8601"
    }
  } 
}
```

### Parameter 처리

```java
@GetMapping("/orders")
public ResponseEntity<List> getOrdersByDate(
    @RequestParam("date")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) {

    // Do
}
```

DateTimeFormat으로 요청 Parameter의 형태를 지정하면 별다른 변환처리 없이 ISO 형태의 데이터 수신이 가능하다.  

```java
@GetMapping("/orders")
public ResponseEntity<List> getOrdersByDate(
    @RequestParam("date") long dateNumber) {
    
    LocalDate date = Instant.ofEpochMilli(dateNumber).atZone(ZoneId.systemDefault()).toLocalDate();
    // Do
}
```

반면 Spring 에서 제공하는 기능이 없는 Unix Time은 별도로 long을 LocalDate로 바꾸는 작업이 필요하다.

### Spring Boot 1.5 (ISO 8601)

1.5 버전에서는 Java 8의 Date/Time API를 처리하기 위해 아래의 작업이 필요하다.  

```xhtml
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
```

```yaml
dependencies { compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.9.4' }
```

```yaml
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
```

maven pom.xml 또는 Gradle에 위의 디펜던시를 추가하고 application.properties에 serialize 설정을 추가하면 예상할 수 있는 결과로 응답된다.  

```js
{  
  "localDate":"2018-01-01",
  "localTime":"10:24",
  "localDateTime":"2018-01-01T10:24:00",
  "zonedDateTime":"2018-01-01T10:24:00+09:00"
}
```

### Spring Boot 2.0 (ISO 8601)

2.0이 되면서 훨씬 간편해졌다. Java 8을 최소 버전으로 지원하면서 별다른 설정없이 아래의 응답을 제공한다.  

```js
{
  "localDateTime": "2018-01-01T10:24:00.445428",
  "offsetDateTime": "2018-01-01T10:24:00.445428+09:00",
}
```

### Unix Time 응답설정

```java
@JsonComponent
public class JsonConverter {
    public static class LocalDateTimeJsonSerializer extends JsonSerializer<LocalDateTime> {
        @Override
        public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            Date out = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
            jsonGenerator.writeNumber(out.getTime());
        }
    }

    public static class LocalDateTimeJsonDeserializer extends JsonDeserializer<LocalDateTime> {
        @Override
        public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
            Date in = new Date(jsonParser.getValueAsLong());
            return LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
        }
    }
}
```

Spring에서 처리해주는 부분은 없고 Json 응답의 serialize/deserialize 를 등록해서 전역적으로 변경하도록 지정하면 모든 LocalDateTime에 대해서 long 형으로 변환하고 long 데이터는 LocalDateTime 으로 받을 때 변환되어 받아진다.

---

## 글을 마치며 나름의 결론

Spring을 기반으로 이야기하다보니 각 표현방식의 장단점을 넘어 ISO 8601이 더 효과적으로 보인다. 하지만 API를 개발하는 개발자와 그것을 사용하는 클라이언트 간에 어떠한 형태로든 약속이 되어 있다면 그 방법이 일관된 커뮤니케이션을 위해서 효과적이라고 생각한다.

> 이미 정해진 인터페이스는 굳이 변경하지 말자

*저의 경우처럼 비록 2년동안 사용해왔던 표현형식이 있지만 각 부분들 담당하는 개발자와 시스템간에 변경에 대한 이해가 있거나 아직 API 구성을 시작하지 않은 상황이라면 Spring 환경에서는 ISO 8601을 추천하고 싶다.* 또한 Spring의 기본 제공되는 것 외에도 직접 Unix Time을 사용하도록 할 수 있으나 자신이 사용하는 기술진영과 프레임워크에서 주도하는 방식을 이용해서 설계하는 것이 향후 버전대응에도 유리할 것으로 기대한다.

인터페이스는 약속이다. 각자의 상황과 이해에 따른 합의된 선택을 하면 후회가 없지 않을까?

## 주석

\[1\]: javascript의 [Date](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global%5FObjects/Date/now?ref=popit.kr)는 milisecond 단위로 표현한다.