2009-02-09 9 views
24

Ich versuche eine Zeitinformation, die ich als UTC-String empfange, in einen Zeitstempel zu konvertieren, der std::mktime in C++ verwendet. Mein Problem ist, dass in <ctime>/<time.h> es keine Funktion gibt, um zu UTC zu konvertieren; mktime gibt nur den Zeitstempel als lokale Zeit zurück.std :: mktime und timezone info

Also muss ich den Zeitzonen-Offset herausfinden und berücksichtigen, aber ich kann keinen plattformunabhängigen Weg finden, der den gesamten Code nicht auf boost::date_time portiert. Gibt es eine einfache Lösung, die ich übersehen habe?

+1

„Mktime nur den Zeitstempel als Ortszeit zurück“ - klar zu sein, ist der Zeitstempel ein UNIX-Zeitstempel (für wen Zeitzonen sind völlig irrelevant). Was passiert, ist, dass 'mktime' seinen _input_ als lokale Zeit interpretiert. –

Antwort

4

mktime geht davon aus, dass der Datumswert in der lokalen Zeitzone liegt. So können Sie die Zeitzonen-Umgebungsvariable vorher ändern (setenv) und die UTC-Zeitzone abrufen.

Windows tzset

können auch versuchen, an verschiedenen hausgemachten utc-mktimes, mktime-utcs, etc.

+0

Danke! Ich habe so etwas in einem Google-Ergebnis gesehen, aber funktioniert es unter Windows? – VolkA

+0

Die Verbindung funktionierte, ich fand sofort eine mkgmtime(). –

1

Die tm Struktur suchen, die von mktime hat eine Zeitzone-Feld.
Was passiert, wenn Sie "UTC" in das Feld "timzone" eingeben?

http://www.delorie.com/gnu/docs/glibc/libc_435.html

+1

Ja, das sieht für Linux einfach aus, aber Microsoft stimmt nicht überein - sie stellen stattdessen eine _mkgmtime Funktion zur Verfügung ... – VolkA

+3

Gemäß der ['mktime()' manpage] (http://www.kernel.org/ doc/man-pages/online/pages/man3/mktime.3.html), hat die 'struct tm' kein Zeitzonenfeld. Laut dem glibc-Dokument, auf das Sie verwiesen haben, ist "Like tm_gmtoff" dieses Feld eine BSD- und GNU-Erweiterung und in einer strikten ISO-C-Umgebung nicht sichtbar. " –

18
timestamp = mktime(&tm) - _timezone; 

oder plattformunabhängige Art und Weise:

timestamp = mktime(&tm) - timezone; 

Wenn Sie in der Quelle Mktime aussehen():

http://www.raspberryginger.com/jbailey/minix/html/mktime_8c-source.html

in Zeile 00117 die Zeit umgewandelt zur Ortszeit:

seconds += _timezone; 
+0

Das ist nur eine Implementierung. Woher bekommst du '_timezone' aus dem aufrufenden Code? –

+2

Ich benutzte einfach die "Zeitzone" (nicht die "_timezone"). Die "Zeitzone" ist definiert in . – Serg

+0

erste Zeile Ihrer Antwort –

6

mktime() verwendet tzname zum Ermitteln der Zeitzone. tzset() initialisiert die Variable tzname aus der TZ-Umgebungsvariablen. Wenn die TZ-Variable in der Umgebung angezeigt wird, ihr Wert jedoch leer ist oder ihr Wert nicht korrekt interpretiert werden kann, wird UTC verwendet.

Tragbares (nicht THREAD) Version nach dem timegm manpage

#include <time.h> 
    #include <stdlib.h> 

    time_t 
    my_timegm(struct tm *tm) 
    { 
     time_t ret; 
     char *tz; 

     tz = getenv("TZ"); 
     setenv("TZ", "", 1); 
     tzset(); 
     ret = mktime(tm); 
     if (tz) 
      setenv("TZ", tz, 1); 
     else 
      unsetenv("TZ"); 
     tzset(); 
     return ret; 
    } 

Eric S. Raymond hat eine Thread-Version in seinem Artikel veröffentlicht Time, Clock, and Calendar Programming In C

time_t my_timegm(register struct tm * t) 
/* struct tm to seconds since Unix epoch */ 
{ 
    register long year; 
    register time_t result; 
#define MONTHSPERYEAR 12  /* months per calendar year */ 
    static const int cumdays[MONTHSPERYEAR] = 
     { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; 

    /*@ +matchanyintegral @*/ 
    year = 1900 + t->tm_year + t->tm_mon/MONTHSPERYEAR; 
    result = (year - 1970) * 365 + cumdays[t->tm_mon % MONTHSPERYEAR]; 
    result += (year - 1968)/4; 
    result -= (year - 1900)/100; 
    result += (year - 1600)/400; 
    if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0) && 
     (t->tm_mon % MONTHSPERYEAR) < 2) 
     result--; 
    result += t->tm_mday - 1; 
    result *= 24; 
    result += t->tm_hour; 
    result *= 60; 
    result += t->tm_min; 
    result *= 60; 
    result += t->tm_sec; 
    if (t->tm_isdst == 1) 
     result -= 3600; 
    /*@ -matchanyintegral @*/ 
    return (result); 
} 
+1

Siehe auch [diese Antwort auf eine andere Frage] (http://stackoverflow.com/a/6474100/60075), die sagt, 'setenv (" TZ "," UTC ", 1) zu verwenden;' stattdessen, so dass es arbeite unter Windows. –

+4

Beachten Sie, dass diese Lösung OH GOTT ist, also NICHT GEWINDESCHNELL. Wenn Sie das aus zwei verschiedenen Threads aufrufen möchten, müssen Sie es in einer Mutexsperre umbrechen. – Quuxplusone

+0

Es ist nicht möglich, irgendetwas zu machen, das von Umgebungsvariablen thread safe abhängt :-( – Lothar

2

Ich habe das gleiche Problem letzten Tag und suche im doc "man mktime":

Die Funktionen mktime() und timegm() konvertieren die ausgebrochene Zeit (in den s tructure mit * timeptr) in einen Zeitwert mit der gleichen Kodierung wie die der von der Funktion time (3) zurückgegebenen Werte (Sekunden aus der Epoche, UTC). Die Funktion mktime() interpretiert die Eingabestruktur nach der aktuellen Zeitzoneneinstellung (siehe tzset (3)). Die Funktion timegm() interpretiert die Eingabestruktur als universelle koordinierte Zeit (UTC).

Kurz:

sollten Sie verwenden timegm statt Mktime zu verwenden.

Grüße,

Pai

4

Wenn Sie dies versuchen, in einem Multi-Thread-Programm zu tun und wollen nicht mit Sperren und Entsperren mutexes umgehen (wenn Sie die Umgebungsvariable-Methode verwenden, müssten Sie to), gibt es eine Funktion namens timegm, die dies tut. Es ist nicht tragbar, so ist hier die Quelle: http://trac.rtmpd.com/browser/trunk/sources/common/src/platform/windows/timegm.cpp

int is_leap(unsigned y) { 
    y += 1900; 
    return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0); 
} 

time_t timegm (struct tm *tm) 
{ 
    static const unsigned ndays[2][12] = { 
     {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, 
     {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} 
    }; 
    time_t res = 0; 
    int i; 

    for (i = 70; i < tm->tm_year; ++i) 
     res += is_leap(i) ? 366 : 365; 

    for (i = 0; i < tm->tm_mon; ++i) 
     res += ndays[is_leap(tm->tm_year)][i]; 
    res += tm->tm_mday - 1; 
    res *= 24; 
    res += tm->tm_hour; 
    res *= 60; 
    res += tm->tm_min; 
    res *= 60; 
    res += tm->tm_sec; 
    return res; 
} 
+0

Dies könnte eine Out-of-Bounds-Exception auslösen, wenn 'tm_mon' in einem Schaltjahr auf 12 gesetzt wird, einer gemeinsamen Semantik für Januar nächsten Jahres in' mktime' (und wenn dies nicht der Fall ist, ist das Ergebnis wahrscheinlich sehr falsch). Es macht viele Schleifen und Funktionsaufrufe (57 im Dezember 2015) und noch teurere Modulo-Operationen (fast 100). Es wird angenommen, dass "time_t" in Sekunden ist, was nicht in Stein gemeißelt ist. – fgrieu

2

Verwendung _mkgmtime, es kümmert sich um alles.

+1

Und wo könnte das verfügbar und dokumentiert sein? –

3

Hier ist ein einfaches, getestetes, hoffentlich tragbares Stück Code, das von struct tm in Sekunden seit Beginn eines einstellbaren UTC Jahres konvertiert, ohne temporäre Änderung der Zeitzone.

// Conversion from UTC date to second, signed 64-bit adjustable epoch version. 
// Written by François Grieu, 2015-07-21; public domain. 

#include <time.h>     // needed for struct tm 
#include <stdint.h>     // needed for int_least64_t 
#define MY_EPOCH 1970   // epoch year, changeable 
typedef int_least64_t my_time_t; // type for seconds since MY_EPOCH 

// my_mktime converts from struct tm UTC to non-leap seconds since 
// 00:00:00 on the first UTC day of year MY_EPOCH (adjustable). 
// It works since 1582 (start of Gregorian calendar), assuming an 
// apocryphal extension of Coordinated Universal Time, until some 
// event (like celestial impact) deeply messes with Earth. 
// It strive to be strictly C99-conformant. 
// 
// input: Pointer to a struct tm with field tm_year, tm_mon, tm_mday, 
//   tm_hour, tm_min, tm_sec set per mktime convention; thus 
//   - tm_year is year minus 1900; 
//   - tm_mon is [0..11] for January to December, but [-2..14] 
//   works for November of previous year to February of next year; 
//   - tm_mday, tm_hour, tm_min, tm_sec similarly can be offset to 
//   the full range [-32767 to 32767]. 
// output: Number of non-leap seconds since beginning of the first UTC 
//   day of year MY_EPOCH, as a signed at-least-64-bit integer. 
//   The input is not changed (in particular, fields tm_wday, 
//   tm_yday, and tm_isdst are unchanged and ignored). 
my_time_t my_mktime(const struct tm * ptm) { 
    int m, y = ptm->tm_year+2000; 
    if ((m = ptm->tm_mon)<2) { m += 12; --y; } 
// compute number of days within constant, assuming appropriate origin 
#define MY_MKTIME(Y,M,D) ((my_time_t)Y*365+Y/4-Y/100*3/4+(M+2)*153/5+D) 
    return (((MY_MKTIME(y   , m, ptm->tm_mday) 
       -MY_MKTIME((MY_EPOCH+99), 12, 1   ) 
      )*24+ptm->tm_hour)*60+ptm->tm_min)*60+ptm->tm_sec; 
#undef MY_MKTIME // this macro is private 
    } 

Key Beobachtungen große Vereinfachung, um den Code in this und that Antworten im Vergleich erlaubt:

  • Nummerierungs Monate ab März alle Monate mit Ausnahme der vor diesem Ursprung Wiederholung mit einem Zyklus von 5 Monaten 153 in Höhe von insgesamt Tage abwechselnd 31 und 30 Tage, so dass für beliebige Monat, und ohne Berücksichtigung von Schaltjahren, die Anzahl der Tage seit dem letzten Februar (innerhalb einer Konstante) unter Verwendung einer entsprechenden Konstante, Multiplikation mit 153 und berechnet werden kann ganzzahlige Division durch 5;
  • Die Korrektur in Tagen, die die Regel für das Schaltjahr auf Jahre multiple-of-100 berücksichtigt (die von Ausnahme zu multiple-of-4-Regeln nicht springt, außer wenn multiple von 400) kann berechnet werden (innerhalb einer Konstante) durch Addition einer entsprechenden Konstanten, ganzzahlige Division durch 100, Multiplikation mit 3 und ganzzahlige Division durch 4;
  • können wir die Korrektur für jede Epoche mit der gleichen Formel berechnen, die wir in der Hauptberechnung verwenden, und können dies mit einem Makro tun, so dass diese Korrektur zur Kompilierungszeit berechnet wird.

ist hier eine andere Version nicht 64-Bit-Unterstützung erforderlich ist, bis 1970 Herkunft gesperrt.

// Conversion from UTC date to second, unsigned 32-bit Unix epoch version. 
// Written by François Grieu, 2015-07-21; public domain. 

#include <time.h>     // needed for struct tm 
#include <limits.h>     // needed for UINT_MAX 
#if UINT_MAX>=0xFFFFFFFF   // unsigned is at least 32-bit 
typedef unsigned  my_time_t; // type for seconds since 1970 
#else 
typedef unsigned long my_time_t; // type for seconds since 1970 
#endif 

// my_mktime converts from struct tm UTC to non-leap seconds since 
// 00:00:00 on the first UTC day of year 1970 (fixed). 
// It works from 1970 to 2105 inclusive. It strives to be compatible 
// with C compilers supporting // comments and claiming C89 conformance. 
// 
// input: Pointer to a struct tm with field tm_year, tm_mon, tm_mday, 
//   tm_hour, tm_min, tm_sec set per mktime convention; thus 
//   - tm_year is year minus 1900 
//   - tm_mon is [0..11] for January to December, but [-2..14] 
//   works for November of previous year to February of next year 
//   - tm_mday, tm_hour, tm_min, tm_sec similarly can be offset to 
//   the full range [-32767 to 32768], as long as the combination 
//   with tm_year gives a result within years [1970..2105], and 
//   tm_year>0. 
// output: Number of non-leap seconds since beginning of the first UTC 
//   day of year 1970, as an unsigned at-least-32-bit integer. 
//   The input is not changed (in particular, fields tm_wday, 
//   tm_yday, and tm_isdst are unchanged and ignored). 
my_time_t my_mktime(const struct tm * ptm) { 
    int m, y = ptm->tm_year; 
    if ((m = ptm->tm_mon)<2) { m += 12; --y; } 
    return ((((my_time_t)(y-69)*365u+y/4-y/100*3/4+(m+2)*153/5-446+ 
     ptm->tm_mday)*24u+ptm->tm_hour)*60u+ptm->tm_min)*60u+ptm->tm_sec; 
    } 
0

ich den folgenden Code verwendet, um einen Zeitstempel in UTC zu generieren:

#include <iostream> 
#include <sstream> 
#include <chrono> 
using namespace std; 

string getTimeStamp() { 
    time_t now = time(NULL); 
    tm* gmt_time = gmtime(&now); 
    ostringstream oss; 
    oss << put_time(gmt_time, "%Y-%m-%d %H:%M:%S"); 
    return oss.str(); 
}