2017-02-16 6 views
0

J'essaie d'obtenir l'année en cours stockée dans une date d'avant 1970 en utilisant un std::chrono::time_point<std::chrono::system_clock>, mais j'ai rencontré un problème concernant la lecture de son contenu dans une struct std::tm.localtime_s échoue où gmtime_s réussit avec des dates avant le 1-1-1970

Je convertis le time_point en un time_t d'abord, après quoi j'ai lu ses valeurs pour obtenir la valeur tm_year. Toutefois, lorsque vous essayez de le faire, le code échoue lors de l'utilisation localtime_s, mais il réussit lorsque j'utilise gmtime_s. C'est seulement pour les dates avant le 1-1-1970, les dates après cela fonctionnent bien en utilisant les deux fonctions.

Le code ci-dessous reproduit l'erreur. Si terstGmTimeVsLocalTime est appelé avec utc=true cela fonctionne, s'il est appelé avec utc=false il ne produit pas la bonne sortie.

#include <iomanip> 
#include <time.h> 
#include <iostream> 

void testGmTimeVsLocaltime(const bool& utc) { 
    // Create time 
    std::tm timeInfoWrite = std::tm(); 
    timeInfoWrite.tm_year = 1969 - 1900; // Year to parse, here it is 1969 
    timeInfoWrite.tm_mon = 0; 
    timeInfoWrite.tm_mday = 1; 
    timeInfoWrite.tm_hour = 1; 
    timeInfoWrite.tm_min = 0; 
    timeInfoWrite.tm_sec = 0; 
    timeInfoWrite.tm_isdst = -1; 

    std::chrono::time_point<std::chrono::system_clock> timePoint = std::chrono::system_clock::from_time_t(utc ? _mkgmtime(&timeInfoWrite) : std::mktime(&timeInfoWrite)); 

    // Convert to time_t 
    std::time_t timeT = std::chrono::system_clock::to_time_t(timePoint); 

    // Read values 
    std::tm timeInfoRead; 
    if (utc) { 
     gmtime_s(&timeInfoRead, &timeT); 
    } else { 
     localtime_s(&timeInfoRead, &timeT); 
    } 

    // Output result 
    std::cout << (timeInfoRead.tm_year + 1900) << '\n'; 

    // Wait for input 
    std::getchar(); 
} 

int main() { 
    testGmTimeVsLocaltime(true); // Set to false to show bug 

    return 0; 
} 

utc=true sorties 1969, comme on pouvait s'y attendre. Cependant, utc=false sort 1899 (probablement parce qu'une erreur se produit et que tm_year est mis à -1).

Y at-il quelque chose qui me manque? The documentation ne spécifie pas spécifiquement que localtime_s doit échouer pour les dates antérieures à 1-1-1970.

Je suis sur Windows 10 x64 si cela fait une différence.

+0

Les documents MSDN suggèrent qu'il échouera: "Avant minuit, 1er janvier 1970." Voir https://msdn.microsoft.com/en-us/library/bf12f0hc.aspx –

+0

@RichardCritten Ah, je vois que j'ai dû utiliser une implémentation spécifique au système d'exploitation. Je n'ai pas réalisé 'localtime_s' ne faisait pas partie de la norme. Y aurait-il une autre fonction qui ne manquerait pas pour les valeurs avant minuit, le 1er janvier 1970? Ou peut-être une autre stratégie? Je suppose que je pourrais récupérer la différence entre UTC et localtime, utilisez simplement la fonction 'gmtime_s', puis ajoutez la différence au résultat lorsque l'heure locale est requise, mais il semble que cela pourrait être fait plus simplement. – Qub1

+0

Les documents que vous liez indiquent _ "Convertit le temps donné depuis __epoch __" _; où est l'époque où la mise en œuvre décide que le temps commence.Sous Windows, il y a des appels OS qui fonctionneront n'importe quand, mais je ne connais pas d'API standard. –

Répondre

1

En utilisant Howard Hinnant's free, open-source date lib, vous pouvez complètement pas de côté le maladroit, erreur et sujettes aux bugs C api, et de travailler directement avec un <chrono> système moderne basé:

#include "chrono_io.h" 
#include "date.h" 
#include <iostream> 

void 
testGmTimeVsLocaltime() 
{ 
    using namespace date; 
    // Create time 
    auto timeInfoWrite = 1969_y/jan/1; 
    sys_days timePoint = timeInfoWrite; // this is a chrono::time_point 
    std::cout << timePoint.time_since_epoch() << '\n'; // -365 days 

    // Convert to time_t 
    // no need 

    // Read values 
    year_month_day timeInfoRead = timePoint; 

    // Output result 
    std::cout << timeInfoRead.year() << '\n'; 
} 

int 
main() 
{ 
    testGmTimeVsLocaltime(); 
} 

Sortie:

-365[86400]s 
1969 

Il existe des littéraux pour faciliter le remplissage d'une structure year_month_day qui est l'analogue des parties de l'année, du mois et du jour d'un tm. Vous pouvez facilement convertir cela en (sys_days). C'est la même chose qu'un system_clock::time_point, mais avec une précision de jours à la place. Il sera lui-même implicitement convertir en une précision de secondes time_point (typedef'd à sys_seconds), ou à un system_clock::time_point. Ci-dessus, je viens de sortir son time_since_epoch() qui montre qu'il est -365 jours avant l'époque.

Il n'y a jamais vraiment besoin de convertir en structures de données API C, mais c'est facile si vous le souhaitez. Par exemple, en supposant time_t est secondes depuis 1970-01-01:

std::time_t timeT = sys_seconds{timePoint}.time_since_epoch().count(); 
std::cout << timeT << '\n'; 

qui délivre en sortie:

-31536000 

La conversion inverse (retour à year_month_day) est tout aussi facile.Si vous voulez convertir timeT il est juste un peu plus impliqué:

year_month_day timeInfoRead = floor<days>(sys_seconds{seconds{timeT}}); 

Ce premier convertit time_t à chrono::seconds, puis à un seconds -precsion time_point, puis à un days -precsion time_point, enfin à la year_month_day type de champ (tm-like).

Enfin year_month_day a une fonction de membre getter year() qui est en streaming. Vous pouvez convertir explicitement year à int si on le souhaite:

int{timeInfoRead.year()} 

Mais je pense qu'il vaut mieux garder les choses comme les années, les mois et les jours comme types distincts de sorte que le compilateur peut vous aider à attraper quand vous les mélanger accidentellement. Enfin, si vous voulez vraiment dire que vous voulez 1969-01-01 00:00:00 dans le fuseau horaire local de votre ordinateur, there's a library pour le faire aussi bien. Et ce n'est qu'une modification mineure du programme simple ci-dessus.

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

void 
testGmTimeVsLocaltime() 
{ 
    using namespace date; 
    using namespace std::chrono; 
    // Create time 
    auto timeInfoWrite = 1969_y/jan/1; 
    auto timePoint = make_zoned(current_zone(), local_days{timeInfoWrite}); 

    // Convert to time_t 
    std::time_t timeT = timePoint.get_sys_time().time_since_epoch().count(); 
    std::cout << timeT << '\n'; 

    // Read values 
    timePoint = sys_seconds{seconds{timeT}}; 
    year_month_day timeInfoRead{floor<days>(timePoint.get_local_time())}; 

    // Output result 
    std::cout << timeInfoRead.year() << '\n'; 
} 

int 
main() 
{ 
    testGmTimeVsLocaltime(); 
} 

Sortie:

-31518000 
1969 

Maintenant vous créez un zoned_seconds en utilisant le fuseau horaire de l'ordinateur current_zone(), et la conversion de votre timeInfoWrite-local_days au lieu de sys_days. Vous pouvez obtenir l'heure locale ou l'heure système sur timePoint. Pour convertir en time_t, le temps du système le plus logique:

std::time_t timeT = timePoint.get_sys_time().time_since_epoch().count(); 

Et maintenant la sortie (pour moi) est 5 h plus tard (18000s).

-31518000 

Vous pouvez revenir soit l'année locale, ou le système (UTC) année, soit en utilisant .get_local_time() ou .get_sys_time(). Pour moi, cela ne fait aucune différence ("America/New_York"). Mais si vous êtes dans "Australia/Sydney", vous obtiendrez 1968 si vous demandez l'année UTC au lieu de 1969. Et tout cela est très facile à simuler en remplaçant simplement "Australia/Sydney" ou "America/New_York" par current_zone() dans le programme ci-dessus.

Oui, cela fonctionne sur Windows, VS-2013 et versions ultérieures. Une installation est requise pour la librairie timezone: