Как исправить ошибки с UTC по местному времени в C?

Это простой вопрос, но решение кажется далеко не простым. Я хотел бы знать, как конвертировать с UTC по местному времени. Я ищу решение на C, которое является стандартным и более или менее гарантированным для работы на любом компьютере в любом месте.

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

Преобразование строки, содержащей localtime, в UTC в C

Преобразование между локальными временами и GMT / UTC в C / C ++

Я пробовал несколько вариантов, таких как (datetime – строка со временем и датой в UTC):

strptime(datetime, "%A %B %d %Y %H %M %S", tp); strftime(printtime, strlen(datetime), "%A %B %d %Y %H %M %S", tp); 

Или же

 strptime(datetime, "%A %B %d %Y %H %M %S", tp); lt=mktime(tp); printtime=ctime(&lt); 

Независимо от того, что я делаю, время печати заканчивается тем же, что и UTC.

Редактируйте 11-29-2013 : на основании очень полезного ответа от «R» ниже я, наконец, собрался, чтобы создать рабочий пример. Я нашел, что он работает правильно в двух часовых поясах, которые я тестировал, CET и PST:

 #include  #include  #include  long long diff_tm(struct tm *a, struct tm *b) { return a->tm_sec - b->tm_sec +60LL*(a->tm_min - b->tm_min) +3600LL*(a->tm_hour - b->tm_hour) +86400LL*(a->tm_yday - b->tm_yday) +(a->tm_year-70)*31536000LL -(a->tm_year-69)/4*86400LL +(a->tm_year-1)/100*86400LL -(a->tm_year+299)/400*86400LL -(b->tm_year-70)*31536000LL +(b->tm_year-69)/4*86400LL -(b->tm_year-1)/100*86400LL +(b->tm_year+299)/400*86400LL; } int main() { time_t utc, local; char buf[100]; const char datetime[]="2013 11 30 23 30 26 UTC"; /* hard coded date and time in UTC */ struct tm *tp=malloc(sizeof(struct tm)); if(tp==NULL) exit(-1); struct tm *localt=malloc(sizeof(struct tm)); if(localt==NULL) exit(-1); memset(tp, 0, sizeof(struct tm)); memset(localt, 0, sizeof(struct tm)); printf("UTC date and time to be converted in local time: %s\n", datetime); /* put values of datetime into time structure *tp */ strptime(datetime, "%Y %m %d %H %M %S %z", tp); /* get seconds since EPOCH for this time */ utc=mktime(tp); printf("UTC date and time in seconds since EPOCH: %d\n", utc); /* lets convert this UTC date and time to local date and time */ struct tm e0={ .tm_year = 70, .tm_mday = 1 }, e1, new; /* get time_t EPOCH value for e0 (Jan. 1, 1970) */ time_t pseudo=mktime(&e0); /* get gmtime for this value */ e1=*gmtime(&pseudo); /* calculate local time in seconds since EPOCH */ e0.tm_sec += utc - diff_tm(&e1, &e0); /* assign to local, this can all can be coded shorter but I attempted to increase clarity */ local=e0.tm_sec; printf("local date and time in seconds since EPOCH: %d\n", local); /* convert seconds since EPOCH for local time into localt time structure */ localt=localtime(&local); /* get nicely formatted human readable time */ strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S %Z", localt); printf("local date and time: %s\n", buf); } 

Он должен компилироваться без проблем в большинстве систем. Я жестко закодировал время и дату в UTC, которые затем будут преобразованы в локальное время и дату.

    Если вы можете предположить POSIX (и, следовательно, POSIX-спецификацию time_t как секунд с эпохи), я бы сначала использовал формулу POSIX для преобразования в секунды с эпохи:

     tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 + (tm_year-70)*31536000 + ((tm_year-69)/4)*86400 - ((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400 

    Затем используйте localtime((time_t []){0}) чтобы получить struct tm представляющую эпоху в локальное время. Добавьте секунды с эпохи в поле tm_sec этой struct tm , затем вызовите mktime для канонизации.

    Изменить: на самом деле единственная зависимость POSIX имеет известную эпоху, которой соответствует (time_t)0 . Возможно, вы можете найти способ обойти это, если вам действительно нужно … например, используя вызовы как gmtime и localtime в time_t 0 ..

    Изменить 2: Эскиз того, как это сделать:

     #include  #include  long long diff_tm(struct tm *a, struct tm *b) { return a->tm_sec - b->tm_sec +60LL*(a->tm_min - b->tm_min) +3600LL*(a->tm_hour - b->tm_hour) +86400LL*(a->tm_yday - b->tm_yday) +(a->tm_year-70)*31536000LL -(a->tm_year-69)/4*86400LL +(a->tm_year-1)/100*86400LL -(a->tm_year+299)/400*86400LL -(b->tm_year-70)*31536000LL +(b->tm_year-69)/4*86400LL -(b->tm_year-1)/100*86400LL +(b->tm_year+299)/400*86400LL; } int main(int argc, char **argv) { char buf[100]; struct tm e0 = { .tm_year = 70, .tm_mday = 1 }, e1, new; time_t pseudo = mktime(&e0); e1 = *gmtime(&pseudo); e0.tm_sec += atoi(argv[1]) - diff_tm(&e1, &e0); mktime(&e0); strftime(buf, sizeof buf, "%c", &e0); puts(buf); } 

    Пожалуйста, не обращайте внимания на уродливый код вывода. Эта программа принимает аргумент в форме «секунд относительно эпохи POSIX» и выводит результирующее время в локальное время. Вы можете конвертировать любое время UTC в секундах с эпохи, используя приведенную выше формулу. Обратите внимание, что этот код никоим образом не зависит от POSIX, но он предполагает, что смещение, возвращаемое diff_tm сочетании с значением секунды, начиная с момента-времени, не переполняет int . Исправлением для этого было бы использовать long long смещение и цикл, который добавляет инкременты не более INT_MAX/2 (или меньше INT_MIN/2) и вызывает mktime для перенормировки до тех пор, пока смещение не достигнет 0.

    Ах … Я мог бы быть новичком в C, но я получил этот рабочий пример:

     #include  #include  int main(void) { time_t abs_ts,loc_ts,gmt_ts; struct tm loc_time_info,gmt_time_info; /*Absolute time stamp.*/ time(&abs_ts); /*Now get once the local time for this time stamp, **and once the GMT (UTC without summer time) time stamp.*/ localtime_r(&abs_ts,&loc_time_info); gmtime_r(&abs_ts,&gmt_time_info); /*Convert them back.*/ loc_ts=mktime(&loc_time_info); gmt_ts=mktime(&gmt_time_info); /*Unfortunately, GMT still has summer time. Get rid of it:*/ if(gmt_time_info.tm_isdst==1) {gmt_ts-=3600;} printf("Local timestamp: %lu\n" "UTC timestamp: %lu\n" "Difference in hours: %lu\n\n", loc_ts, gmt_ts, (loc_ts-gmt_ts)/3600); return 0; } 

    Что производит этот вывод:

    Местная timestamp: 1412554119

    Время постов: 1412546919

    Разница в часах: 2

    Теперь у вас есть разница между временем по Гринвичу и местным временем в секундах. Этого должно быть достаточно, чтобы преобразовать его.

    Одно примечание к вашему коду, aseq: вы используете malloc без необходимости здесь (вы можете также фиксировать значения в стеке, а malloc может быть дорогим, в то время как распределение стека часто намного быстрее), и вы не освобождаете его. Это очень, очень плохая практика.

    Еще одна вещь:

    memset (tp, 0, sizeof (struct tm));

    Было бы лучше сделать, если вы передадите sizeof (* tp) (или, если вы поместите tp в стек, sizeof (tp)) в memset. Это гарантирует, что даже если тип вашего объекта изменится, он все равно будет полностью memset.

    Подводя итог: преобразование даты разбивки (struct tm) в UTC к (местному) календарному времени (time_t) достигается с помощью timegm () – противоположность mktime () – BUT timegm () не является стандартом (как это логично). Стандарт C оставляет нам только time (), gmtime (), mktime () и difftime ().

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

    В принципе, как я понимаю, разница между местным временем и временем UTC – это просто смещение, поэтому, если мы сможем оценить это смещение, мы можем настроить результат mktime (), так что вот мое предложение:

     time_t my_timegm(struct tm *tm) { time_t epoch = 0; time_t offset = mktime(gmtime(&epoch)); time_t utc = mktime(tm); return difftime(utc, offset); } 

    Быстрый тест:

     int main(void) { time_t now = time(0); struct tm local = *localtime(&now); struct tm utc = *gmtime(&now); time_t t1 = mktime(&local); time_t t2 = my_timegm(&utc); assert(t1 == t2); printf("t =%lu\nt1=%lu\nt2=%lu\n",now,t1,t2); return 0; } 
     //working stand alone function adjusting UTC to local date and time //globals(unsigned integers): gps.Mth, gps.Yr, gps.Hm (eg:2115 for 21:15) //adjust date and time according to UTC //tz(timezone) eg: 1100, for 11 hours, tzdir: 1 forward, 0 backwards void AdjustUTCToTimeZone(u16 tz, u8 tzdir){ u8 maxDayInAnyMonth[13] = {0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //gps.Mth 1-12 (not zero) if(gps.Yr%4==0){maxDayInAnyMonth[2]=29;}//adjust for leapyear u8 maxDayUtcMth =maxDayInAnyMonth[gps.Mth]; u8 maxDayPrevMth=maxDayInAnyMonth[gps.Mth-1]; if(!maxDayPrevMth){maxDayPrevMth=31;} //month before utc month u16 hr=(gps.Hm/100)*100;u16 m=gps.Hm-hr; //2115 --> 2100 hr and 15 min if(tzdir){//adjusting forwards tz+=gps.Hm; if(tz>2400){gps.Hm=tz-2400;gps.Day++; //spill over to next day if(gps.Day>maxDayUtcMth){ gps.Day=1;gps.Mth++; //spill over to next month if(gps.Mth>12){gps.Mth=1; gps.Yr++; //spill over to next year } } }else{gps.Hm=tz;} }else{//adjusting backwards if(tz>gps.Hm){gps.Hm=(2400-(tz-hr))+m;gps.Day--; // back to previous day if(gps.Day==0){ //back to previous month gps.Mth--;gps.Day=maxDayPrevMth; if(!gps.Mth){gps.Mth=12; //back to previous year gps.Yr--; } } }else{gps.Hm-=tz;} } } 

    Я обнаружил, что решение, предоставленное OP, не работает в случаях, когда применяется DST. Например, в моем случае, в настоящее время, DST не действовал, но если я установил начальную дату, которая должна быть конвертирована в местное время с DST, то это не сработало бы, то есть сегодняшняя дата 3/1/2018 и DST не действует, но если я установил дату преобразования, скажем, 8/1/2018 0:00:00, когда действует DST, тогда указанное решение будет конвертировать в локальное время, но не будет принимать DST в учетная запись. Я обнаружил, что инициализация e0 до даты и часа исходной строки даты / времени и ее члена tm_isdst до -1 решила проблему. Затем я создал следующую программу с дополнительными функциями, которые вы можете включить в свой код. Первоначальный формат даты и времени совпадает с тем, что использует MySQL, потому что для этого я нуждался в этом.

     #include  #include  #include  long long diff_tm(struct tm *a, struct tm *b) { return a->tm_sec - b->tm_sec + 60LL * (a->tm_min - b->tm_min) + 3600LL * (a->tm_hour - b->tm_hour) + 86400LL * (a->tm_yday - b->tm_yday) + (a->tm_year - 70) * 31536000LL - (a->tm_year - 69) / 4 * 86400LL + (a->tm_year - 1) / 100 * 86400LL - (a->tm_year + 299) / 400 * 86400LL - (b->tm_year - 70) * 31536000LL + (b->tm_year - 69) / 4 * 86400LL - (b->tm_year - 1) / 100 * 86400LL + (b->tm_year + 299) /400 * 86400LL; } void localToUTC(char *buf, const char *localTime) { struct tm tp; strptime(localTime, "%Y-%m-%d %H:%M:%S", &tp); tp.tm_isdst = -1; time_t utc = mktime(&tp); struct tm res = *gmtime(&utc); strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &res); } void utcToLocal(char *buf, const char *utcTime) { struct tm tp; strptime(utcTime, "%Y-%m-%d %H:%M:%S", &tp); tp.tm_isdst = -1; time_t utc = mktime(&tp); struct tm e0 = { .tm_year = tp.tm_year, .tm_mday = tp.tm_mday, .tm_mon = tp.tm_mon, .tm_hour = tp.tm_hour, .tm_isdst = -1 }; time_t pseudo = mktime(&e0); struct tm e1 = *gmtime(&pseudo); e0.tm_sec += utc - diff_tm(&e1, &e0); time_t local = e0.tm_sec; struct tm localt = *localtime(&local); strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &localt); } int main(void) { char mytime_1[20] = "2018-02-28 13:00:00"; char utctime_1[20], back_1[20]; localToUTC(utctime_1, mytime_1); utcToLocal(back_1, utctime_1); printf("My time: %s\n", mytime_1); printf("UTC time: %s\n", utctime_1); printf("Back: %s\n", back_1); printf("-------------------------------------------\n"); char mytime_2[20] = "2018-07-28 17:00:00"; char utctime_2[20], back_2[20]; localToUTC(utctime_2, mytime_2); utcToLocal(back_2, utctime_2); printf("My time: %s\n", mytime_2); printf("UTC time: %s\n", utctime_2); printf("Back: %s\n", back_2); printf("-------------------------------------------\n"); return 0; } 

    Я думаю, что это проще, чем это; time.h определяет три переменные:

     extern int daylight; extern long timezone; extern char *tzname[]; 

    которые загружаются на основе переменной env TZ при вызове

      tzset(); 

    если у вас есть время utc

     struct tm date; date.tm_isdst = 0; 

    конвертировать его в time_t с помощью mktime

     time_t utc = mktime( &date ); 

    затем преобразовать его в местное время

     time_t local = utc - timezone + ( daylight?3600:0 ); 

    часовой пояс – это количество секунд от utc для текущего часового пояса, а дневной свет – 1, чтобы указать, что летнее время находится в игре, а ноль – нет.

    Небольшая осторожность: когда я закодировал это для микроcontrollerа и скрестил скомпилированный, это время.h определило эти переменные с начальными подчеркиваниями.

    См. Справочную страницу для time.h

    Я последовал за ответом @Dachschaden, и я сделал пример, который также отображает читаемый человеком вывод, и я удаляю параметр DST для разницы в секундах между временем по Гринвичу и местным временем. Вот:

     #include  #include  #define DATE_MAX_STR_SIZE 26 #define DATE_FMT "%FT%TZ%z" int main() { time_t now_time, now_time_local; struct tm now_tm_utc, now_tm_local; char str_utc[DATE_MAX_STR_SIZE]; char str_local[DATE_MAX_STR_SIZE]; time(&now_time); gmtime_r(&now_time, &now_tm_utc); localtime_r(&now_time, &now_tm_local); /* human readable */ strftime(str_utc, DATE_MAX_STR_SIZE, DATE_FMT, &now_tm_utc); strftime(str_local, DATE_MAX_STR_SIZE, DATE_FMT, &now_tm_local); printf("\nUTC: %s", str_utc); printf("\nLOCAL: %s\n", str_local); /* seconds (epoch) */ /* let's forget about DST for time difference calculation */ now_tm_local.tm_isdst = 0; now_tm_utc.tm_isdst = 0; now_time_local = now_time + (mktime(&now_tm_local) - mktime(&now_tm_utc)); printf("\nUTC in seconds: %lu", now_time); printf("\nLOCAL in seconds: %lu\n", now_time_local); return 0; } 

    Выход на моей машине:

     UTC: 2016-05-05T15:39:11Z-0500 LOCAL: 2016-05-05T11:39:11Z-0400 UTC in seconds: 1462462751 LOCAL in seconds: 1462448351 

    Обратите внимание, что DST включен в этом случае (разница между часами и часовым поясом между UTC и LOCAL составляет 1 час).

    попробуйте это, тестовый выход: utcEpochTime: 1487652688, localEpochTime: 1487699488, diff: 46800

     $ python >>>46800 / 60 / 60 13 

    разница составляет 13 часов, что хорошо, так как мой часовой пояс UTC + 8.

     #include  #include  int main(int argc, char *argv[]) { time_t utcEpochTime = time(0); time_t localEpochTime = 0; struct tm tm = {0}; localtime_r(&utcEpochTime, &tm); tm.tm_isdst = -1; localEpochTime = timegm(&tm); printf("utcEpochTime: %d, localEpochTime: %d, diff: %d\n", (int)utcEpochTime, (int)localEpochTime, (int)(localEpochTime - utcEpochTime)); return 0; } 

    Простой и эффективный способ: добавить (или вычесть) количество секунд между вашим часовым поясом и UTC (с учетом летнего времени).

    В качестве примера, который работал очень хорошо минуту назад, 30 декабря 2017 года, с американским стандартным временем (без DST), что на 7 часов меньше UTC:

     time_t current_time_UTC; time_t current_time_MST; struct tm *current_broken_time_MST; uint32_t seven_hours_in_seconds = 25200; // Get this any way you want current_time_UTC = time (NULL); // UTC current_time_MST = current_time_UTC - seven_hours_in_seconds; // MST current_broken_time_MST = localtime (&current_time_MST); // MST 

    Наслаждаться.

     void CTestDlg::OnBtnTest() { HANDLE hFile; WIN32_FIND_DATA wfd; SYSTEMTIME systime; FILETIME localtime; char stime[32]; // memset(&wfd, 0, sizeof(wfd)); if((hFile=FindFirstFile( "F:\\VC\\MFC\\Test\\Release\\Test.exe ", &wfd))==INVALID_HANDLE_VALUE) { char c[2]; DWORD dw=GetLastError(); wsprintf(c, "%d ", dw); AfxMessageBox(c); return ;// } FileTimeToLocalFileTime(&wfd.ftLastWriteTime,&localtime); FileTimeToSystemTime(&localtime,&systime); sprintf(stime, "%4d-%02d-%02d %02d:%02d:%02d ", systime.wYear,systime.wMonth,systime.wDay,systime.wHour, systime.wMinute,systime.wSecond); AfxMessageBox(stime); }