FE 작업을 하면서, API 응답 값을 보다가 의문이 들었습니다.
created_at: "2022-04-18T18:36:31.680000" // 타임존 정보가 없다!
end_date: "2023-11-01"
release_date: "2021-11-02"
시간에 관한 각 데이터의 포멧이 달랐습니다. 특히 모든 값이 타임존 정보가 없어, 추후 확장할 때 문제가 발생할 여지가 많았습니다. 그래서 저는 BE 개발자분과 시간을 ISO 문자열로 다룰지, 타임스탬프로 다룰지 정하기 위해 얘기를 나누었습니다.
그러던 도중, AA님이 난입햐셔서 말을 얹으셨습니다.
아 근데 timestamp로 보내면 tz정보가 없어서 일반적인 js 라이브러리에서는 localtime으로 자동변환해버려요. 그래서 timestamp보다는 tz정보 있는 isostring이 낫긴할거에요. BB도 그래서 좀 시간추가로 더하고 빼는게 들어간게 있을거에요. js라이브러리에서 timestamp와 tz를 동시에 받는게 있나요?
AA님이 위와 같이 말을 해주셨습니다만, 제가 알던 사실과 다른 부분들이 있어 의문이 들었습니다.
'정말로 타임스탬프는 타임존 정보가 없는 것인가?' ‘로컬 타임존으로 자동변환을 하는가?’ 'ISO 문자열이 더 나은 것인가?'

[AA님이 2022.04.27. 20:30:39 (KST) 즈음 뽑은 UNIX timestamp 값을 JS 콘솔에 출력해보았다.]
AA님이 스샷을 하나 올리면서, 타임스탬프는 이런 시차 이슈가 있다고 말씀하셨습니다. 하지만 저는 타임스탬프는 항상 UTC(GMT) 기준이기 때문에 위와 같은 시차가 발생할 여지가 없다고 알고 있었습니다. 그래서 AA님이 주신 값이 정확히 어떤 시간인지 알아보니 다음과 같았습니다.

[1651026639 타임스탬프가 의미하는 시간.]
저는 AA님이 단순히 타임스탬프를 이상하게 뽑아서, KST 20:30:39가 아닌, KST 11:30:39를 나타내는 타임스탬프(1651026639)가 뽑힌 것으로 추측했습니다.
예를 들어 의도적으로 시간을 보정한 뒤, 타임스탬프를 뽑았을 수도 있습니다.
# 당시 시간을 재현하기 위해 의도적으로 파라미터를 설정.
kst_datetime = datetime(2022, 4, 27, 20, 30, 39)
# 💥 의도적으로 -9h
utc_like = kst_datetime - timedelta(hours=9)
print(utc_like.timestamp()) # 1651026639.0
실제로 2022-04-27T20:30:39 KST 의 타임스탬프 값은 1651059039 이며, 이 값은 1651026639 와 9시간 만큼 차이가 납니다. 따라서 AA님이 주신 값이 2022.04.27. 20:30:39 KST 가 아닌, 2022.04.27. 11:30:39 KST 임을 알 수 있었습니다.
아쉽게도 당시에는 AA님이 정확히 어떻게 시간을 뽑았는지 물어보지 못했기 때문에, 무엇이 이런 혼란을 야기하는지 알 수 없었습니다.
저는 자바스크립트가 시간을 다루는 방법이 잘못된 것이 아니라는 믿음이 있었기에, 파이썬에서는 시간을 어떻게 다루는지 궁금해져서 조사를 했습니다.
파이썬에서 datetime 객체는 자체적으로 타임존 정보(tzinfo)를 가지고 있습니다. 여기서 주의해야 할 점은 나이브 객체와 어웨어 객체가 존재한다는 점입니다.
🚨 타임존 정보 포함 유무에 따라, 나이브(naive) 객체와 어웨어(aware) 객체로 구분할 수 있습니다. 이에 따라, 시간을 처리할 때 타임존을 주의해야 합니다.
타임존 정보가 없는 경우, 파이썬은 해당 시간을 로컬 시간대를 기준으로 해석합니다. 반면에 타임존 정보가 있는 경우, 해당 시간대를 기준으로 해석합니다. 타임존 정보에 따라 해석이 달라지며, 개발자의 추가적인 오프셋 보정이 달라지므로 타임존 정보를 명시하는 것이 중요합니다.
예를 들어, 한국표준시로 "1970년 1월 1일 0시 0분 0초" 라는 값을 서버에서 클라이언트로 내려주는 상황을 가정해봅시다. 만약 BE 개발자가 tzinfo 에 대해 잘 모른다면(혹은 신경쓰지 않는다면) 다음과 같이 코딩할 것입니다. (서버 위치는 편의상 한국이라고 가정합니다.)
# python
>>> d = datetime(1970, 1, 1) # 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 (그리니치 표준시)'
이제 한국 개발자던 영국 개발자던, 세계 어디서도 동일한 시간을 보게 됩니다.
결국, 시간을 다룰 때 나타내고자 하는 시간과 그에 맞는 타임존 둘 다 명확하게 지정해주는 것이 좋습니다.
타임스탬프가 좋은지 ISO 문자열이 더 좋은지 따지는 것을 떠나서, 수많은 BE 코드와 DB의 값을 모두 수정하는 것은 사실상 불가능했기 때문에 실제로 문제를 해결하지는 못했습니다.
시간 데이터에 타임존 정보가 명시되어 있지 않아 자의적으로 해석할 여지가 있음으로 인해 개발의 비효율이 남아있었지만, 이를 개선할 수 없어서 많이 안타까웠습니다.
이번 경험을 통해 다음 두가지를 크게 깨달았습니다.
마지막으로, 자바스크립트는 시간을 어떻게 다루는지 찾아보았습니다.
🚨
Date객체의 중심을 구성하는 시간 값은 UTC 기준이지만, 날짜와 시간 등 구성 요소를 가져오는 메서드는 모두 현지(호스트 시스템의 위치)의 시간대를 사용한다는 것을 기억해야 합니다.
MDN Date 문서의 내용입니다. MDN 문서를 보고 여러가지 테스트한 결과, 아래와 같이 동작한 다는 것을 명확히 알게 되었습니다.
Date 인스턴스의 내부 시간 값은 UTC를 기준으로 한다.Date 인스턴스 생성 시, 주어진 타임존 정보에 따라 내부 시간 값을 보정한다. 만약 주어진 타임존 정보가 없다면, 로컬 타임존을 기준으로 한다.Date 인스턴스는💡 기존
Date의 여러가지 한계점을 해결하기 위해 Temporal API가 TC39에 제안되었으며, 25년 8월 기준 3단계입니다.