컴퓨터 시간의 1970년은 무슨 의미일까?

🗓 ⏰ 소요시간 8 분

Java 에서 시간을 다루는 클래스인 java.util.Date 클래스를 쓰다가 문득 의문이 들었습니다. 다음은 현재 시간을 가져오는 getTime() 메소드인데요, 왜 1970년 1월 1일 기준일까요? 그리고 왜 milliseconds 기준일까요?

public long getTime()
Returns the number of milliseconds since January 1, 1970, 00:00:00 GMT represented by this Date object.

이번 포스팅에서는 시간과 컴퓨터 시스템의 관계를 알아보겠습니다.

태양시 (solar time)

인간이 시간이라는 것을 구분하기 시작할 때 가장 쉬운 방법이 무엇이었을까요? 아마도 태양이었을 겁니다. 매일 동쪽에서 떠올라 머리 위를 지나 서쪽으로 지는 태양. 이 태양이 우리 머리 위에 올 때를 정오라고 합니다. 이를 기준으로 다시 정오가 되는 것을 하루 (1일)라고 볼 수 있겠죠.

태양시는 이렇게 태양을 기준으로 하는 시각계입니다. 북극점과 남극점을 최단 거리로 연결하는 선, 즉 태양이 관측자의 머리 바로 위를 지날 때는 12간지 중 자시🐭(00시)와 오시🐴(12시)에 온다고 해서 자오선(경선, meridian)이라고 하는데, 이를 기준으로 하루를 삼습니다.

세계시 (Universal Time, UT)

이렇게 지구와 태양을 바탕으로 세계 표준으로 정한 시각이 세계시입니다. 세계시의 종류에는 다음과 같습니다.

  • UT0: 별이나 천체의 일주운동을 이용해 태양의 위치를 결정
  • UT1: UT0 에 극점이 움직이는 극운동을 보정하여 계산

더 정밀한 원자 시계

하지만 태양시는 일정하지가 않습니다. 태양의 빛이 지구까지 오는 시간은 8분 12초 ~ 8분 28초라고 합니다. 즉, 우리가 눈으로 보고 생각한 태양시와 실제 태양의 위치에 따른 태양시는 약 1.35초 ~ 1.39초가 날 수밖에 없죠. 게다가 지구가 23.5도 기울어져 있고 공전이 타원궤도이기 때문에 계절에 따라 조금 다르다고 합니다. 태양의 운동이 매일 같지 않았다는 걸 바빌로니아 사람들은 이미 알고 있었다고 하네요. 게다가 지구의 자전 또한 점점 느려집니다.

앞서 매우 큰 지구와 태양으로 시간을 측정했다면 매우 작은 원자로도 시간을 측정하는 방법도 있습니다. 원자는 들뜸과 바닥 상태를 주기적으로 반복하는데, 이 고유한 진동의 주파수를 이용하면 정밀한 시간을 잴 수 있다는걸 알게 되었습니다. 세슘 원자 시계의 경우 1초 동안 진동하는 횟수는 9,192,631,770번으로 30만 년에 1초의 오차를 보인다고 합니다.

협정 세계시 (Coordinated Universal Time, UTC)

https://en.wikipedia.org/wiki/Coordinated_Universal_Time

어쨌든 우리가 사는 세상은 지구와 태양이 있는 세상이니 해가 경선에 있는 시간을 맞추기 위해서는 원자 시계에도 그 오차를 반영해야겠죠? 따라서 1월 1일이나 7월 1일 0시를 기점으로 1초를 더하거나 빼서 세슘 원자시계와 태양시의 차이의 오차를 보정하는데 이 1초를 윤초(leap second)라고 합니다. 이렇게 보정한 시간을 협정 세계시라고 정하고 1972년 1월 1일부터 국제적인 표준시가 되었습니다.

자, 그렇다면 실마리가 보이는군요. UTC 를 기준으로 하는건 UTC가 세계 표준시이기 때문이고, 1970년을 기준으로 하는건 아마도 1972년부터 UTC가 시행되었기 때문인 듯 합니다.

컴퓨터가 시간을 세는 법

컴퓨터는 어떻게 시간을 셀까요? CPU는 심장이 뛰듯 tick 을 발생시키는데 이를 기준으로 시스템이 돌아갑니다. 그래서 컴퓨터가 사람의 시간을 측정하는 방법은 특정 시점을 기준으로 그 때부터 발생된 틱의 수를 세는 방식입니다.

유닉스에서는 이 시작점을 POSIX time 혹은 Epoch time 이라고 하는데 바로 1970년 1월 1일 00:00:00 UTC 입니다. 이때부터 시작된 경과 시간을 초로 환산해 정수로 나타낸 것입니다. 이렇게 계산된 시간은 유닉스 계열 운영체제와 파일 형식들에서 사용되고 있습니다.

1
2
3
long millis = System.currentTimeMillis();
System.out.println(millis); // prints a Unix timestamp in milliseconds
System.out.println(millis / 1000); // prints the same Unix timestamp in seconds

이 누적된 초 또는 밀리초는 협정 세계시 UTC를 기준으로 하고 있기 때문에 어느 나라든 동일하고, 사용자의 위치에 따라 시차를 달리해서 보여줄 수가 있는 것이죠.

https://currentmillis.com/tutorials/system-currentTimeMillis.html

밀리초(milliseconds)의 장점

이렇게 프로그래밍에서 밀리초로 시간을 저장하게 되면 어떤 장점이 있을까요?

간단한 아키텍쳐

https://currentmillis.com/tutorials/system-currentTimeMillis.html

  • DB에는 일반 숫자로 시간을 저장할 수 있습니다.
  • 서버에서는 사용자의 위치와 시차에 상관없이 이 숫자만 처리하면 됩니다.
  • 클라이언트에 보여줄 때는 어디든 해당 로컬 타임존으로 변환해서 보여줄 수 있습니다.

벤치마킹

심플한 숫자로 이루어져 있어 간단히 성능을 측정할 때도 좋구요.

1
2
3
4
5
6
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
int[] myNumbers = new int[] { 5, 2, 1, 3, 4, 6, 9, 7, 2, 1 };
sort(myNumbers);
}
System.out.println("Duration: " + (System.currentTimeMillis() - start));

유니크한 ID

이 숫자를 이용해서 유니크한 아이디를 만들어낼 수 있습니다. 임시파일을 생성하거나 임시토큰을 만들어낼 수 있습니다.

이 외에도 여러가지 용도로 사용이 가능합니다.

이대로 괜찮을까?

하지만 여기서 또 한 가지 의문이 들 수도 있습니다. 유한한 정수 변수에 무한한 시간을 어떻게 담을 수 있을까요?

맞습니다. 오버플로우 문제가 발생할 수 있죠. 대부분의 32비트 운영 체제에서 초 시간을 저장하는 time_t 자료형은 32비트 정수형입니다. 그렇다면 2^31-1 초가 지난 2038년 1월 19일 화요일 03:14:07 UTC 가 오면 어떻게 될까요?

이러한 문제를 2038년 문제(year 2038 problem, Unix Millennium bug, Y2K38)라고 합니다. 오버플로우가 발생해서 음수가 되어버리고 1970년 또는 1901년으로 돌아가서 32비트 유닉스와 관련된 시스템은 모두 장애가 나버릴겁니다!

2038년이면 지금이 2018년이니까 20년 정도 남았습니다. 간단한 방법은 64비트 정수로 바꾸는 겁니다. 새로 만드는 프로그램은 상관없지만 이미 짜여져 오랫동안 사용해야 하는 프로그램들이 문제가 될텐데 지속적으로 수정하고 있다고 합니다. 만약 64비트 정수를 사용하면 이러한 문제를 약 3000억 년 정도 연기시킬 수 있고, 태양의 수명은 약 123억년이라고 하니 별 걱정 없겠네요.

이번 포스팅에서는 시간의 종류와 컴퓨터에서 어떻게 시간을 측정하게 되었는지 살펴봤습니다.

참고자료