c언어에서 time(NULL) 함수로, 1970년 1월 1일부터 현재까지 경과된 초를 리턴한다고 했었습니다. 이 값만 끌어와서 뭔가 의미있는 데이터로 가공하고 싶은데, 어떻게 하면 좋을까요? 현재까지 경과된 초를 넘겨주면, 데이터를 잘 가공해서, struct tm *형을 리턴해 주는 함수가 있는데요. 이것이 localtime 함수입니다.

 

 

struct tm *localtime(time *cur_t);

 

 

 그러면, struct tm형은 어떠한 필드들로 이루어져 있을까요?

 

 

 예제에서 다루는 중요한 필드만 몇 개 봅시다. tm_sec, tm_min, tm_hour는 각각 시, 분, 초를 의미합니다. 그리고, tm_mday, tm_mon, tm_year가 있는데요. 이것은, 각각 몇 일, 몇 월, 몇 년임을 의미합니다. 그런데 tm_year 값을 보면, year에서 1900을 뺀 값이 실값이라고 되어 있는데요. 예를 들어서 현재 시간을 locatime으로 넘겨줬다면, tm을 리턴할 텐데, 그 안에 있는 tm_year 필드는 2019가 아닌, 119가 됩니다.

 

 그리고, Month는 0부터 11까지 되어 있는데요. 제가 이 포스팅을 완료한 시간을 넘기면, tm_mon이 7이 아닌 6이 들어가 있을 거에요. 이 부분 주의해 주시면 됩니다. tm_wday는, 일, 월, 화, 수, 목, 금, 토를 나타내는 변수인데요. 각각 이것들은 0, 1, 2, 3, 4, 5, 6에 대응됩니다.

 

 


 localtime을 이용한 예제 프로그램을 봅시다.

 

 

 먼저, 1970년 1월 1일 0시 0분 0초부터 현재까지 경과된 시간을 now가 가지고 있어요. 즉, now는 제가 글을 쓰고 있는 시간인 2019년 7월 23일 오후 3시 x분 x초다. 라는 정보를 time_t형으로 압축되어서 가지고 있을 거에요. 이 정보를 어디에 넘겨줬나요? localtime 함수에 넘겨주었는데요. now의 주솟값을 넘겨주었습니다.

 

 이렇게 localtime 함수에 의해서 잘 가공이 되면, struct tm 구조체의 포인터가 리턴될 겁니다. print_day 함수를 봅시다.

 

 

 여기에서는 현재 몇 년, 몇 월, 몇 일, 몇 시, 몇 분, 몇 초인지만 출력하고 있어요. tm 구조체에는 tm_year 필드에, 현재 년도 - 1900이 저장이 되어 있고요. tm_mon에 현재 월 - 1이 저장되어 있어요. 실제 값을 출력하려면 tm_year에는 1900을 더해야 하고, tm_mon에는 1을 더해야 할 거에요.

 

 

 출력 결과는 다음과 같습니다. 그런데, 저는 오늘이 화요일이다. 라는 정보까지 출력하고 싶어요. 그러면 어떻게 해야 할까요? 간단합니다. tm_wday 필드를 이용하면 되는데요. 이 필드 값이 0이라면 일요일, 1이라면 월요일, 2라면 화요일, 3이라면 수요일, 4라면 목요일, 5라면 금요일, 6이라면 토요일을 의미합니다.

 

 

 그에 맞게, 문자열 배열을 선언해 주겠습니다. 각각 0번부터 6번에, "SUN", "MON", ... , "SAT" 리터럴을 저장하고 있어요. 우리는 wday가 0이라면 "SUN"에, 1이라면 "MON"에만 접근하면 되겠네요?

 

 

 즉, 요일을 출력할 때에는, p[t->tm_wday]를 가지고 오면 됩니다.

 

 

 여기까지 별로 어렵지 않네요. 정리해 봅시다. 어떤 걸 했나요? time_t형으로 가져온, 1970년 1월 1일 0시 0분 0초에서부터 현재까지 경과된 초를 가지고, 어떻게 변환시켰나요? struct tm 형으로 바꿨어요. 거기에는 무엇이 들어있었나요? 몇 년, 몇 월, 몇 일, 몇 시, 몇 분, 몇 초인지 나와 있었습니다. 사실 기준 시간을 알고 그 시간으로부터, 현재까지의 경과 시간을 알면 현재 날짜랑 시간 또한 구할 수 있어요.

 

 


 이제 문제 상황을 주겠습니다. 저는, 지금 막 바로 책을 빌렸습니다. 대여 기간이 30일이 딱 되면 연체입니다. 저는 언제까지 책을 반납하면 될까요? 그 날짜랑 시간을 구하고 싶습니다.

 

 

 프로그램을 이렇게 바꾸시면 됩니다. 1970년 1월 1일 0시 0분 0초부터 현재까지 경과된 초를 얻어오는 건 time(NULL)이였습니다. 하루는 몇 초인가요? 24에 60에 60을 곱하면 될 거에요. 30일은 몇 초인가요? 하루가 몇 초인지 계산한 다음에, 그 값에다가 30을 곱하면 됩니다.

 

 따라서, time(NULL)에다가 30*(24*60*60)을 더하면, 현재 시간으로부터 30일이 지난 후 기준 시간으로부터 경과된 초를 구할 수 있을 겁니다. 이것을 localtime에 넘겨서, 30일이 지난 후 날짜와 시간을 얻어올 수 있을 겁니다.

 

 

 저는 8월 22일까지 반납하면 되겠군요. 이것을 잘 응용하면 D day 프로그램도 만들 수 있을 거에요. 이벤트가 언제 일어나는지 저장해 둔 다음에, 몇 일이나 남았는지 구하는 것이지요. 이것은 자료구조 프로젝트 때 살짝 언급을 해 보도록 하겠습니다.

 

 


 그런데, localtime 함수를 사용할 때 주의하셔야 할 점이 있어요.

 

 

 t는 오늘부터 30일 후, n은 오늘 데이터를 저장하는 코드입니다. 실행 결과가 어떻게 나올까요?

 

 

 예상과는 다르게 나옵니다. 왜 그럴까요? localtime은 공유되는 변수를 써서 그렇습니다. 실제로, t의 주솟값과 n의 주솟값을 찍어보면 어떻게 나오는지 보시면 쉽게 알 수 있습니다. 이는 strtok과 같은 함수도 마찬가지인데요. 내부에서 전역, 혹은 static 변수를 쓰기 때문에 계속 공유 변수를 쓰게 됩니다.

 

 

 t = localtime(&next); 를 실행했을 때 상황이에요. 저는 노란색 부분에다가 30일 후 날짜에 대한 정보를 저장하고 있어요. 그리고, 공유 변수의 위치를 리턴합니다.

 

 

 그러면 t가 shared value를 가리키고 있는 상황입니다. 정확히 말하면, static한 변수의 주소를 가지고 있어요. 그리고 나서 무엇이 다시 호출이 될 거냐면, u = localtime(&now)를 수행할 거니까, 다시 로컬타임이 호출됩니다.

 

 

 그러면 이 때도 마찬가지고, static 변수에다가 씁니다. now에 대한 정보를. 그리고 제가 보라색으로 칠한 변수가 메모리 주소 어디에 저장이 되어 있는지를 리턴을 할 건데요. 보라색으로 칠한 부분을 t가 가리키고 있어요.

 

 

 그런데, 이것을 u도 가리켰다? 얕은 복사입니다. 둘 다 정적 변수를 가리키고 있으니까, 똑같은 값이 리턴이 될 수 밖에 없어요. 함수가 스레드에 안전하지 않는 이유는, 정적 메모리 버퍼에 담긴 데이터를 이용하기 때문인데요. 이 함수가 그러합니다. 그렇기 때문에, 처리하실 때 조심하셔야 해요.

 

 이런 문제를 해결하기 위해서, 리눅스 환경에서는 localtime_r 함수를 쓸 수 있어요.

 

 

struct tm *localtime_r(time_t *cur_t, struct tm *res);

 

 

 1번째 인자는 같아요. 2번째 인자가 문제인데, 결과값을 저장할 버퍼입니다. 제가 넘긴 time_t형의, 압축된 데이터를 가지고, struct tm 형으로 변환할 건데요. 변환한 결과를 res가 가리키는 공간에 저장하겠다는 겁니다.

 

 

localtime_r 사용법

 다른 점이 보이시나요? 저는 결과를 저장할 버퍼인, t와 n을 선언해 주었습니다. 그리고, localtime_r의 2번째 인자에, 그 버퍼들을 넘겼을 뿐입니다. 이렇게 하시면, 30일 후의 날짜와, 현재 날짜가 제대로 출력이 됩니다.