curl 링크 없이 C++ date 라이브러리의 시간대 기능 사용하기

Twitter icon류광, 2021-11-26 20:11
curl 라이브러리를 링크하지 않고 date 라이브러리의 시간대 관련 기능을 사용하는 방법.

date 라이브러리는 C++ 표준화 과정에 깊숙이 관여해 온 C++ 전문가 하워드 히넌트Howard Hinnant가 만든 날짜, 시간 조작 라이브러리입니다.

조만간 출간될 번역서 "(가제)C++20: 풍부한 예제로 익히는 핵심 기능"1은 "C++ 크로노 라이브러리를 완벽하게 지원하는 컴파일러가 없는 상황2에서 C++20의 날짜·시간 관련 기능을 시험"하는 목적으로 이 라이브러리를 사용합니다.

date 라이브러리 사용과 관련해서 번역서에 이런 문구가 나옵니다:

C++ 컴파일러들의 지원 미비 때문에, 이번 절의 두 예제처럼 C++20의 시간대 라이브러리를 사용하는 프로그램을 빌드하려면 date 라이브러리의 tz.cpp 파일을 함께 컴파일해야 하며, curl 라이브러리를 링크해야 한다. curl 라이브러리는 현재 IANA 시간대 데이터베이스를 웹에서 내려받는 데 필요하다.

이 글에서는 Windows 환경에서 curl 라이브러리를 링크하지 않고 date 라이브러리의 시간대 관련 기능을 사용하는 방법을 이야기합니다.

목표는 번역서 [목록 4.55]에 나오는 다음과 같은 예제 C++ 프로그램을 성공적으로 컴파일하고 실행하는 것입니다.

// localTime.cpp

#include "date/tz.h"
#include <iostream>

int main() {

  std::cout << '\n';

  using namespace date;

  std::cout << "UTC  time" << '\n';
  auto utcTime = std::chrono::system_clock::now();
  std::cout << "  " << utcTime << '\n';
  std::cout << "  " << date::floor<std::chrono::seconds>(utcTime)
            << '\n';

  std::cout << '\n';

  std::cout << "Local time" << '\n';
  auto localTime = date::make_zoned(date::current_zone(), utcTime);
  std::cout << "  " << localTime << '\n';
  std::cout << "  "
            << date::floor<std::chrono::seconds>(localTime.get_local_time())
            << '\n';
  auto offset = localTime.get_info().offset;
  std::cout << "  UTC offset: "  << offset << '\n';

  std::cout << '\n';

}

curl 라이브러리는 date 라이브러리의 시간대 기능(tz.cpp)이 처음 활성화될 때 IANA에 있는 tzdata의 최신 버전을 내려받는 데 쓰입니다. 그런데 이 tzdata는 실시간으로 자주 갱신되는 데이터베이스가 아닙니다. 시간대 기능을 사용하는 프로그램을 실행할 때마다 매번 IANA에 접속해서 tzdata의 새 버전이 나왔는지 확인할 필요가 있을까, 그냥 가끔 수동으로 또는 cron 같은 도구로 tzdata를 내려받으면 되지 않겠는가 하는 것이 이 글을 쓰게 된 배경입니다.

tzdata 다운로드 기능은 매크로 상수 HAS_REMOTE_API를 정의해서 tz.cpp를 컴파일한 경우에만 활성화되며, HAS_REMOTE_API를 따로 정의하지 않으면 curl 라이브러리를 참조하는 부분이 컴파일에서 아예 제외됩니다.

거두절미하고, 다음은 Windows에서 g++로 예제 프로그램을 빌드하는 명령들입니다. date 라이브러리 깃허브 저장소에서 소스 코드 압축 파일을 내려받아서 D:\Dev\Libs\date에 풀었다고 가정합니다.

> SET DATE_DIR=D:\Dev\Libs\date
> g++ -std=c++20 -I %DATE_DIR%\include %DATE_DIR%\src\tz.cpp -c -o tz.o -DINSTALL=.
> g++ -std=c++20 -I %DATE_DIR%\include localTime.cpp tz.o -lole32 -o localTime.exe

평범한 컴파일 및 링크 명령들이니 설명은 필요 없겠지요. 특이 사항이라면 -DINSTALL=.인데, 이렇게 하면 tz.cpp는 현재 디렉터리의 tzdata 하위 디렉터리에서 시간대 데이터베이스를 찾습니다.3 고정된 위치를 사용하려면 -DINSTALL=C:\Data 등 절대 경로를 지정하면 됩니다. INSTALL 매크로 상수를 아예 정의하지 않으면 운영체제의 기본 다운로드 디렉터리가 쓰입니다. 하위 디렉터리 이름 tzdata는 고정입니다.

그리고 Windows에서는 실행 파일 빌드 시 tz.o와 함께 ole32.lib도 링크해야 합니다.

이제 localTime.exe를 실행하면 실행은 되는데 끝에서 실행 시점 예외가 발생합니다.

> localTime.exe

UTC  time
  2021-11-26 09:51:08.381669300
  2021-11-26 09:51:08

Local time
terminate called after throwing an instance of 'std::runtime_error'
  what():  Timezone database not found at ".\tzdata"

tzdata 디렉터리에 시간대 데이터베이스가 없어서 생긴 오류이므로, INSTALL로 지정한 디렉터리에 tzdata라는 이름의 하위 디렉터리를 만든 후 시간대 데이터베이스 파일들을 채워 넣으면 문제가 해결됩니다.

tzdata 디렉터리에 필요한 파일은 다음 두 가지입니다.

tzdata 디렉터리에 tzdata-latest.tar.gz를 풀어 넣고, windowsZones.xml(Windows에서만 필요한 것으로 보입니다)도 집어넣은 후 localTime.exe를 실행하면 이제는 정상적인 결과가 나옵니다.

> localTime.exe
UTC  time
  2021-11-26 09:51:08.381669300
  2021-11-26 09:51:08

Local time
  2021-11-26 18:51:08.381669300 KST
  2021-11-26 18:51:08

보너스: 컴파일 시 tz.o를 사용하는 게 뽀대(?)가 안 난다면 다음과 같이 라이브러리 파일을 만들어 사용하세요.

> ar crv libdate-tz.a tz.o

  1. 원서는 Rainer Grimm의 C++20: Get the Details 

  2. 원서를 저술하던 2021년 8월 당시 기준의 이야기입니다. 

  3. tzdata 디렉터리를 컴파일할 때 고정하는 것이 아니라 환경 변수로 지정할 수 있게 하면 좋을 것 같은데, 혹시 능력이 되는 분은 패치를 만들어서 해당 깃허브 저장소에 PR을 보내 보시길! 

태그: 프로그래밍 C++ C++20
comments powered by Disqus