std :: mktime и информация о часовом поясе - PullRequest
31 голосов
/ 10 февраля 2009

Я пытаюсь преобразовать информацию о времени, отображаемую в виде строки UTC, в метку времени, используя std::mktime в C ++. Моя проблема в том, что в <ctime> / <time.h> нет функции для преобразования в UTC; mktime возвращает только временную метку как местное время.

Так что мне нужно выяснить смещение часового пояса и принять его во внимание, но я не могу найти независимый от платформы способ, который не предполагает перенос всего кода на boost::date_time. Есть ли какое-то простое решение, которое я упустил из виду?

Ответы [ 10 ]

21 голосов
/ 01 марта 2011
timestamp = mktime(&tm) - _timezone;

или независимый от платформы способ:

 timestamp = mktime(&tm) - timezone;

Если вы посмотрите в источнике mktime ():

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

в строке 00117 время, переведенное в местное время:

seconds += _timezone;
6 голосов
/ 04 июля 2012

mktime () использует tzname для определения часового пояса. tzset () инициализирует переменную tzname из переменной окружения TZ. Если переменная TZ появляется в среде, но ее значение пусто или ее значение не может быть правильно интерпретировано, используется UTC.

Портативная (не защищенная от потоков) версия в соответствии с 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;
   }

Эрик С Рэймонд опубликовал поточную версию в своей статье Программирование времени, часов и календаря на 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);
}
4 голосов
/ 08 ноября 2013

Если вы пытаетесь сделать это в многопоточной программе и не хотите иметь дело с блокировкой и разблокировкой мьютексов (если вы используете метод переменной среды, который вам необходим), есть функция 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;
}
3 голосов
/ 21 июля 2015

Вот простой, проверенный, мы надеемся, переносимый фрагмент кода, конвертирующий из struct tm в секунды с начала настраиваемого UTC года без временной смены часового пояса.

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

Ключевые наблюдения, допускающие большое упрощение по сравнению с кодом в this и , на которые отвечает:

  • нумерация месяцев с марта, все месяцы, кроме месяца, предшествующего этому происхождению, повторяются с циклом из 5 месяцев, общей продолжительностью 153 дня, чередуясь с 31 и 30 днями, так что для любого месяца и без учета скачка годы, число дней с предыдущего февраля может быть вычислено (в пределах константы) с использованием сложения соответствующей константы, умножения на 153 и целочисленного деления на 5;
  • может быть вычислена поправка в днях, учитывающая правило для високосного года по годам, кратным 100 (которые, за исключением правил, кратных 4, не являются високосными, за исключением случаев, когда число кратно 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;
    }
3 голосов
/ 23 апреля 2013

В тот же день у меня возникла та же проблема, и при поиске в документе "man mktime":

Функции mktime () и timegm () преобразуют выделенное время (в структуре, на которую указывает * timeptr) в значение времени с той же кодировкой, что и из значений, возвращаемых функцией time (3) (то есть, секунды с начала эпохи, UTC). Функция mktime () интерпретирует структуру ввода в соответствии с текущая настройка часового пояса (см. tzset (3)). Функция timegm () интерпретирует входную структуру как представляющую универсальное координированное время (UTC).

Short:

Вы должны использовать timegm вместо mktime.

С уважением,

Пай

3 голосов
/ 10 февраля 2009

mktime предполагает, что значение даты находится в местном часовом поясе. Таким образом, вы можете заранее изменить переменную окружения часового пояса (setenv) и получить часовой пояс UTC.

Windows tzset

Можно также попробовать посмотреть различные домашние utc-mktimes, mktime-utcs и т. Д.

2 голосов
/ 15 ноября 2013

Используйте _mkgmtime , он обо всем позаботится.

1 голос
/ 10 февраля 2009

Структура tm, используемая mktime, имеет поле часового пояса.
Что произойдет, если вы введете «UTC» в поле тимзона?

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

0 голосов
/ 31 декабря 2018

Я просто пытался понять, как это сделать. Я не уверен, что это решение идеально (оно зависит от того, насколько точно библиотека времени выполнения рассчитывает переход на летнее время), но оно неплохо работает для моей проблемы.

Сначала я думал, что смогу просто вычислить разницу между gmtime и localtime и добавить ее к моей конвертированной метке времени, но это не сработает, потому что разница будет меняться в зависимости от времени года, в котором код запускается, и если ваше исходное время будет в другой половине года, вы выйдете на час.

Итак, хитрость заключается в том, чтобы заставить библиотеку времени выполнения вычислять разницу между UTC и местным временем для времени, которое вы пытаетесь преобразовать.

Итак, я делаю вычисление моего времени ввода, а затем изменяю это вычисленное время, подключая его обратно к localtime и gmtime и добавляя разницу между этими двумя функциями:

std::tm     tm;

// Fill out tm with your input time.

std::time_t basetime = std::mktime( &tm );
std::time_t diff;

tm = *std::localtime( &basetime );
tm.tm_isdst = -1;
diff = std::mktime( &tm );

tm = *std::gmtime( &basetime );
tm.tm_isdst = -1;
diff -= std::mktime( &tm );

std::time_t finaltime = basetime + diff;

Это довольно окольный способ вычислить это, но я не мог найти другого пути, не прибегая к вспомогательным библиотекам или написав собственную функцию преобразования.

0 голосов
/ 09 июня 2017

Я использовал следующий код для генерации метки времени в UTC:

#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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...