CS · Time · Backend

시간 - 컴퓨터에 '지금'은 수 하나다

화면에 뜬 '2025년 9월 30일 오후 3시'를 컴퓨터는 그렇게 들고 있지 않다. 저장된 건 수 하나뿐이고, 날짜와 시각은 그 수에 규칙을 씌워 만든 것이다. 그 규칙이 어긋날 때 무슨 일이 벌어지는지부터 본다.

·시간이라는 데이터 1편
목차
  1. 저장된 것은 수 하나다
  2. 공항 로비의 큰 시계 하나
  3. 같은 수가 도시마다 다르게 읽힌다
  4. 순간과 달력은 다른 일이다
  5. 팻말이 없으면 되돌릴 수 없다
  6. 큰 시계도 완벽하지 않다
  7. 실무에서: 저장은 순간으로, 표시는 팻말로
  8. 정리

서버에 찍힌 시각이 아홉 시간 밀려 본 적 있다면 이미 이 이야기를 만난 것이다. 그날 무슨 일이 있었는지를 이 시리즈에서 푼다.

저장된 것은 수 하나다

프로그램이 “지금”을 물으면 돌아오는 것은 날짜도 시각도 아니다. 수 하나다.

plaintext
1759208400

이 수에는 년도 칸도 없고 월 칸도 없다. 아침인지 밤인지도 안 적혀 있다. 그저 정해진 어느 시점부터 몇 초가 흘렀는지를 센 값이다.

화면에 2025-09-30 15:00이 보이는 건, 이 수를 받아 든 프로그램이 달력 규칙과 지역 규칙을 씌워 사람이 읽을 꼴로 그려 준 것뿐이다. 저장된 곳에 그 문장은 없다.

이건 글자가 사실은 수라는 이야기와 같은 구조다. 다만 글자는 표만 맞추면 되는데, 시간은 표가 계절마다 바뀌고 나라가 고쳐 단다. 그래서 따로 볼 값이 있다.

공항 로비의 큰 시계 하나

이 시리즈는 처음부터 끝까지 공항 로비를 따라간다.

로비 한가운데에 큰 시계가 하나 걸려 있다. 세상에 시계는 이것 하나뿐이고, 멈추지도 되돌아가지도 않고 그냥 앞으로만 센다. 그 아래 벽에는 도시 이름이 적힌 팻말이 줄줄이 붙어 있다.

plaintext
        ┌─────────────┐
        │  큰 시계     │   ← 세상에 하나뿐. 계속 앞으로만 센다
        └─────────────┘
   [서울 +9]  [런던 +0]  [뉴욕 -4]   ← 팻말: 큰 시계를 어떻게 읽나

팻말에는 시각이 안 적혀 있다. 큰 시계를 보고 몇 시간을 더하거나 빼라는 지시만 적혀 있다. 서울 팻말이 +9면 큰 시계가 6시를 가리킬 때 서울은 15시다.

시계는 하나, 팻말은 여럿. 이 그림 하나로 시리즈 전체가 굴러간다.

  • 큰 시계가 세는 수는 무엇인가 → 에폭
  • 팻말의 숫자는 누가 정하나 → UTC와 오프셋, 그리고 시간대
  • 팻말이 계절마다 바뀌면 → 서머타임
  • 팻말을 안 적고 시각만 적으면 → 저장할 때 벌어지는 사고
  • 큰 시계마저 흔들리면 → 단조 시계

같은 수가 도시마다 다르게 읽힌다

핵심은 수 자체에는 몇 시라는 뜻이 없다는 것이다. 뜻은 어느 팻말로 읽느냐가 정한다.

1759208400
큰 시계가 들고 있는 것 - 수 하나가 전부다
서울 +09:00
15:00같은 순간, 오후
런던 +01:00
07:00같은 순간, 아침
뉴욕 −04:00
02:00같은 순간, 새벽

수는 그대로인데 팻말을 바꿔 들면 시각이 갈라진다. 셋 다 같은 순간을 자기 팻말대로 정확히 읽은 것이다.

같은 순간을 놓고 팻말을 바꿔 들자 시각이 셋으로 갈라졌다. 어느 하나가 맞고 나머지가 틀린 게 아니다. 셋 다 같은 순간을 자기 팻말대로 정확히 읽은 것이다.

세 사람이 동시에 전화를 받았는데 한 명은 “지금 15시”라 하고 다른 한 명은 “지금 6시”라 한다. 둘 다 참이다.

순간과 달력은 다른 일이다

시간 이야기가 헷갈리는 가장 큰 이유는 두 가지 다른 것이 한 이름으로 불리기 때문이다. 여기서 갈라 두면 나머지가 쉬워진다.

무엇인가팻말이 필요한가
순간우주에 딱 한 번 있었던 그 시점필요 없다. 수 하나로 끝난다
달력 시각사람이 읽는 2025-09-30 15:00필요하다. 팻말 없이는 뜻이 안 정해진다

“결제가 일어난 시점”은 순간이다. 서울에서 보든 뉴욕에서 보든 같은 순간이고, 수 하나면 잃는 것이 없다.

“매일 아침 9시에 알림”은 달력 시각이다. 여기서 9시는 순간이 아니라 팻말을 통해 매번 다시 계산되는 값이다. 사용자가 비행기를 타고 이동하면 그 9시가 가리키는 순간도 달라진다.

둘을 같은 타입에 담으면 반드시 어딘가에서 어긋난다. 이 시리즈가 되풀이해 말할 한 가지가 이것이다.

팻말이 없으면 되돌릴 수 없다

여기서 비유가 한 번 깨진다. 공항 로비라면 벽을 올려다보면 팻말이 보인다. 저장된 값은 못 본다.

2025-09-30 15:00이라고만 적힌 값을 받으면 이게 서울 15시인지 런던 15시인지 알 방법이 없다. 팻말 없는 달력 시각은 아홉 시간 폭의 구간을 가리킬 뿐이다.

그래서 현실에서는 팻말을 값에 같이 실어 보낸다.

  • 문자열로 적을 때는 뒤에 +09:00이나 Z를 붙인다
  • DB 컬럼은 시간대를 같이 담는 타입을 고른다
  • API는 시각을 실어 보내는 규칙을 문서에 못 박는다

셋 다 값 옆에 붙는 쪽지다. 쪽지가 없으면 받는 쪽은 추측한다. 그리고 추측이 틀리는 날이 사고 나는 날이다.

큰 시계도 완벽하지 않다

비유가 두 번째로 깨지는 자리다. 공항의 큰 시계는 하나뿐이지만, 컴퓨터마다 자기 시계를 따로 갖고 있다.

그 시계들은 조금씩 어긋난다. 그래서 바깥의 정확한 시계에 주기적으로 맞추는데, 맞추는 순간 시각이 훌쩍 뛰거나 거꾸로 갈 수 있다. “10초 전에 시작한 작업이 아직 시작 안 한 것으로 나온다”는 황당한 일이 여기서 나온다.

그래서 실제 컴퓨터에는 시계가 사실 둘이다. 하나는 지금이 몇 시인지를 말하고, 다른 하나는 얼마나 지났는지만 센다. 뒤엣것은 사람이 읽을 수 없는 대신 절대 거꾸로 가지 않는다. 이 시리즈의 뒷부분에서 이 둘을 갈라 본다.

실무에서: 저장은 순간으로, 표시는 팻말로

시간 사고는 계산 한가운데서 나지 않는다. 값이 경계를 넘는 자리에서 난다. DB에 넣을 때, API로 보낼 때, 화면에 그릴 때, 배치가 도는 시각을 정할 때다.

그래서 실무의 기본형은 하나다. 안쪽에서는 순간으로만 다루고, 사람에게 보여 줄 때만 팻말을 씌운다.

java
// 위험: 이 값이 어느 팻말로 만들어졌는지 코드가 모른다
LocalDateTime now = LocalDateTime.now();

// 안전: 순간을 그대로 들고 있고, 팻말은 보여 줄 때만 씌운다
Instant now = Instant.now();
String shown = now.atZone(ZoneId.of("Asia/Seoul")).format(fmt);

앞의 코드가 무서운 건 틀렸을 때도 잘 돌아간다는 점이다. 개발 장비에서는 멀쩡하고 서버에 올리면 아홉 시간이 밀린다. 서버의 지역 설정이 달라서다. 내 컴퓨터에선 되는데의 전형적인 얼굴 중 하나가 시간이다.

테스트도 같다. 지금 시각을 코드가 직접 물으면 하루 중 언제 돌리느냐에 따라 결과가 달라진다. 그래서 시각은 바깥에서 갈아끼울 수 있게 만들어 둔다.

정리

  • 컴퓨터에 날짜와 시각은 없다. 수 하나가 있고, 사람이 읽는 시간은 그 수에 규칙을 씌운 결과다.
  • 같은 수도 어느 팻말로 읽느냐에 따라 다른 시각이 된다. 셋 다 자기 팻말대로는 정확하다.
  • 순간과 달력 시각은 다른 것이다. 순간은 수 하나로 끝나고, 달력 시각은 팻말이 있어야 뜻이 정해진다.
  • 수 자체에는 팻말이 안 들어 있다. 그래서 값 옆에 쪽지로 붙여 보낸다.
  • 안쪽에서는 순간으로 다루고 보여 줄 때만 팻말을 씌운다. 반대로 하면 경계마다 어긋난다.

다음 글은 그 수 자체로 내려간다. 무엇을 기준으로 세기 시작했고, 그 칸이 언제 넘치는지부터 봐야 나머지가 보인다.