2010-11-09 3 views
8

j'ai une chaîne avec le format suivant:C++ Conversion d'une chaîne de temps à quelques secondes de l'époque

2010-11-04T23: 23: 01Z

Le Z indique que le temps est UTC.
Je préfère stocker cela comme une époque pour faciliter la comparaison.

Quelle est la méthode recommandée pour cela?

Actuellement (après une recherche quck) l'algorithme de simplist est:

1: <Convert string to struct_tm: by manually parsing string> 
2: Use mktime() to convert struct_tm to epoch time. 

// Problem here is that mktime uses local time not UTC time. 
+0

_mkgmtime() dans mon CRT. Ymmv. –

+0

@Loki, nous n'avons pas besoin d'une balise meta pour attraper toutes les choses qui sont neutres sur le système d'exploitation. S'il vous plaît, arrêtez de recréer l'étiquette. – Charles

+0

@Charles: S'il vous plaît arrêter de changer ma question. J'aurais cependant eu l'indication que je pense que la modification est invalide après la deuxième fois que je l'ai changé. –

Répondre

6

En utilisant la fonctionnalité C++ 11 nous pouvons maintenant utiliser les flux pour analyser les temps:

Le iomanip std::get_time convertit une chaîne en fonction d'un ensemble de paramètres de format et de les convertir en un objet struct tz.

Vous pouvez ensuite utiliser std::mktime() pour convertir ceci en valeur d'époque.

#include <iostream> 
#include <sstream> 
#include <locale> 
#include <iomanip> 

int main() 
{ 
    std::tm t = {}; 
    std::istringstream ss("2010-11-04T23:23:01Z"); 

    if (ss >> std::get_time(&t, "%Y-%m-%dT%H:%M:%S")) 
    { 
     std::cout << std::put_time(&t, "%c") << "\n" 
        << std::mktime(&t) << "\n"; 
    } 
    else 
    { 
     std::cout << "Parse failed\n"; 
    } 
} 
1

Vous pouvez utiliser le boost::date_time et écrire un petit analyseur manuel (probablement à base de regexp-) pour vos cordes.

4

Vous pouvez utiliser une fonction telle que strptime pour convertir une chaîne en struct tm, au lieu de l'analyser manuellement.

3

Ce n'est pas un dup exact mais vous trouverez la réponse @ Cubbi de here utile, je parie. Cela suppose spécifiquement une entrée UTC. Boost supporte également la conversion directe d'ISO 8601 en boost::posix_time::from_iso_string qui appelle boost::date_time::parse_iso_time, là encore vous devez simplement supprimer le 'Z' final et traiter le TZ comme UTC implicite.

#include <iostream> 
#include <boost/date_time.hpp> 

namespace bt = boost::posix_time; 

const std::locale formats[] = { 
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d %H:%M:%S")), 
std::locale(std::locale::classic(),new bt::time_input_facet("%Y/%m/%d %H:%M:%S")), 
std::locale(std::locale::classic(),new bt::time_input_facet("%d.%m.%Y %H:%M:%S")), 
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d"))}; 
const size_t formats_n = sizeof(formats)/sizeof(formats[0]); 

std::time_t pt_to_time_t(const bt::ptime& pt) 
{ 
    bt::ptime timet_start(boost::gregorian::date(1970,1,1)); 
    bt::time_duration diff = pt - timet_start; 
    return diff.ticks()/bt::time_duration::rep_type::ticks_per_second; 

} 
void seconds_from_epoch(const std::string& s) 
{ 
    bt::ptime pt; 
    for(size_t i=0; i<formats_n; ++i) 
    { 
     std::istringstream is(s); 
     is.imbue(formats[i]); 
     is >> pt; 
     if(pt != bt::ptime()) break; 
    } 
    std::cout << " ptime is " << pt << '\n'; 
    std::cout << " seconds from epoch are " << pt_to_time_t(pt) << '\n'; 
} 
int main() 
{ 
    seconds_from_epoch("2004-03-21 12:45:33"); 
    seconds_from_epoch("2004/03/21 12:45:33"); 
    seconds_from_epoch("23.09.2004 04:12:21"); 
    seconds_from_epoch("2003-02-11"); 
} 
6

Il s'agit du format ISO8601. Vous pouvez utiliser la fonction strptime pour l'analyser avec l'argument %FT%T%z. Il ne fait pas partie de la norme C++, mais vous pouvez en utiliser l'implémentation open source (this, par exemple).

1

Le problème est que mktime utilise l'heure locale et non l'heure UTC.

Que diriez-vous juste de calculer le décalage horaire entre l'heure UTC et l'heure locale, puis de l'ajouter à la valeur renvoyée par mktime?

time_t local = time(NULL), 
     utc = mktime(gmtime(&local)); 
int diff = utc - local; 
1

Qu'est-ce qui ne va pas avec strptime()?

Et sous Linux, vous obtenez même les 'secondes est de UTC' champ soulageant de tout besoin d'analyser:

#define _XOPEN_SOURCE 
#include <iostream> 
#include <time.h> 

int main(void) { 

    const char *timestr = "2010-11-04T23:23:01Z"; 

    struct tm t; 
    strptime(timestr, "%Y-%m-%dT%H:%M:%SZ", &t); 

    char buf[128]; 
    strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S", &t); 

    std::cout << timestr << " -> " << buf << std::endl; 

    std::cout << "Seconds east of UTC " << t.tm_gmtoff << std::endl; 
} 

qui me cède

/tmp$ g++ -o my my.cpp 
/tmp$ ./my 
2010-11-04T23:23:01Z -> 04 Nov 2010 23:23:01 
Seconds east of UTC 140085769590024 
+1

Cette valeur «secondes à l'est de l'UTC» est clairement fausse, puisque la valeur indiquée est supérieure à 4 millions d'années. Les fuseaux horaires devraient se situer dans une plage de +/- 50000 secondes d'UTC. – caf

+0

Darn. Tu as raison. Et même lorsque je définis _BSD_SOURCE (que la page de manuel 'mktime/ctime' a mentionné), il apparaît toujours trop grand. Il me manque quelque chose d'autre. –

0

X/Open fournit une variable globale timezone qui indique le nombre de secondes que l'heure locale est derrière UTC. Vous pouvez l'utiliser pour régler la sortie de mktime():

#define _XOPEN_SOURCE 
#include <stdio.h> 
#include <time.h> 

/* 2010-11-04T23:23:01Z */ 
time_t zulu_time(const char *time_str) 
{ 
    struct tm tm = { 0 }; 

    if (!strptime(time_str, "%Y-%m-%dT%H:%M:%SZ", &tm)) 
     return (time_t)-1; 

    return mktime(&tm) - timezone; 
} 
3

problème ici est que mktime utilise l'heure locale pas de temps UTC.

Linux fournit timegm, ce qui correspond à ce que vous voulez (c'est-à-dire mktime pour l'heure UTC).

Voici ma solution, que j'ai forcé à accepter seulement "Zulu" (Z fuseau horaire). Notez que strptime ne semble pas réellement analyser le fuseau horaire correctement, même si glib semble avoir un certain soutien pour cela. C'est pourquoi je viens de lancer une exception si la chaîne ne se termine pas en 'Z'.

static double EpochTime(const std::string& iso8601Time) 
{ 
    struct tm t; 
    if (iso8601Time.back() != 'Z') throw PBException("Non Zulu 8601 timezone not supported"); 
    char* ptr = strptime(iso8601Time.c_str(), "%FT%T", &t); 
    if(ptr == nullptr) 
    { 
     throw PBException("strptime failed, can't parse " + iso8601Time); 
    } 
    double t2 = timegm(&t); // UTC 
    if (*ptr) 
    { 
     double fraction = atof(ptr); 
     t2 += fraction; 
    } 
    return t2; 
} 
2

Nouvelle réponse à une vieille question. Justification de la nouvelle réponse: Dans le cas où vous souhaitez utiliser les types <chrono> pour résoudre un problème comme celui-ci.

En plus de C++ 11/C++ 14, vous en aurez besoin free, open source date/time library:

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

int 
main() 
{ 
    std::istringstream is("2010-11-04T23:23:01Z"); 
    is.exceptions(std::ios::failbit); 
    date::sys_seconds tp; 
    date::parse(is, "%FT%TZ", tp); 
    std::cout << "seconds from epoch is " << tp.time_since_epoch().count() << "s\n"; 
} 

Ce programme affiche:

seconds from epoch is 1288912981s 

Si l'analyse échoue en aucune façon, une exception sera levée. Si vous préférez ne pas lancer d'exceptions, ne pas is.exceptions(std::ios::failbit);, mais plutôt is.fail().

Questions connexes