현실 세계의 문제
운영 중인 서비스에 인입된 CS중 이런 것이 있었습니다.
사용자가 호텔 예약을 위해 결제를 진행하려고 하는데, 결제 페이지에서 보이는 일정이 선택한 일정과 다르게 나타남.
(e.g. 예약 페이지는 12-13일 일정으로 보이는데, 결제 페이지에서는 11-12일 일정으로 보임)
CS 담당자가 동일하게 시도할 경우 재현되지 않음.
제목에서 눈치채셨겠지만, 시간대와 관련된 문제였어요. 관련 개념을 차근차근 살펴보며 이슈의 원인과 해결에 관해 이야기해 보겠습니다.
Unix Timestamp
컴퓨터에서 시간을 다루는 방식은 인간의 직관과는 매우 다릅니다.
우리는 달력에 그려진 날짜를 생각하지만, 컴퓨터는 Unix Timestamp라는 숫자로 시간을 다룹니다.
이는 1970년 1월 1일 00:00:00 UTC로부터 경과한 시간을 밀리초 단위로 나타낸 값인데요, 절대적인 기준을 두고 그로부터 얼마나 떨어져 있는지를 말합니다.
// 컴퓨터의 시간 표현
const computerTime = new Date("2025-01-12").getTime(); // 1736640000000
하지만 32비트 시스템에서 저장할 수 있는 최댓값이 11111111 11111111 11111111 11111111
로,
2038년 1월 19일 3시 14분 7초가 Timestamp로 표현할 수 있는 마지막 시간이라는 문제가 있어요. 이걸 2038년 문제라고 부릅니다.
64비트 시스템을 사용하면 이 문제를 3,000억 년 정도 연기시킬 수 있다고 합니다.
// 32비트 시스템의 한계 (2038년 문제)
const maxInt32 = Math.pow(2, 31) - 1;
console.log(new Date(maxInt32 * 1000));
// 2038년 1월 19일 03:14:07 UTC
GMT
아까 Timestamp를 이야기할 때, 1970년 1월 1일 00:00:00 UTC로부터 경과한 시간이라고 표현했어요.
대충은 알겠는데, UTC라는 생소한 단어가 나옵니다. UTC를 설명하기 전에, GMT를 먼저 설명해야 할 것 같아요.
GMT(Greenwich Mean Time, 그리니치 평균시) 는 우리가 교과서 어디선가 본 것 같은 영국의 '그리니치 천문대'에서 측정한 시간입니다. 그리니치 천문대에서 태양이 가장 높은 위치(남중)에 있을 때를 정오로 정했는데요, 지구의 자전이 완벽히 일정하지 않아서 1년간 측정한 평균값을 사용한다고 합니다.
19세기 중반까지 대부분의 도시는 태양에 의해 정의된 자체 현지 시각을 사용했다고 해요. 시간을 측정하는 방법을 제도적으로 정해둔 국가도 없었고, 국제적인 협약도 없었다고 합니다. 그런데 19세기에 철도와 통신망이 확장되면서 국가 시간 표준의 필요성이 절실해졌습니다. 기차를 타는데 출발시간, 도착시간이 도시마다 다르면 안 되겠죠?
이때 GMT는 이미 천문학에서 중요한 지표로 사용되고 있었다고 해요. 따라서 영국의 철도 회사들은 표준 시간으로 GMT를 도입하기 시작합니다. 철도 회사들이 하나둘 표준 시간을 정하기 시작하면서, 영국 철도청은 1847년에 공식적으로 GMT를 철도 시간으로 채택하였습니다. 영국의 모든 철도는 GMT를 표준 시간으로 사용하게 된 것이에요. 이후 영국의 모든 공공 시계가 GMT에 맞춰졌고, 결국 1880년에 영국 법정 표준 시간이 됩니다.
시간이 흘러 1884년 미국의 워싱턴 D.C.에서 국제 자오선 회의가 열립니다. 여기서 그리니치 천문대가 본초자오선이 되었는데, 자세히는 천문대에 있는 Airy Transit Circle이라는 망원경이 기준점이라고 하네요. 이제 GMT가 전 세계 시간대 체계의 기초가 됩니다.
자오선이란?
남극에서 북극까지 잇는 상상의 선을 의미합니다. 본초자오선이 0°이며, 그것을 기준으로 지구를 360개로 쪼개면 우리가 알고 있는 경도가 됩니다.
그리니치 천문대가 본초자오선이 된 것은 영국의 영향력이 그만큼 컸다는 것을 보여줍니다. 미국은 이미 시간대 체계 기준으로 GMT를 사용하고 있었고, 세계 해양 상업의 대부분이 그리니치를 본초자오선으로 활용하는 해도에 의존했다고 해요.
참고: What is Greenwich Mean Time?
UTC
GMT가 전 세계 시간대 체계의 기초가 되었는데, 왜 위에서는 UTC라는 것을 사용했을까요?
위에서 언급했듯, GMT는 지구의 자전이 완벽히 일정하지 않기 때문에 다소 부정확한 면이 있었어요. 그래서 과학자들은 더욱 정확한 방법을 고민합니다.
우리가 인식하는 시간이라는 것은 일정한 간격으로 흐르는 것이에요. 따라서 항상 일정한 간격으로 발생하는 무엇인가를 기준으로 1초를 정해보기로 했습니다.
1950년대 당시에 가장 정확하게 측정할 수 있는 것은 바로 세슘 원자의 진동이었습니다. 세슘은 아주 규칙적이며 안정적으로 진동하는 특성을 가졌는데요, 마이크로파의 주파수를 조절해 가며 쏴보니 원자가 9,192,631,770번 진동할 때 걸리는 시간이 천문학에서 계산한 1초와 가장 근접했다고 해요. 오차는 고작 1억 년에 1초 정도라고 합니다.
1958년 1월 1일을 기준으로 순수하게 세슘 원자의 진동만을 계산하여 만들어진 것이 TAI(International Atomic Time, 국제원자시) 입니다. 이제 천문학적 불확실성에서 벗어나 세슘 원자의 진동을 바탕으로 시간을 계산하는 원자 시계를 사용하게 된 것이죠.
참고: What Is International Atomic Time (TAI)?
참고: Wikipedia - International Atomic Time
TAI는 UTC를 구성하는 요소라고 할 수 있습니다. TAI는 실제 하루의 길이를 결정하는 지구 자전 속도의 변화를 고려하지 않았기 때문에 오차가 발생했어요. 지구는 자전 속도가 조금씩 느려지고 있다고 하네요. 그래서 TAI에 필요할 때마다 윤초를 추가하여 보정한 것이 UTC라고 해요.
UTC(Coordinated Universal Time, 국제협정시) 는 1960년대에 제시된 개념인데요, 1967년에 국제도량형총회에서 세슘 원자의 진동을 기준으로 한 '초'에 대한 정의가 채택되고, 1972년 1월 1일에 공식적으로 GMT는 UTC로 대체됩니다. 그 이후, 지금까지 우리는 UTC를 국제 시간 표준으로 사용하고 있어요. 그러니까, 절대적인 시간 계산의 기준점이 바로 UTC인 것입니다.
참고: UTC: The World’s Time Standard
아, 본초자오선은 여전히 유효한 개념인데요, UTC가 도입된 후에도 그리니치가 본초자오선을 유지하고 있습니다. 다만, 기존의 Airy Transit Circle 망원경이 아니라 약 100m 정도 떨어진 곳이 기준점이 되었다고 하네요.
본초자오선의 반대편에는 날짜변경선이 생겼습니다. 본초자오선 기준 +12시간, -12시간의 차이가 24시간이기 때문에 날짜변경선을 기준으로 하루의 차이가 발생합니다.
이제 각 나라는 UTC를 기준으로, 자율적으로 시간대를 결정하고 있어요. 권고는 자오선을 기준으로 하며, UTC와 정수 시간 차이 나도록 하는 것이지만, 강제는 아니라고 합니다. 그래서 비슷한 경도라도 시간대가 다를 수 있습니다.
한국은 UTC+9로, UTC보다 9시간 더 빠릅니다. 그러니까, 그리니치에서 오후 1시라면 한국은 오후 10시인 것이죠. 이는 지구가 서쪽에서 동쪽으로 자전하기 때문입니다. 한국은 영국보다 동쪽에 있어요. 해는 동쪽에서 뜹니다. 해를 먼저 보게 되니까 더 이른 거죠. 기억하세요, '+는 앞서있다! -는 늦어있다!'
ISO 8601
ISO 8601은 국제표준화기구(ISO)에서 제정한 날짜와 시간의 국제 표준 형식입니다. 쉽게 말해서 우리가 날짜와 시간을 표기할 때 어떻게 적을 거냐는 거죠. 자바스크립트도 해당 형식을 사용하고 있습니다. 자바스크립트 스펙에서 다루는 상세한 내용은 가장 아래에 번역해 두었으니 참고해 주세요.
간단하게 우리가 흔히 접하는 형식을 살펴볼게요.
2025-01-13T13:00:00Z // 2025년 1월 13일 오후 1시 UTC
YYYY-MM-DD
는 익숙하실 것 같아요. 그리고 이어지는 것은 시, 분, 초를 나타내는 HH:mm:ss
입니다. 둘은 T
라는 구분자를 두고 띄어쓰기 없이 이어져 있어요.
맨 마지막의 Z
는 Zulu Time을 줄여서 부르는 말인데, UTC +00:00
을 의미해요. 군사적으로 사용되는 용어라고 합니다. 그러니까, Z
가 붙으면 UTC와의 차이(오프셋)가 없는 그리니치의 시간을 의미합니다. 위의 경우는 한국에서 봐도, 미국에서 봐도 무조건 그리니치의 오후 1시를 의미하는 거죠. 그리고 Z
대신 +01:00
, +09:00
등을 사용하면 UTC기준 얼마나 떨어져 있는지(오프셋)을 표현할 수 있어요.
아래 코드를 브라우저 콘솔에서 실행해 보면 확인해 볼 수 있어요.
// 둘은 같은 결과입니다.
new Date("2025-01-13T13:00:00Z") //Mon Jan 13 2025 22:00:00 GMT+0900 (한국 표준시)
new Date("2025-01-13T13:00:00+00:00") //Mon Jan 13 2025 22:00:00 GMT+0900 (한국 표준시)
그 외에 소수점으로 밀리초를 표기하거나, 확장 연도를 사용할 수 있는데 자주 보지는 못한 것 같아요.
로컬 타임(벽시계 시간)
'3시에 화상 회의합시다'라는 이야기를 들었다고 생각해 볼게요. 명동에서 일하는 저와 미국에서 일하는 동료 모두 자신의 벽시계를 기준으로 화상 회의에 참여한다면 어떨까요?
위에서 보았듯이, 한국은 UTC보다 9시간이 빠르고, 미국은 지역에 따라 다르지만 대략 8시간 느립니다. 저와 동료가 각자의 벽시계를 본다면 결코 만날 수 없는 시차겠죠.(제가 빈 회의실에서 17시간을 기다린다면 만날 수도 있겠네요)
만약 3시가 한국 시각 기준이었다면 이런 상황이 될 것 같아요.
// 서울에서 "오후 3시"를 기준으로 회의 시간을 잡았을 때
const seoulMeeting = new Date('2025-01-13T15:00:00+09:00');
console.log('서울 시각:',
seoulMeeting.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' }));
// 서울 시각: 2025. 1. 13. 오후 3:00:00
console.log('뉴욕 시각:',
seoulMeeting.toLocaleString('en-US', { timeZone: 'America/New_York' }));
// 뉴욕 시각: 1/13/2025, 1:00:00 AM
이처럼 "오후 3시"라는 동일한 벽시계 시간이 서울과 뉴욕에서는 완전히 다른 순간을 가리키게 됩니다. 이는 실제 비즈니스 로직에서 심각한 문제를 일으킬 수 있어요.
이렇게 각자의 시간대를 반영한 시간을 벽시계 시간, 로컬 타임이라고 부릅니다. 위에서는 한국 시각을 기준으로 생각했는데요, UTC를 기준으로 삼아도 각자의 벽시계 시간은 다르죠.
// 특정 순간을 UTC로 표현
const specificMoment = new Date('2025-01-13T06:00:00Z'); // 'Z'는 UTC를 의미
// 이 순간은 전 세계 어디서나 동일한 순간을 가리킵니다
console.log('UTC 시간:', specificMoment.toISOString());
// UTC 시간: 2025-01-13T06:00:00.000Z
console.log('Unix Timestamp:', specificMoment.getTime());
// Unix Timestamp: 1736748000000
// 하지만 이 순간을 각 지역에서는 다르게 경험합니다
console.log('서울:',
specificMoment.toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' }));
// 서울: 2025. 1. 13. 오후 3:00:00
console.log('뉴욕:',
specificMoment.toLocaleString('en-US', { timeZone: 'America/New_York' }));
// 뉴욕: 1/13/2025, 1:00:00 AM
console.log('런던:',
specificMoment.toLocaleString('en-GB', { timeZone: 'Europe/London' }));
// 런던: 13/01/2025, 06:00:00
자바스크립트의 시간
우리가 시간을 이야기할 때, 그것이 로컬 타임인지 UTC인지를 명확히 표현하는 것은 매우 중요합니다. 일반적으로 우리 생활에서 이야기하는 시간은 로컬 타임이죠. 같은 공간에 함께 존재하는 사람들과 이야기할 때는 물론이고, 해외에 있는 사람들과 이야기할 때도 'UTC 3시에 만나자!' 라고는 하지 않는 것처럼요.
그럼, 자바스크립트에서는 어떨까요?
const dateOnly = new Date("2025-01-13")
// Mon Jan 13 2025 09:00:00 GMT+0900 (한국 표준시)
위 경우, dateOnly
는 UTC로 해석됩니다.
만약 로컬 타임으로 해석되었다면 Mon Jan 13 2025 00:00:00 GMT+0900 (한국 표준시)
이어야겠죠. 하지만 UTC로 해석했기 때문에 0시가 아니라 9시로 표현됩니다. 한국은 9시간 빠르니까요!
const dateWithTime = new Date("2025-01-13T00:00:00")
// Mon Jan 13 2025 00:00:00 GMT+0900 (한국 표준시)
그런데 시간대 없이(UTC 오프셋 없이) 입력한다면 로컬 타임으로 해석되는 걸 확인할 수 있어요. 그러니까, 날짜만 입력하면 UTC고 시간까지 입력하면 로컬 타임이에요. 왜 이렇게 동작하는 걸까요?
ISO 8601 표준에서는 타임존 정보가 없는 날짜/시간 텍스트는 로컬 타임으로 해석한다고 명시하고 있습니다. 그런데 ES5 시절 ECMAScript 스펙에서는 반대로 타임존 정보가 없을 때 UTC로 해석하도록 스펙을 정의했다고 해요. 그러니까, 반대로 구현해 버린 것이죠.
참고: MDN - Date.parse
참고: Wikipedia - ISO 8601
국제 표준과 반대로 정의했다는 걸 깨닫고, TC39에서는 ES6에서 이를 바로잡아 타임존 정보가 없으면 로컬 타임존으로 해석하도록 스펙을 바꾸었어요. 이 바뀐 스펙이 브라우저에서 구현되자, ES5 시절에 구현된 수많은 사이트에서 수많은 버그가 발생했다고 합니다.
결국 브라우저들의 기존 구현과 사용자 피드백을 종합해서 ISO 8601 표준과는 다르지만, 기존 호환성을 살리는 방향으로 절충해서 지금과 같은 스펙이 되었다고 하네요.
참고: tc39/ecma262 - Date Time String Format: default time zone difference from ES5 not web-compatible
이렇게 이미 의존하고 있는 코드들이 너무 많은 Date 객체는 API에 Breaking Change를 발생시키기 어려워 새로운 날짜/시간을 다루는 타입으로 Temporal API라는 새로운 스펙을 준비하고 있다고 합니다.
참고: Temporal API 문서
또한, 우리가 흔히 사용하는 Date 객체의 메서드들은 모두 로컬 타임을 기준으로 동작해요. 아마 대부분의 경우, Date 객체를 원하는 양식의 문자열로 포매팅할 때 toString
, getDate
, getHours
등의 메서드를 사용할 것 같은데, 이는 모두 로컬 타임 기준으로 반환합니다.
const event = new Date("2025-01-13"); // date-only 포맷은 UTC 기준 2025년 1월 13일 0시로 해석됨
console.log(event.toString()); // Mon Jan 13 2025 09:00:00 GMT+0900 (한국 표준시)
console.log(event.getDate()); // 13
console.log(event.getHours()); // 9
UTC가 아닌 로컬 타임 기준 0시로 Date 객체를 생성하면 이렇습니다.
const eventLocal = new Date("2025-01-13T00:00:00"); // 시간을 명시하면 로컬 타임 기준 2025년 1월 13일 0시로 해석됨
console.log(eventLocal.toString()); // Mon Jan 13 2025 00:00:00 GMT+0900 (한국 표준시)
console.log(eventLocal.getDate()); // 13
console.log(eventLocal.getHours()); // 0
만약 항상 UTC 기준으로 포매팅하고 싶다면 UTC 메서드를 사용해야 합니다.
const eventLocal = new Date("2025-01-13T00:00:00"); // 로컬 타임(한국) 기준 2025년 1월 13일 0시
console.log(eventLocal.toUTCString()); // Sun, 12 Jan 2025 15:00:00 GMT
console.log(eventLocal.getUTCDate()); // 12
console.log(eventLocal.getUTCHours()); // 15
문제 상황과 해결
사용자가 2025년 1월 12일 날짜에 호텔을 예약하려 한다고 상상해 볼게요.
new Date("2025-01-12")
이라는 코드가 실행됩니다. 자바스크립트는 이를 UTC 시간으로 해석해요. 2025년 1월 12일 00시 00분 00초 UTC
한국의 로컬 타임으로 변환하면 Sun Jan 12 2025 09:00:00 GMT+0900 (한국 표준시)
가 되겠네요.
여기까지는 문제가 없어 보여요. 실제로 한국에서 서비스를 이용하는 동안에는 아무런 문제가 발생하지 않을 것 같습니다. CS 담당자분은 재현이 되지 않았던 것처럼요.
하지만 사용자가 미국 LA에 있다면 어떨까요? 동일한 코드 new Date("2025-01-12")
는 여전히 UTC 시간으로 해석됩니다. 2025년 1월 12일 00시 00분 00초 UTC
입니다.
여기서 문제가 발생하는데요, LA는 UTC보다 8시간 뒤처져 있습니다. 따라서 미국 로컬 타임으로 변환하면 1/11/2025, 4:00:00 PM
이 됩니다. 즉, UTC시간을 사용했기 때문에 각자의 로컬 타임으로 변환하는 과정에서 날짜가 차이 나버린 것입니다.
이 문제를 해결하기 위해서는 UTC 대신 애초에 각자의 로컬 타임 기준으로 해석하게 만들면 됩니다. 그러니까, UTC 자정 → 로컬 타임 과정에서 오프셋을 고려하게 되는데, 이를 방지하기 위해 애초에 2025-01-12
라는 문자열을 로컬 타임 자정으로 해석하면 해결될 것 같아요.
서비스에서 date-fns라는 라이브러리를 사용하고 있는데요, 여기에는 parseISO
라는 함수가 존재합니다. 이 함수는 자바스크립트의 구현이 아니라 ISO 8601 표준에 따라 동작합니다. 즉, new Date("2025-01-12")
대신 parseISO("2025-01-12")
를 사용하면 로컬 타임을 사용하여 해결할 수 있습니다.
const hotelCheckInUTC = new Date("2025-01-12");
// 1월 12일 자정 UTC는...
// 한국에서는 1월 12일 오전 9시로 해석
// LA에서는 1월 11일 오후 4시로 해석
const hotelCheckInLocalTime = parseISO("2025-01-12");
// 한국에서나, 미국에서나 각자의 시간대에서 자정으로 해석
// 한국에서는 1월 12일 0시(한국 시각)로 해석
// LA에서는 1월 12일 0시(LA 시각)로 해석
// UTC 기준으로 보자면 둘은 다른 시간대를 의미하지만, 어디서나 같은 날짜를 보여줄 수 있게 되었다.
참고: Fixing JavaScript Date - Web Compatibility and Reality
아래는 ECMAScript 스펙 중 날짜 시간 문자열 형식을 번역한 내용입니다.
21.4.1.32 날짜 시간 문자열 형식
ECMAScript는 ISO 8601 달력 날짜 확장 형식을 단순화한 날짜-시간용 문자열 교환 형식을 정의합니다. 형식은 다음과 같습니다:
YYYY-MM-DDTHH:mm:ss.sssZ
여기서 각 요소는 다음과 같습니다:
요소 | 설명 |
---|---|
YYYY | 율리우스력의 연도를 0000에서 9999까지의 네 자리 십진수로 표현하거나, "+" 또는 "-" 다음에 여섯 자리 십진수가 오는 확장 연도로 표현 |
- | "-" (하이픈)이 문자열에 두 번 그대로 나타남 |
MM | 월을 01(1월)부터 12(12월)까지의 두 자리 십진수로 표현 |
DD | 일을 01부터 31까지의 두 자리 십진수로 표현 |
T | "T"가 문자열에 그대로 나타나며, 시간 요소의 시작을 나타냄 |
HH | 자정 이후 경과한 완전한 시간을 00부터 24까지의 두 자리 십진수로 표현 |
: | ":" (콜론)이 문자열에 두 번 그대로 나타남 |
mm | 시간 시작 이후 경과한 완전한 분을 00부터 59까지의 두 자리 십진수로 표현 |
ss | 분 시작 이후 경과한 완전한 초를 00부터 59까지의 두 자리 십진수로 표현 |
. | "." (점)이 문자열에 그대로 나타남 |
sss | 초 시작 이후 경과한 완전한 밀리초를 세 자리 십진수로 표현 |
Z | UTC 오프셋 표현으로, "Z"(오프셋 없는 UTC)로 지정하거나 "+" 또는 "-" 다음에 시간 표현 HH:mm이 오는 형태 |
이 형식은 다음과 같은 날짜 전용 형식을 포함합니다:
YYYY
YYYY-MM
YYYY-MM-DD
또한 위의 날짜 전용 형식 중 하나 다음에 선택적 UTC 오프셋 표현이 추가된 다음 시간 형식 중 하나가 바로 이어지는 "날짜-시간" 형식도 포함합니다:
THH:mm
THH:mm:ss
THH:mm:ss.sss
범위를 벗어나거나 형식을 준수하지 않는 요소가 포함된 문자열은 이 형식의 유효한 인스턴스가 아닙니다.
참고 1
하루는 자정에 시작하고 끝나므로, 00:00과 24:00이라는 두 가지 표기법을 사용하여 하나의 날짜와 연관될 수 있는 두 자정을 구분할 수 있습니다. 이는 다음 두 표기법이 정확히 같은 시점을 가리킨다는 것을 의미합니다: 1995-02-04T24:00과 1995-02-05T00:00. 후자를 "달력 날짜의 끝"으로 해석하는 것은 ISO 8601과 일치하지만, 해당 명세는 이를 시간 간격을 설명하기 위해 예약하고 있으며 단일 시점의 표현 내에서는 허용하지 않습니다.
참고 2
CET, EST 등과 같은 민간 시간대의 약어를 지정하는 국제 표준은 존재하지 않으며, 때로는 같은 약어가 두 개의 매우 다른 시간대에 사용되기도 합니다. 이러한 이유로 ISO 8601과 이 형식 모두 시간대 오프셋의 숫자 표현을 지정합니다.
21.4.1.32.1 확장 연도
1970년 1월 1일을 기준으로 약 273,790년 전후의 전체 시간값 범위를 다루기 위해서는(21.4.1.1) 0년 이전이나 9999년 이후의 연도를 표현해야 합니다. ISO 8601은 연도 표현의 확장을 허용하지만, 정보 교환 파트너 간의 상호 합의가 있는 경우에만 가능합니다. 단순화된 ECMAScript 형식에서는, 이러한 확장된 연도 표현은 6자리여야 하며 항상 + 또는 - 부호가 앞에 붙습니다. 0년은 양수로 간주되며 + 부호를 앞에 붙여야 합니다. -000000으로 0년을 표현하는 것은 유효하지 않습니다. 시간값 범위를 벗어나는 순간을 나타내는 확장 연도를 포함한 날짜 시간 문자열 형식과 일치하는 문자열은 Date.parse에 의해 인식되지 않는 것으로 처리되며, 구현별 동작이나 휴리스틱으로 대체하지 않고 해당 함수가 NaN을 반환하게 합니다.
참고
확장 연도를 포함한 날짜-시간 값의 예:-271821-04-20T00:00:00Z 기원전 271822년 -000001-01-01T00:00:00Z 기원전 2년 +000000-01-01T00:00:00Z 기원전 1년 +000001-01-01T00:00:00Z 서기 1년 +001970-01-01T00:00:00Z 서기 1970년 +002009-12-15T00:00:00Z 서기 2009년 +275760-09-13T00:00:00Z 서기 275760년