JS 와 Python 에서 시간을 다룰 때 주의할 점

Einere·2022년 12월 24일
2

시간 포멧 맞추기

FE 작업을 하면서, API 응답 값을 보다가 의문이 들었다.

created_at: "2022-04-18T18:36:31.680000" // 타임존 정보가 없다!
end_date: "2023-11-01"
release_date: "2021-11-02"

시간에 관한 데이터의 포멧이 달랐다. 특히, 모든 속성이 타임존에 대한 정보가 없어, 추후 확장할 때 문제가 발생할 여지가 많았다. 그래서 나는 BE 개발자분과 시간을 ISO 문자열로 다룰지, 타임스탬프로 다룰지 정하기 위해 얘기를 나누었다.

그런데 도중에 A님이 난입을 하셔서 말을 얹으셨다.

타임존

아 근데 timestamp로 보내면 tz정보가 없어서 일반적인 js 라이브러리에서는 localtime으로 자동변환해버려요. 그래서 timestamp보다는 tz정보 있는 isostring이 낫긴할거에요. B(사내 FE 서비스)도 그래서 좀 시간추가로 더하고 빼는게 들어간게 있을거에요. js라이브러리에서 timestamp와 tz를 동시에 받는게 있나요?

A님이 슬랙 스레드에 이런 댓글을 주셨다. 내가 알던 사실과 다른 부분들이 있어 의문이 들었다.

'정말로 JS의 타임스탬프는 타임존 정보가 없는 것인가?' ‘로컬 타임존으로 자동변환을 하는가?’ 'ISO 문자열이 더 나은 것인가?' '이 스크린샷은 왜 문제 상황을 야기하고 있는 것일까?'

A님의 의견

A님이 2022.04.27. 20:30:39 KST 즈음 뽑은 UNIX timestamp 값을 브라우저 콘솔에 출력해보았다.

[A님이 2022.04.27. 20:30:39 KST 즈음 뽑은 UNIX timestamp 값을 브라우저 콘솔에 출력해보았다.]

A님이 스샷을 하나 올리면서, timestamp 는 이런 시차 이슈가 있다고 말씀하셨다. 하지만 나는 타임스탬프는 항상 UTC(GMT) 기준이기 때문에 위 스크린샷과 같은 시차가 발생할 여지가 없다고 알고 있었다.

그래서 A님이 주신 값이 정확히 어떤 시간인지 알아보니 다음과 같았다.

A님이 파이썬으로 타임스탬프를 이상하게 뽑아서, KST 20:30:39가 아닌, KST 11:30:39를 나타내는 타임스탬프(1651026639)가 뽑힌 것 같다.

당시 A님이 어떻게 타임스탬프를 뽑았는지 물어보지 못했지만, 타임스탬프 값이 잘못되었다는 것은 확실했다.

나는 파이썬에서 시간을 어떻게 다루는지 궁금해져서 조금 찾아보았다.

파이썬이 시간을 다루는 방법

Python 에서 datetime 객체는 자체적으로 타임존 정보(tzinfo)를 가지고 있다. 여기서 주의해야 할 점이, 나이브 객체와 어웨어 객체가 존재한다는 점이다.

🚨 타임존 정보 포함 유무에 따라, 나이브(naive) 객체와 어웨어(aware) 객체로 구분할 수 있다. 이에 따라, 시간을 처리할 때 타임존을 주의해야 한다.

타임존 정보가 없는 경우, 파이썬은 해당 시간을 로컬 시간대를 기준으로 해석한다. 반면에 타임존 정보가 있는 경우, 해당 시간대를 기준으로 해석한다. 이렇듯, 타임존 정보에 따라 개발자의 추가적인 오프셋 보정 유무 및 해석의 여지가 달라지므로 타임존 정보를 명시하는 것이 중요하다.

예를 들어, 한국시로 "1970년 1월 1일 0시 0분 0초" 라는 값을 서버에서 클라이언트로 내려주는 상황을 가정해보자. 만약 BE 개발자가 tzinfo 에 대해 잘 모른다면(혹은 신경쓰지 않는다면) 다음과 같이 코딩할 것이다. (서버 위치는 편의상 한국이라고 가정하자.)

# python
>>> d = datetime(1970, 1, 1) # BE 개발자가 tzinfo 설정하는 것을 까먹었다면..?
>>> d.tzinfo == None
True
>>> d.isoformat()
'1970-01-01T00:00:00' # Z 혹은 offset 붙지 않은 것에 주의!

이제 '1970-01-01T00:00:00' 를 클라이언트에 내려주면 FE 개발자는 이를 파싱해서 사용할 것이다. 운좋게 개발자가 한국에 살고 있다면 다음과 같이 올바른 값을 본다.

// javascript
> const d = new Date('1970-01-01T00:00:00');
> d.toString();
'Thu Jan 01 1970 00:00:00 GMT+0900 (한국 표준시)' // 다행히 한국시로 1970년 1월 1일 0시가 나온다.

그런데 만약 영국 런던에 사는 개발자라면?

> const d = new Date('1970-01-01T00:00:00')
> d.toString();
'Thu Jan 01 1970 00:00:00 GMT+0100 (그리니치 표준시)' // 분명 한국 기준으로 1970년 1월 1일 0시 0분 0초를 보내주었는데..?

영국에 사는 개발자는 8시간을 뺀 값인 "1969년 12월 31일 16시"로 나와야 하는데 한국 개발자가 보는 것과 동일한 1970년 1월 1일 0시로 보이게 된다.

이제, 서버에서 타임존 정보를 포함한 시간 문자열을 내려준다고 가정해보자.

>>> d1 = datetime(1970, 1, 1, tzinfo=timezone(timedelta(hours=9)))
>>> d1.isoformat()
'1970-01-01T00:00:00+09:00'
// 한국 유저
> const d = new Date('1970-01-01T00:00:00+09:00')
> d.toString();
'Thu Jan 01 1970 00:00:00 GMT+0900 (한국 표준시)'

// 영국 유저
> const d = new Date('1970-01-01T00:00:00+09:00')
> d.toString();
'Wed Dec 31 1969 16:00:00 GMT+0100 (그리니치 표준시)'

이제 한국 유저든 영국 유저든, 세계 어디서도 동일한 시간을 보게 된다.

결국, 시간을 다룰 때 나타내고자 하는 시간과 그에 맞는 타임존 둘 다 명확하게 지정해주는 것이 좋다.

확장 가능성은?

사실 BE 코드와 DB에 저장되는 모든 시간 관련 데이터에 타임존이 지정되어있지 않기 때문에, 타임스탬프 혹은 ISO 문자열로 해결할 수 있는 문제가 아니였다.

모든 데이터가 KST 기준으로 생성된 값이기 때문에, DB를 모두 갈아엎지 않는 한 프론트에서 모든 값을 KST로 해석하고 다루는 수 밖에 없었다... 😭

물론 서비스 초창기부터 국제 진출을 고려하고 개발하지는 않을 수 있지만, 적어도 타임존은 명시해서 사용하는게 좋을 것 같다.

번외로 자바스크립트는 어떻게 동작하는지 찾아보았다.

Date 의 동작 방식

Date 객체의 중심을 구성하는 시간 값은 UTC 기준이지만, 날짜와 시간 등 구성 요소를 가져오는 메서드는 모두 현지(호스트 시스템의 위치)의 시간대를 사용한다는 것을 기억해야 합니다.

MDN Date 문서의 내용이다. MDN 문서를 보고 여러가지 테스트 결과, 아래와 같이 동작한 다는 것을 알게 되었다.

💡 UNIX timestamp는 항상 GMT(UTC) 기준이다!

  1. Date 인스턴스는 내부적으로 UNIX timestamp 로 상태를 가진다.
  2. Date 인스턴스 생성 시, 인자에 타임존 정보가 없다면 로컬 타임존으로 간주하며, 명시적인 타임존 정보가 있다면 해당 시간대로 간주한다.
  3. Date 인스턴스는
    1. 일반 getter 함수에 대해, 로컬 타임존 보정을 적용하여 반환한다. (즉, 타임존 오프셋 만큼 더한 후 뱉는다.)
    2. UTC getter 함수에 대해, 로컬 타임존 보정을 적용하지 않고 반환한다.

참고

왜 내가 작성한 JavaScript Date 코드가 서버에서는 다르게 보이는 거죠?

profile
제품을 만드는, 지속가능한 웹 개발자를 지향합니다.

0개의 댓글