2017-08-09 10 views
1

J'essaie de convertir une heure dans une variable de time_t en struct tm* dans un fuseau horaire autre que le fuseau horaire local.C++: conversion de l'heure Unix en fuseau horaire non local

bâtiment sur this post, qui traite de l'opération inverse allant struct tm*-time_t, j'ai écrit la fonction suivante:

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; 

} 

Cependant, la conversion échoue, et je reçois

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 

-à-dire L'heure locale est définie sur l'heure UTC au lieu de UTC + 2 pour CEST.

Répondre

2

Les abréviations de fuseau horaire indiquées par date et d'autres outils ne sont pas les noms des fuseaux horaires. La base de données de fuseaux horaires de l'IANA utilise plutôt une grande ville dans la région concernée. La raison en est que les abréviations ne sont pas uniques et ne transmettent pas suffisamment d'informations pour convertir les dates passées car les lieux changent de fuseau horaire. En outre, POSIX spécifie que la variable TZ doit être analysée d'une manière spécifique, et il suggère plutôt de traiter une abréviation de fuseau horaire simple comme UTC (mais avec une abréviation différente). Au lieu de cela, vous devez utiliser le nom de fuseau horaire réel, tel que Europe/Berlin ou un spécificateur de fuseau horaire POSIX, tel que CET-1CEST.

1

Je fournis une réponse supplémentaire au cas où quelqu'un voudrait le faire d'une manière qui soit sûre pour les threads (sans définir de variable globale comme une variable d'environnement). Cette réponse nécessite C++ 11 (ou mieux) et Howard Hinnant's free, open-source timezone library, qui a été porté sur linux, macOS et Windows.

Cette bibliothèque se base sur <chrono>, en l'étendant aux calendriers et aux fuseaux horaires. On peut s'interfacer avec l'API de synchronisation C telle que struct tm avec cette bibliothèque, mais de telles fonctionnalités ne sont pas intégrées à la bibliothèque. Cette bibliothèque rend l'API C inutile, sauf si vous devez vous interfacer avec un autre code qui utilise l'API C.

Par exemple, il est un type appelé date::zoned_seconds que les couples un system_clock::time_point (sauf avec précision seconds) avec un date::time_zone pour créer l'heure locale dans une zone de temps arbitraire. Comme described in more detail here, voici comment vous pouvez convertir un zoned_seconds en std::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; 
} 

L'utilisation de ce bloc de construction, il devient presque trivial de convertir un time_t à un tm dans une zone de temps arbitraire (sans définir global):

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))}); 
} 

Si vous utilisez 17 C++, vous pouvez supprimer using namespace date comme floor<seconds> sera complètement prévu à l'intérieur namespace std::chrono.

Le code ci-dessus crée un zoned_seconds du stringtimezone et un system_clock::time_point dérivé du time_tt. Le zoned_seconds est conceptuellement un pair<time_zone, system_clock::time_point> mais de précision arbitraire.Et il peut également être considéré comme un pair<time_zone, local_time> puisque le time_zone sait comment faire la carte entre l'UTC et l'heure locale. Donc, la plupart du code écrit par l'utilisateur ci-dessus consiste simplement à extraire l'heure locale du zoned_seconds et à le placer dans un struct tm.

Le code ci-dessus peut être exercé comme ceci:

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

Si vous n'avez pas besoin le résultat dans un tm mais peut rester à la place dans le système <chrono>, il existe de très belles caractéristiques du timezone library pour l'analyse syntaxique et le formatage du zoned_seconds, par exemple:

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

sortie de l'échantillon:

2017-08-10 16:50:28 AKDT