Преобразуйте числа даты и времени в time_t И укажите часовой пояс

У меня есть следующие целые числа:

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

Их значения: 2012, 06, 27, 12, 47, 53 соответственно. Я хочу представить дату и время «2012/06/27 12:47:53 UTC», если я выбрал «UTC» где-то еще в своем приложении, или «2012/06/27 12:47:53 AEST», если я выбрали «AEST» где-то еще в моем приложении.

Я хочу преобразовать это в time_t, и вот код, который я сейчас использую для этого:

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

Итак, я использую tm struct и mktime, однако это не работает, потому что всегда используется мой местный часовой пояс.

Каков правильный способ сделать это?

Итак, ниже приведено решение, которое я придумал до сих пор. В основном он делает одну из трех вещей:

  1. Если UNIX, просто используйте timegm
  2. If not UNIX
    1. Either, do math using the difference between UTC epoch and local epoch as an offset
      • Reservation: Math may be incorrect
    2. Or, set the "TZ" environment variable to UTC temporarily
      • Reservation: will trip up if/ when this code needs to be multithreaded
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
}

Н.Б. StringBuilder - это внутренний класс, это не имеет значения для целей этого вопроса.

Больше информации:

Я знаю, что это можно легко сделать с помощью boost и др. Но это НЕ вариант. Мне нужно, чтобы это было сделано математически или с использованием стандартной функции c или c++ или их комбинации.

timegm, по-видимому, решает эту проблему, однако не является частью стандарта C/POSIX. Этот код в настоящее время скомпилирован для нескольких платформ (Linux, OSX, WIndows, iOS, Android (NDK)), поэтому мне нужно найти способ заставить его работать на всех этих платформах, даже если решение включает в себя #ifdef $PLATFORM типов.


person bguiz    schedule 27.06.2012    source источник
comment
Есть ли у вас другие доступные библиотеки? У вас есть список часовых поясов и их смещения?   -  person Zac    schedule 03.07.2012
comment
@Zac Нет, я не хочу использовать какие-либо библиотеки, так как они требуют больших затрат на разработку при кросс-компиляции. Также нет у меня списка часовых поясов и их смещения. Взгляните на мое обновление выше, у меня есть способ рассчитать смещения часовых поясов - вам это кажется правильным?   -  person bguiz    schedule 04.07.2012
comment
‹UGLY HACK› Вы можете преобразовать его в строку с помощью strftime(), заменить часовой пояс в строке, а затем преобразовать его обратно с помощью mktime(strptime()) ‹/UGLY HACK›, но я бы просто убедил власть имущих, что повышение на самом деле является вариантом.   -  person smocking    schedule 10.07.2012
comment
@smocking Не могли бы вы опубликовать это как ответ?   -  person bguiz    schedule 10.07.2012
comment
Конечно, но мне на самом деле немного стыдно, что я даже разместил это как комментарий :-)   -  person smocking    schedule 10.07.2012
comment
@smocking : хотя, как вы говорите, это уродливо (не говоря уже о том, что не очень эффективно), это означает, что мне [1] не нужно заниматься математикой (и нужно беспокоиться о ее правильности) или [ 2] манипулировать любыми переменными среды (и беспокоиться о проблемах многопоточности), таким образом избегая ловушек обоих методов, которые я придумал до сих пор. Так что можете отвечать с достоинством нетронутым!   -  person bguiz    schedule 10.07.2012


Ответы (6)


Меня от этого немного тошнит, но вы можете преобразовать его в строку с strftime(), заменить часовой пояс в строке, а затем преобразовать его обратно с strptime() и в time_t с mktime(). В деталях:

#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

Тем не менее, я настоятельно рекомендую вам убедить власть предержащих в том, что ускорение все-таки возможно. Boost имеет отличную поддержку пользовательских часовых поясов. Есть и другие библиотеки, которые делают это элегантно.

person smocking    schedule 10.07.2012
comment
@smocking : присуждает вам награду, поскольку она работает, и единственная проблема, которая у нее есть, заключается в том, что она уродлива - person bguiz; 11.07.2012

Если все, что вам нужно, это преобразовать struct tm, указанный в UTC, в time_t, вы можете сделать это следующим образом:

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

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

Протестировано на gcc/cygwin и Visual Studio 2010.

Надеюсь, это поможет!

Обновление: как вы очень хорошо заметили, мое вышеприведенное решение может возвращать значение time_t, которое отличается на один час, когда состояние перехода на летнее время запрошенной даты отличается от состояния для текущего времени.

Решение этой проблемы состоит в том, чтобы иметь дополнительную функцию, которая может сообщить вам, попадает ли дата в область летнего времени или нет, и использовать ее и текущий флаг летнего времени для настройки времени, возвращаемого mktime. На самом деле это легко сделать. Когда вы вызываете mktime(), вам просто нужно установить для члена tm_dst значение -1, и тогда система сделает все возможное, чтобы вычислить для вас летнее время в заданное время. Предполагая, что мы доверяем системе в этом, вы можете использовать эту информацию, чтобы применить исправление:

#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;
}
person Miguel    schedule 04.07.2012
comment
Мигель: Спасибо за ответ. Одна проблема, которую я вижу в этом, заключается в том, что значение timezone в time.h представляет собой разницу во времени с UTC в настоящий момент, а не в момент времени, который вы вычисляете. Это означает, что рассчитанное время будет отличаться на один час, когда вы вычисляете время, которое находится на противоположном летнем времени, в котором вы находитесь в настоящее время (6 месяцев в году). Приняли ли вы это во внимание; или поправьте меня, если я ошибаюсь? - person bguiz; 05.07.2012
comment
Хм. На самом деле вы правы, мое решение может иметь погрешность в один час, поэтому единственный способ решить эту проблему — иметь историческую базу данных изменений перехода на летнее время во всех местах, которые вы хотите поддерживать. Оказывается, такая база данных существует, вы можете получить ее с iana.org/time-zones. Они предоставляют данные, а также функции времени, которые их используют. Я думаю, было бы довольно легко изменить их реализацию mktime, чтобы принять часовой пояс UTC. Функции в этой библиотеке компилируются в Unix, но не похоже, что будет много работы по переносу их на Windows. - person Miguel; 05.07.2012
comment
Или же вы можете посмотреть на реализацию PHP DateTime, которая основана на той же базе данных и компилируется везде. - person Miguel; 05.07.2012
comment
@bguiz: я считаю, что нашел полное решение проблемы, которое все еще очень портативно. Это довольно интересная проблема! - person Miguel; 10.07.2012

Если вы работаете в Linux или другой UNIX или UNIX-подобной системе, у вас может быть timegm функция, которая делает то, что вы хотите. Связанная страница руководства имеет переносимую реализацию, поэтому вы можете сделать ее самостоятельно. В Windows я не знаю такой функции.

person Some programmer dude    schedule 27.06.2012
comment
спасибо за ваш вклад - хотя это не полностью решает мою проблему, пожалуйста, посмотрите мое обновление вопроса. - person bguiz; 27.06.2012
comment
Просто пингую вас, чтобы вы получили мое последнее обновление! Посмотрите, я хотел бы услышать ваши комментарии. - person bguiz; 04.07.2012

После того, как я несколько дней ломал голову над этим, пытаясь получить функцию timegm(1), которая работает на Android (которая не поставляется с ней), я наконец нашел это простое и элегантное решение, которое прекрасно работает:

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

Я не понимаю, почему это не было бы подходящим кросс-платформенным решением.

Надеюсь, это поможет!

person Leo Accend    schedule 29.01.2014
comment
Это не подходит, потому что tm_gmtoff не переносим. - person avakar; 27.09.2017

Кажется, есть более простое решение:

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

На самом деле я еще не тестировал, действительно ли это работает, потому что мне еще нужно немного портировать, но он компилируется.

person moritz    schedule 08.12.2014

Вот мое решение:

#ifdef WIN32
#   define timegm _mkgmtime
#endif

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;

return timegm(&timeinfo);

Это должно работать как для Unix, так и для Windows

person Matheus Medeiros    schedule 30.08.2018