2010-11-09 87 views
8

tengo una cadena con el siguiente formato:C++ en la conversión de una cadena de tiempo de segundos desde la época

2010-11-04T23: 23: 01Z

La Z indica que es el momento UTC.
Preferiría almacenar esto como un tiempo de época para facilitar la comparación.

¿Cuál es el método recomendado para hacer esto?

la actualidad (después de una búsqueda Quck) es el algoritmo simplista:

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() en mi CRT. Ymmv. –

+0

@Loki, no necesitamos una metaetiqueta para capturar todas las cosas que son OS-neutral. Por favor deja de volver a crear la etiqueta. – Charles

+0

@Charles: Por favor, deja de cambiar mi pregunta. Hubiera pensado que tendrías la pista de que la edición no es válida después de la segunda vez que la cambié. –

Respuesta

6

Usando C++ 11 funcionalidad que ahora podemos utilizar corrientes a veces analizar:

El iomanip std::get_time va a convertir una cadena basada en un conjunto de parámetros de formato y los convierte en un objeto de struct tz.

Puede usar std::mktime() para convertir esto en un valor de época.

#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

Se podría utilizar el boost::date_time y escribir un pequeño programa de análisis manual de (probablemente basada regexp-) para sus cadenas.

4

Puede usar una función como strptime para convertir una cadena en struct tm, en lugar de analizarla manualmente.

3

No es un duplicado exacto pero la respuesta de @ Cubbi es here útil, apuesto. Esto asume específicamente la entrada UTC.

Boost también es compatible con la conversión directa de ISO 8601 a través de boost::posix_time::from_iso_string que llama a boost::date_time::parse_iso_time, aquí de nuevo simplemente quitaría la 'Z' posterior y trataría el TZ como UTC implícito.

#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

Este es el formato ISO8601. Puede usar la función strptime para analizarlo con el argumento %FT%T%z. No forma parte del estándar C++, aunque puede usar la implementación de código abierto (this, por ejemplo).

1

El problema aquí es que mktime usa la hora local no la hora UTC.

¿Qué le parece simplemente calcular la diferencia de tiempo entre UTC y hora local, y luego agregarlo al valor devuelto por mktime?

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

¿Qué pasa con strptime()?

Y en Linux, incluso de llegar a los 'segundos al este de UTC' campo que el alivio de cualquier necesidad de analizar:

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

que para mí produce

/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

Ese valor "Segundos al este de UTC" es claramente falso, ya que el valor mostrado es superior a 4 millones de años. Las zonas horarias deben estar dentro de un rango de +/- 50000 segundos de UTC. – caf

+0

Darn. Tienes razón. E incluso cuando defino _BSD_SOURCE (que mencionó la página del manual 'mktime/ctime') sigue apareciendo como demasiado grande. Me falta algo más. –

0

X/Open proporciona una variable global timezone que indica el número de segundos que la hora local está detrás de UTC. Usted puede usar esto para ajustar la salida 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

problema aquí es que mktime utiliza hora local, no el tiempo UTC.

Linux proporciona timegm que es lo que desea (es decir, mktime para la hora UTC).

Aquí está mi solución, que forcé a aceptar solo "Zulu" (Z zona horaria). Observe que strptime en realidad no parece analizar la zona horaria correctamente, incluso aunque glib parece tener algo de apoyo para eso. Es por eso que acabo de lanzar una excepción si la cadena no termina 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

Nueva respuesta a una vieja pregunta. Justificación de una nueva respuesta: en caso de que desee utilizar los tipos <chrono> para resolver un problema como este.

Además de C++ 11/C++ 14, necesitará este free, open source date/time library:

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

Este programa:

seconds from epoch is 1288912981s 

Si el análisis sintáctico no de cualquier manera, se lanzará una excepción. Si prefiere no lanzar excepciones, no lo haga is.exceptions(std::ios::failbit);, sino que busque is.fail().

Cuestiones relacionadas