2012-06-27 10 views
8

Mam następujące liczby całkowite:data i czas konwersji liczb do time_t i określić strefę czasową

int y, mon, d, h, min, s; 

Ich wartości to: 2012, 06, 27, 12, 47, 53 odpowiednio. Chcę reprezentować czas daty "2012/06/27 12:47:53 UTC", jeśli wybrałem opcję "UTC" w innym miejscu w moim zgłoszeniu lub "2012/06/27 12:47:53 AEST", jeśli wybrałem "AEST" gdzie indziej w mojej aplikacji.

Chcę konwersji na time_t, a oto kod, że jestem obecny używając do tego:

struct tm timeinfo; 
timeinfo.tm_year = year - 1900; 
timeinfo.tm_mon = mon - 1; 
timeinfo.tm_mday = day; 
timeinfo.tm_hour = hour; 
timeinfo.tm_min = min; 
timeinfo.tm_sec = sec; 
//timeinfo.tm_isdst = 0; //TODO should this be set? 

//TODO find POSIX or C standard way to do covert tm to time_t without in UTC instead of local time 
#ifdef UNIX 
return timegm(&timeinfo); 
#else 
return mktime(&timeinfo); //FIXME Still incorrect 
#endif 

Więc używam tm struct i mktime, jednak to nie działa dobrze, ponieważ zawsze przyjmuje moją lokalną strefę czasową.

Jaki jest prawidłowy sposób wykonania tej czynności?

Poniżej znajduje się rozwiązanie, które do tej pory wymyśliłem. To w zasadzie czyni jedną z trzech rzeczy:

  1. Jeśli UNIX, wystarczy użyć timegm
  2. jeśli nie UNIX
    1. Albo, czy matematyki stosując różnicę między czasem UTC epoki i lokalnym epoki jako offset
      • Rezerwacja: Matematyka może być niepoprawna
    2. Lub ustaw tymczasowo zmienną środowiskową "TZ" na UTC
      • Rezerwacja: wyłączy się, jeśli/kiedy ten kod musi być wielowątkowy
namespace tmUtil 
{ 
    int const tm_yearCorrection = -1900; 
    int const tm_monthCorrection = -1; 
    int const tm_isdst_dontKnow = -1; 

#if !defined(DEBUG_DATETIME_TIMEGM_ENVVARTZ) && !(defined(UNIX) && !defined(DEBUG_DATETIME_TIMEGM)) 
    static bool isLeap(int year) 
    { 
     return 
      (year % 4) ? false 
      : (year % 100) ? true 
      : (year % 400) ? false 
      : true; 
    } 

    static int daysIn(int year) 
    { 
     return isLeap(year) ? 366 : 365; 
    } 
#endif 
} 

time_t utc(int year, int mon, int day, int hour, int min, int sec) 
{ 
    struct tm time = {0}; 
    time.tm_year = year + tmUtil::tm_yearCorrection; 
    time.tm_mon = mon + tmUtil::tm_monthCorrection; 
    time.tm_mday = day; 
    time.tm_hour = hour; 
    time.tm_min = min; 
    time.tm_sec = sec; 
    time.tm_isdst = tmUtil::tm_isdst_dontKnow; 

    #if defined(UNIX) && !defined(DEBUG_DATETIME_TIMEGM) //TODO remove && 00 
     time_t result; 
     result = timegm(&time); 
     return result; 
    #else 
     #if !defined(DEBUG_DATETIME_TIMEGM_ENVVARTZ) 
      //TODO check that math is correct 
      time_t fromEpochUtc = mktime(&time); 

      struct tm localData; 
      struct tm utcData; 
      struct tm* loc = localtime_r (&fromEpochUtc, &localData); 
      struct tm* utc = gmtime_r (&fromEpochUtc, &utcData); 
      int utcYear = utc->tm_year - tmUtil::tm_yearCorrection; 
      int gmtOff = 
       (loc-> tm_sec - utc-> tm_sec) 
       + (loc-> tm_min - utc-> tm_min) * 60 
       + (loc->tm_hour - utc->tm_hour) * 60 * 60 
       + (loc->tm_yday - utc->tm_yday) * 60 * 60 * 24 
       + (loc->tm_year - utc->tm_year) * 60 * 60 * 24 * tmUtil::daysIn(utcYear); 

      #ifdef UNIX 
       if (loc->tm_gmtoff != gmtOff) 
       { 
        StringBuilder err("loc->tm_gmtoff=", StringBuilder((int)(loc->tm_gmtoff)), " but gmtOff=", StringBuilder(gmtOff)); 
        THROWEXCEPTION(err); 
       } 
      #endif 

      int resultInt = fromEpochUtc + gmtOff; 
      time_t result; 
      result = (time_t)resultInt; 
      return result; 
     #else 
      //TODO Find a way to do this without manipulating environment variables 
      time_t result; 
      char *tz; 
      tz = getenv("TZ"); 
      setenv("TZ", "", 1); 
      tzset(); 
      result = mktime(&time); 
      if (tz) 
       setenv("TZ", tz, 1); 
      else 
       unsetenv("TZ"); 
      tzset(); 
      return result; 
     #endif 
    #endif 
} 

nb StringBuilder jest klasą wewnętrzną, nie ma znaczenia dla celów tego pytania.

Więcej informacji:

Wiem, że można to zrobić łatwo za pomocą impuls, et al. Ale to NIE jest i opcja. Muszę to zrobić matematycznie lub używając standardowej funkcji c lub C++ lub ich kombinacji.

timegm wydaje się rozwiązać ten problem, jednak nie wydaje się być częścią standardu C/POSIX. Ten kod jest obecnie kompilowany na wielu platformach (Linux, OSX, WIndows, iOS, Android (NDK)), więc muszę znaleźć sposób, aby działał na wszystkich tych platformach, nawet jeśli rozwiązanie polega na wpisywaniu rzeczy typu #ifdef $PLATFORM.

+0

Czy masz dostępne inne biblioteki? Czy masz listę stref czasowych i ich przesunięć? – Zac

+0

@Zac Nie, nie chcę używać żadnych bibliotek, ponieważ ponoszą one znaczne koszty rozwoju podczas kompilacji krzyżowej. Również nie, nie mam listy stref czasowych i ich przesunięć. Spójrz na moją aktualizację powyżej, mam tam sposób, aby obliczyć przesunięcia strefy czasowej - czy to wygląda dla Ciebie? – bguiz

+0

Możesz przekonwertować go na ciąg znaków za pomocą 'strftime()', zamień strefę czasową w łańcuchu, a następnie przekonwertuj ją z powrotem na 'mktime (strptime())', ale chciałbym tylko przekonać moce, które są tym boostem w rzeczywistości opcja. – smocking

Odpowiedz

4

To sprawia, że ​​chce mi się wymiotować w ustach trochę, ale można przekonwertować go na sznurku z strftime() , zamień strefę czasową w łańcuchu, a następnie przekonwertuj ją z powrotem na strptime() i na time_t z mktime(). W szczegółach:

#ifdef UGLY_HACK_VOIDS_WARRANTY 
time_t convert_time(const struct tm* tm) 
{ 
    const size_t BUF_SIZE=256; 
    char buffer[BUF_SIZE]; 
    strftime(buffer,256,"%F %H:%M:%S %z", tm); 
    strncpy(&buffer[20], "+0001", 5); // +0001 is the time-zone offset from UTC in hours 
    struct tm newtime = {0}; 
    strptime(buffer, "%F %H:%M:%S %z", &newtime); 
    return mktime(&newtime); 
} 
#endif 

Jednak gorąco polecam, aby przekonać się, że moce są w tym przypadku rozwiązaniem. Funkcja Boost zapewnia świetne wsparcie dla niestandardowych stref czasowych. Są inne biblioteki, które również to robią.

+0

@ smocking: przyznanie nagrody, tak jak działa, a jedynym problemem, jaki naprawdę ma, jest to, że jest brzydka. – bguiz

2

Jeśli korzystasz z systemu Linux lub innego systemu UNIX lub UNIX, możesz mieć funkcję timegm, która spełnia Twoje oczekiwania. Połączona strona podręcznika ma przenośną implementację, dzięki czemu możesz ją wykonać samodzielnie. W systemie Windows nie znam takiej funkcji.

+0

dziękuję za twój wkład - nie w pełni rozwiązuję mój problem, zapoznaj się z aktualizacją tego pytania. – bguiz

+0

Po prostu pingujesz cię, więc dostaję moją najnowszą aktualizację! Spójrz, chciałbym usłyszeć twoje komentarze. – bguiz

4

Jeśli chcesz to przekonwertować struct tm podany w UTC do time_t następnie można zrobić to tak:

#include <time.h> 

time_t utc_to_time_t(struct tm* timeinfo) 
{ 
    tzset(); // load timezone information (this can be called just once) 

    time_t t = mktime(timeinfo); 
    return t - timezone; 
} 

To w zasadzie konwertuje czas UTC do time_t jakby dany czas był miejscowy, następnie stosuje korektę strefy czasowej do wyniku, aby przywrócić ją do UTC.

Testowane na gcc/cygwin i Visual Studio 2010.

Mam nadzieję, że to pomoże!

Aktualizacja: Jak dobrze zauważył, moje rozwiązanie powyżej może powrócić time_t wartość, która jest jedną godzinę, gdy czas letni stan odpytywany data jest inna niż ta, do chwili obecnej.

Rozwiązaniem tego problemu jest posiadanie dodatkowej funkcji, która może powiedzieć, czy data przypada w regionie DST, i użyć tej i aktualnej flagi DST, aby dostosować czas zwrócony przez mktime. To jest naprawdę łatwe do zrobienia. Gdy zadzwonisz pod numer mktime(), musisz ustawić element tm_dst na -1, a system zrobi wszystko, aby uzyskać DST o określonej godzinie. Zakładając, że mamy zaufanie do systemu na tym, można wykorzystać te informacje do zastosowania korekty:

#include <time.h> 

time_t utc_to_time_t(struct tm* timeinfo) 
{ 
    tzset(); // load timezone information (this can be called just once) 

    timeinfo->tm_isdst = -1; // let the system figure this out for us 
    time_t t = mktime(timeinfo) - timezone; 

    if (daylight == 0 && timeinfo->tm_isdst != 0) 
     t += 3600; 
    else if (daylight != 0 && timeinfo->tm_isdst == 0) 
     t -= 3600; 
    return t; 
} 
+0

Miguel: Dzięki za odpowiedź. Jednym z problemów, które widzę z tym, jest to, że wartość 'timezone' w' time.h' reprezentuje różnicę czasu od UTC w chwili obecnej, a nie w momencie, który obliczasz. Oznacza to, że obliczony czas zostanie wyłączony o jedną godzinę, gdy obliczane są czasy, które są przeciwnego czasu letniego, w którym aktualnie jesteś (6 miesięcy w roku). Czy wziąłeś to pod uwagę; lub poprawić mnie, jeśli się mylę? – bguiz

+0

Hmm. Właściwie masz rację, moje rozwiązanie może mieć błąd równy jednej godzinie, więc jedynym sposobem rozwiązania tego problemu będzie posiadanie historycznej bazy danych zmian czasu letniego we wszystkich lokalizacjach, które chcesz obsłużyć. Okazuje się, że taka baza danych istnieje, możesz ją pobrać z http://www.iana.org/time-zones. Dostarczają dane oraz funkcje czasu, które z nich korzystają. Myślę, że byłoby całkiem łatwo zmodyfikować ich implementację mktime, aby założyć strefę czasową UTC. Funkcje tej biblioteki są kompilowane na systemie Unix, ale nie wygląda na to, że byłoby dużo pracy, aby przenieść je do systemu Windows. – Miguel

+0

Albo możesz przyjrzeć się implementacji DateTime w PHP, która jest oparta na tej samej bazie danych i kompiluje się wszędzie. – Miguel

0

Po pokonaniu głowę przed tym przez kilka dni próbuje uzyskać timegm(1) funkcję, która działa na Androidzie (który nie jest dostarczany z jednym), ale w końcu odkryliśmy to proste i eleganckie rozwiązanie, które działa pięknie:

time_t timegm(struct tm *tm) { 
    time_t t = mktime(tm); 
    return t + localtime(&t)->tm_gmtoff; 
} 

Nie rozumiem, dlaczego nie byłoby to odpowiednie rozwiązanie wieloplatformowe.

Mam nadzieję, że to pomoże!

+0

Nie byłby odpowiedni, ponieważ 'tm_gmtoff' nie jest przenośny. – avakar

0

Nie wydaje się być prostsze rozwiązanie:

#include <time64.h> 
time_t timegm(struct tm* const t) 
{ 
    return (time_t)timegm64(t); 
} 

Właściwie nie testowałem jeszcze, czy naprawdę to działa, bo mam jeszcze trochę przeniesieniem do zrobienia, ale kompiluje.

Powiązane problemy