c++ tutorial std:: mktime和時區信息




c++用法 (8)

我試圖將時間信息作為UTC字符串轉換為時間戳,使用C ++中的std::mktime 。 我的問題是在<ctime> / <time.h>中沒有函數轉換為UTC; mktime只會以本地時間返回時間戳。

所以我需要弄清楚時區偏移量並考慮到它,但是我找不到一個與平台無關的方式,它不涉及將整個代碼移植到boost::date_time 。 我忽略了一些簡單的解決方案嗎?




如果你正在嘗試在多線程程序中這樣做,並且不想處理鎖定和解鎖互斥鎖(如果你使用環境變量方法,你必須),有一個叫做timegm的函數可以做到這一點。 它不是可移植的,所以這裡是源代碼: 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;
}

使用_mkgmtime ,它處理所有事情。


我最後一天遇到同樣的問題,並蒐索文檔“man mktime”:

函數mktime()和timegm()將時間斷點(在* timeptr指向的結構中)轉換為時間值,其編碼與time(3)函數返回的值相同(即,距離Epoch,UTC的秒)。 mktime()函數根據當前時區設置解釋輸入結構(請參閱tzset(3))。 timegm()函數將輸入結構解釋為表示通用協調時間(UTC)。

短:

你應該使用timegm,而不是使用mktime。

問候,


這是一個簡單的,經過測試的,可移植的代碼,從可調整的UTC年開始就可以從struct tm轉換為秒,而不會暫時改變時區。

// 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
    }

與本文中的代碼相比,主要觀察允許極大的簡化,並且答案如下:

  • 從3月開始計算月數,除原始數據之外的所有月份重複循環5個月,共計153天,交替31天和30天,因此,對於任何月份,不考慮閏年,自上一天起的天數2月可以計算(在一個常數範圍內),使用加上一個適當的常數,乘以153和整數除以5;
  • 以百萬倍為單位的百萬倍數(除四倍數規則的例外是非跳躍,除非400的倍數)可通過加法計算(在常數範圍內)合適的常數,除以100的整數,乘以3,除以整數除以4;
  • 我們可以使用我們在主運算中使用的相同公式來計算任何時期的修正,並且可以使用宏執行此操作,以便在編譯時計算此修正。

這是另一個不需要64位支持的版本,鎖定到1970年的原點。

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


mktime()使用tzname來檢測時區。 tzset()從TZ環境變量初始化tzname變量。 如果TZ變量出現在環境中,但其值為空或其值無法正確解釋,則使用UTC。

根據timegm聯機幫助頁的便攜式(非線程安全)版本

   #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在他的文章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);
}




c++