2017-08-09 112 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