2010-11-09 15 views
8

Ho una stringa con il seguente formato:C++ Conversione di una stringa tempo di secondi di epoca

2010-11-04T23: 23: 01Z

La Z indica che il tempo è UTC.
Preferirei archiviarlo come un'epoca in cui semplificare il confronto.

Qual è il metodo consigliato per farlo?

Attualmente (dopo una ricerca quck) l'algoritmo simplist è:

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() nel mio CRT. YMMV. –

+0

@Loki, non abbiamo bisogno di un metatag per catturare tutto ciò che è neutrale al sistema operativo. Si prega di smettere di ricreare il tag. – Charles

+0

@Charles: si prega di smettere di cambiare la mia domanda. Avrei però pensato che tu avessi il suggerimento di ritenere che la modifica non sia valida dopo la seconda volta che l'ho modificata. –

risposta

6

Uso C++ 11 funzionalità ora possiamo utilizzare flussi per analizzare volte:

L'iomanip std::get_time converte una stringa basata su un insieme di parametri di formato e convertirli in un oggetto struct tz.

È quindi possibile utilizzare std::mktime() per convertirlo in un valore di epoca.

#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

Si potrebbe utilizzare il boost::date_time e scrivere un piccolo parser manuale (probabilmente regexp-based) per le stringhe.

4

È possibile utilizzare una funzione come strptime per convertire una stringa in struct tm, anziché analizzarla manualmente.

3

Non è un duplicato esatto ma troverete la risposta di @ Cubbi da here utile, scommetto. Questo assume specificamente l'input UTC.

Boost supporta anche la conversione diretta da ISO 8601 tramite boost::posix_time::from_iso_string che chiama boost::date_time::parse_iso_time, di nuovo si dovrebbe semplicemente rimuovere la "Z" finale e trattare la TZ come UTC implicito.

#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

Questo è il formato ISO8601. È possibile utilizzare la funzione strptime per analizzarla con l'argomento %FT%T%z. Non è una parte dello standard C++ sebbene sia possibile utilizzarne l'implementazione open source (this, ad esempio).

1

Il problema è che mktime utilizza l'ora locale non l'ora UTC.

Come calcolare la differenza di orario tra UTC e ora locale, quindi aggiungerla al valore restituito da mktime?

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

Cosa c'è che non va con strptime()?

E su Linux, ancora di ottenere i 'secondi est di UTC' campo che si sollevando da ogni necessità di analizzare:

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

che per me cede

/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

Il valore "Secondi ad est di UTC" è chiaramente falso, poiché il valore visualizzato supera i 4 milioni di anni. I fusi orari dovrebbero essere entro un intervallo di +/- 50000 secondi di UTC. – caf

+0

Dann. Hai ragione. E anche quando definisco _BSD_SOURCE (che la pagina di manuale 'mktime/ctime' ha menzionato) risulta ancora troppo grande. Mi manca qualcos'altro. –

0

X/Open offre invece una variabile globale timezone che indica il numero di secondi in cui l'ora locale è dietro l'UTC. È possibile utilizzare questo per regolare l'uscita di 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

problema qui è che mktime usi non ora locale ora UTC.

Linux fornisce timegm che è ciò che si desidera (ad esempio, mktime per l'ora UTC).

Ecco la mia soluzione, che ho costretto ad accettare solo "Zulu" (Z timezone). Nota: che in realtà non sembra che il fuso orario sia stato analizzato correttamente, anche sebbene glib sembra avere qualche supporto per quello. Questo è il motivo per cui lancio un'eccezione se la stringa non termina in "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

Nuova risposta a una vecchia domanda. Motivazione per una nuova risposta: nel caso in cui si desideri utilizzare i tipi <chrono> per risolvere un problema come questo.

Oltre a C++ 11/C++ 14, avrete bisogno di questo free, open source date/time library:

uscite
#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"; 
} 

questo programma:

seconds from epoch is 1288912981s 

Se il parse non riesce in alcun modo, verrà generata un'eccezione. Se preferisci non generare eccezioni, non chiamare is.exceptions(std::ios::failbit);, ma controllare per is.fail().