2017-08-09 111 views
1

我試圖將某個變量time_t中的時間轉換爲struct tm*,該時間不是本地時區。C++:將Unix時間轉換爲非本地時區

大廈this post,其中討論了反向操作,從struct tm*time_t,我寫了下面的功能:

struct tm* localtime_tz(time_t* t, std::string timezone) { 

    struct tm* ret; 
    char* tz; 

    tz = std::getenv("TZ"); // Store currently set time zone                               

    // Set time zone                                         
    setenv("TZ", timezone.c_str(), 1); 
    tzset(); 

    std::cout << "Time zone set to " << std::getenv("TZ") << std::endl; 

    ret = std::localtime(t); // Convert given Unix time to local time in time zone                          
    std::cout << "Local time is: " << std::asctime(ret); 
    std::cout << "UTC time is: " << std::asctime(std::gmtime(t)); 

    // Reset time zone to stored value                                     
    if (tz) 
    setenv("TZ", tz, 1); 
    else 
    unsetenv("TZ"); 
    tzset(); 

    return ret; 

} 

然而,轉換失敗,我也得到

Time zone set to CEST 
Local time is: Wed Aug 9 16:39:38 2017 
UTC time is: Wed Aug 9 16:39:38 2017 

即本地時間設置爲UTC時間,而不是CEST的UTC + 2。

回答

2

date和其他工具顯示的時區縮寫不是時區的名稱。 IANA時區數據庫使用相關地區內的主要城市。原因在於縮寫不是唯一的,並且由於地點切換時區,它們沒有傳達足夠的信息來轉換過去的日期。

此外,POSIX指定必須以特定方式解析變量TZ,並且它幾乎暗示了處理像UTC(但具有不同縮寫)的裸露時區縮寫。

相反,您必須使用實際時區名稱,例如Europe/Berlin或POSIX時區說明符,例如CET-1CEST

1

如果有人想以線程安全的方式執行此操作(無需設置全局環境變量),我提供了一個附加答案。這個答案需要C++ 11(或更好)和Howard Hinnant's free, open-source timezone library,它已經移植到linux,macOS和Windows。

此庫建立在<chrono>上,將其擴展到日曆和時區。可以使用此庫與C timing API(如struct tm)進行交互,但這些工具不是內置到庫中的。這個庫使C API不必要,除非你必須與使用C API的其他代碼進行交互。

例如有一個叫做date::zoned_seconds型耦合一個system_clock::time_point(除了seconds精度)與date::time_zone在任意時區創建本地時間。作爲described in more detail here,這裏是你如何可以轉換zoned_secondsstd::tm

std::tm 
to_tm(date::zoned_seconds tp) 
{ 
    using namespace date; 
    using namespace std; 
    using namespace std::chrono; 
    auto lt = tp.get_local_time(); 
    auto ld = floor<days>(lt); 
    time_of_day<seconds> tod{lt - ld}; // <seconds> can be omitted in C++17 
    year_month_day ymd{ld}; 
    tm t{}; 
    t.tm_sec = tod.seconds().count(); 
    t.tm_min = tod.minutes().count(); 
    t.tm_hour = tod.hours().count(); 
    t.tm_mday = unsigned{ymd.day()}; 
    t.tm_mon = unsigned{ymd.month()} - 1; 
    t.tm_year = int{ymd.year()} - 1900; 
    t.tm_wday = unsigned{weekday{ld}}; 
    t.tm_yday = (ld - local_days{ymd.year()/jan/1}).count(); 
    t.tm_isdst = tp.get_info().save != minutes{0}; 
    return t; 
} 

使用這個積木,它變得幾乎微不足道的一個time_t轉換爲tm在任意時間區(不設置一個全球性的):

std::tm 
localtime_tz(time_t t, const std::string& timezone) 
{ 
    using namespace date; 
    using namespace std::chrono; 
    return to_tm({timezone, floor<seconds>(system_clock::from_time_t(t))}); 
} 

如果您使用C++ 17,你可以刪除using namespace date作爲floor<seconds>將在namespace std::chrono完全提供。

上面的代碼建立從stringtimezone一個zoned_seconds並從time_tt衍生的system_clock::time_pointzoned_seconds在概念上是pair<time_zone, system_clock::time_point>,但具有任意精度。它也可以被認爲是pair<time_zone, local_time>,因爲time_zone知道如何在UTC和本地時間之間進行映射。所以上面大部分用戶編寫的代碼只是從zoned_seconds中提取本地時間,並將其放入struct tm

上面的代碼可以行使這樣的:

auto tm = localtime_tz(time(nullptr), "America/Anchorage"); 

如果您不需要導致tm而是可以留在<chrono>系統中,存在於timezone library非常不錯的功能解析和格式化zoned_seconds,例如:

cout << zoned_seconds{"America/Anchorage", floor<seconds>(system_clock::now())} << '\n'; 

示例輸出:

2017-08-10 16:50:28 AKDT