Простой способ преобразовать struct tm (выраженную в UTC) в тип time_t
Как мне сделать выше? Есть функция mktime, но она обрабатывает входные данные как выраженные по местному времени, но как мне выполнить преобразование, если моя входная переменная tm находится в UTC?
13 ответов
Для тех на окнах, ниже функция доступна:
_mkgmtime
ссылка для получения дополнительной информации: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/mkgmtime-mkgmtime32-mkgmtime64
Вот решение, которое я использую (не могу вспомнить, где я его нашел), когда это не платформа Windows
time_t _mkgmtime(const struct tm *tm)
{
// Month-to-day offset for non-leap-years.
static const int month_day[12] =
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
// Most of the calculation is easy; leap years are the main difficulty.
int month = tm->tm_mon % 12;
int year = tm->tm_year + tm->tm_mon / 12;
if (month < 0) { // Negative values % 12 are still negative.
month += 12;
--year;
}
// This is the number of Februaries since 1900.
const int year_for_leap = (month > 1) ? year + 1 : year;
time_t rt = tm->tm_sec // Seconds
+ 60 * (tm->tm_min // Minute = 60 seconds
+ 60 * (tm->tm_hour // Hour = 60 minutes
+ 24 * (month_day[month] + tm->tm_mday - 1 // Day = 24 hours
+ 365 * (year - 70) // Year = 365 days
+ (year_for_leap - 69) / 4 // Every 4 years is leap...
- (year_for_leap - 1) / 100 // Except centuries...
+ (year_for_leap + 299) / 400))); // Except 400s.
return rt < 0 ? -1 : rt;
}
Ответ Локи Астари был хорошим началом, timegm
является одним из возможных решений. Тем не менее, справочная страница timegm
дает портативную версию этого, как timegm
не POSIX-совместимый. Вот:
#include <time.h>
#include <stdlib.h>
time_t
my_timegm(struct tm *tm)
{
time_t ret;
char *tz;
tz = getenv("TZ");
if (tz)
tz = strdup(tz);
setenv("TZ", "", 1);
tzset();
ret = mktime(tm);
if (tz) {
setenv("TZ", tz, 1);
free(tz);
} else
unsetenv("TZ");
tzset();
return ret;
}
timegm()
работает, но нет на всех системах.
Вот версия, которая использует только ANSI C. (РЕДАКТИРОВАТЬ: не совсем ANSI C! Я делаю математику для time_t, предполагая, что единицы измерения находятся в секундах с начала эпохи. AFAIK, стандарт не определяет единицы измерения time_t.) Примечание он использует, так сказать, хак для определения часового пояса машины, а затем соответствующим образом корректирует результат из mktime.
/*
returns the utc timezone offset
(e.g. -8 hours for PST)
*/
int get_utc_offset() {
time_t zero = 24*60*60L;
struct tm * timeptr;
int gmtime_hours;
/* get the local time for Jan 2, 1900 00:00 UTC */
timeptr = localtime( &zero );
gmtime_hours = timeptr->tm_hour;
/* if the local time is the "day before" the UTC, subtract 24 hours
from the hours to get the UTC offset */
if( timeptr->tm_mday < 2 )
gmtime_hours -= 24;
return gmtime_hours;
}
/*
the utc analogue of mktime,
(much like timegm on some systems)
*/
time_t tm_to_time_t_utc( struct tm * timeptr ) {
/* gets the epoch time relative to the local time zone,
and then adds the appropriate number of seconds to make it UTC */
return mktime( timeptr ) + get_utc_offset() * 3600;
}
Следующая реализация timegm(1)
работает плавно на Android, и, вероятно, работает на других вариантах Unix:
time_t timegm( struct tm *tm ) {
time_t t = mktime( tm );
return t + localtime( &t )->tm_gmtoff;
}
Новый ответ на старый вопрос, потому что C++20 Chronic делает эту операцию почти тривиальной и очень эффективной.
- Потокобезопасность.
- Не учитывает локальное смещение UTC.
- Никаких итераций, даже внутри реализации хроно.
#include <chrono>
#include <ctime>
std::time_t
my_timegm(std::tm const& t)
{
using namespace std::chrono;
return system_clock::to_time_t(
sys_days{year{t.tm_year+1900}/(t.tm_mon+1)/t.tm_mday} +
hours{t.tm_hour} + minutes{t.tm_min} + seconds{t.tm_sec});
}
разработан таким образом, что вам больше никогда не придется иметь дело с API синхронизации C. Но даже если вам придется с этим справиться,<chrono>
тоже можно сделать это проще.
Обновлять:
В ответ на первый комментарий ниже:
Подвыражениеyear{t.tm_year+1900}/(t.tm_mon+1)/t.tm_mday
создает{year, month, day}
структура под названием. Т.е. для построения файла не выполняются никакие вычисления, он просто сохраняет три поля.
Тогдаyear_month_day
преобразуется в эквивалентный класс даты, называемыйsys_days
. Это основано на с точностью доdays
. Здесь хранится количество дней, прошедших с эпохи времени Unix 1 января 1970 года. Это преобразование использует алгоритмdays_from_civil
подробно описано по ссылке. Обратите внимание, что алгоритм не содержит циклов, и хороший оптимизатор также может избавиться от ветвей (он делает это, используя clang в -O3).
Наконец, к дате добавляется время суток, при этом хроно предоставляет все необходимые коэффициенты пересчета (умножьте количество дней на 86400, количество часов на 3600 и т. д.).
Результатом являетсяtime_point
на основеsystem_clock
с точностью . Для всех известных мне реализаций хроноsystem_clock::to_time_t
функция просто развернет счетчикseconds
поэтому его можно хранить вtime_t
.
Страница POSIX для tzset, описывает глобальную переменную extern long timezone
который содержит местный часовой пояс в виде смещения секунд от UTC. Это будет присутствовать во всех POSIX-совместимых системах.
Чтобы часовой пояс содержал правильное значение, вам, вероятно, потребуется позвонить tzset()
во время инициализации вашей программы.
Вы можете просто добавить timezone
на выход mktime
чтобы получить вывод в UTC.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
time_t utc_mktime(struct tm *t)
{
return mktime(t) + timezone;
}
int main(int argc, char **argv)
{
struct tm t = { 0 };
tzset();
utc_mktime(&t);
}
Примечание: технически tzset()
а также mktime()
не гарантируется поточность.
Если поток обращается к tzname, [XSI] [Option Start] дневной свет или к часовому поясу [Option End] напрямую, когда другой поток находится в вызове tzset(), или к любой функции, которая требуется или которой разрешено устанавливать информацию о часовом поясе, как если бы вызов tzset(), поведение не определено.
... но большинство реализаций есть. GNU C использует мьютексы в tzset()
чтобы избежать одновременного изменения глобальных переменных, которые он устанавливает, и mktime()
видит очень широкое использование в многопоточных программах без синхронизации. Я подозреваю, что если кто-то столкнется с побочными эффектами, это будет от использования setenv()
изменить значение TZ
как сделано в ответе от @liberforce.
Вот мой подход, который основан исключительно на функциях / conversion, и единственное предположение, которое он делает, - это то, что он линейный:
- Притворяясь против лучшего знания, структура хранит местное время (без летнего времени, если кто-то спросит; это не имеет значения, но должно соответствовать шагу 3), преобразуйте его в.
- Преобразуйте дату обратно в структуру, но на этот раз в формате UTC.
- Претендуя на лучшее знание, что
tm
структура, чтобы также хранить локальную (не-DST, если кто-то спросит, но, что более важно, согласованную с шагом 1), и снова преобразовать ее в. - По двум результатам я теперь могу вычислить разницу между местным временем (без летнего времени, если кто-нибудь спросит) и UTC в единицах измерения.
- Добавление этой разницы к первому результату дает мне правильное время в UTC.
Обратите внимание, что вычисление разницы может быть выполнено один раз, а затем применено к любому количеству дат по желанию; это может быть способ решить проблемы, возникающие из-за отсутствия потоковой безопасности в
gmtime
.
(Изменить: опять же, это может вызвать проблемы, если часовой пояс изменяется между датой, используемой для вычисления смещения, и датой, которая должна быть преобразована.)
tm tt;
// populate tt here
tt.tm_isdst = 0;
time_t tLoc = mktime(&tt);
tt = *gmtime(&tLoc);
tt.tm_isdst = 0;
time_t tRev = mktime(&tt);
time_t tDiff = tLoc - tRev;
time_t tUTC = tLoc + tDiff;
Предостережение: если в системе используется TAI-ориентированный
time_t
(или что-либо еще, что учитывает дополнительные секунды), результирующее время может отличаться на 1 секунду, если применяется к моменту времени, близкому к вставке секунды координации.
Я был также обеспокоен проблемой mktime(). Мое решение заключается в следующем
time_t myTimegm(std::tm * utcTime)
{
static std::tm tmv0 = {0, 0, 0, 1, 0, 80, 0, 0, 0}; //1 Jan 1980
static time_t utcDiff = std::mktime(&tmv0) - 315532801;
return std::mktime(utcTime) - utcDiff;
}
Идея состоит в том, чтобы получить разницу во времени, вызвав std::mktime() с известным временем (в данном случае 1980/01/01), и вычесть его метку времени (315532801). Надеюсь, поможет.
Давайте просто решим это, предположим, что местное время «1970-1-1 0:0:0», у нас есть смещение часового пояса.
static time_t timegm(struct tm *utc)
{
struct tm epoch = {};
epoch.tm_year = 1970 - 1900;
epoch.tm_mon = 1 - 1;
epoch.tm_mday = 1;
time_t off = mktime(&epoch);
return mktime(utc) - off;
}
Это действительно комментарий с кодом для ответа на ответ Лео Аксенд: Попробуйте следующее:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
/*
* A bit of a hack that lets you pull DST from your Linux box
*/
time_t timegm( struct tm *tm ) { // From Leo's post, above
time_t t = mktime( tm );
return t + localtime( &t )->tm_gmtoff;
}
main()
{
struct timespec tspec = {0};
struct tm tm_struct = {0};
if (gettimeofday(&tspec, NULL) == 0) // clock_gettime() is better but not always avail
{
tzset(); // Not guaranteed to be called during gmtime_r; acquire timezone info
if (gmtime_r(&(tspec.tv_sec), &tm_struct) == &tm_struct)
{
printf("time represented by original utc time_t: %s\n", asctime(&tm_struct));
// Go backwards from the tm_struct to a time, to pull DST offset.
time_t newtime = timegm (&tm_struct);
if (newtime != tspec.tv_sec) // DST offset detected
{
printf("time represented by new time_t: %s\n", asctime(&tm_struct));
double diff = difftime(newtime, tspec.tv_sec);
printf("DST offset is %g (%f hours)\n", diff, diff / 3600);
time_t intdiff = (time_t) diff;
printf("This amounts to %s\n", asctime(gmtime(&intdiff)));
}
}
}
exit(0);
}
Для всех часовых поясов и во все времена было бы чрезвычайно сложно, если не невозможно. Вам потребуется точная запись всех различных произвольных указов часового пояса и летнего времени (DST). Иногда неясно, кто является местной властью, не говоря уже о том, что было издано указом и когда. Большинство систем, например, отстают на одну секунду для времени безотказной работы (время, когда система работала) или времени загрузки (отметка времени загрузки системы), если была охвачена дополнительная секунда. Хорошим тестом будет дата, которая когда-то была в летнее время, но сейчас нет (или наоборот). (Не так давно в США это изменилось.)