如何用C转换UTC到当地时间?

这是一个简单的问题,但解决方案似乎远非简单。 我想知道如何从UTC转换为本地时间。 我正在寻找一种标准的C解决方案,或多或少保证可以在任何位置的任何计算机上工作。

我仔细阅读了以下链接,但我找不到解决方案:

在C中将包含localtime的字符串转换为UTC

在C / C ++中在本地时间和GMT / UTC之间转换

我尝试了很多变体,例如(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); 

无论我尝试什么,printtime最终都与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 。 将自纪元以来的秒数添加到此struct tmtm_sec字段中,然后调用mktime进行规范化。

编辑:实际上唯一的POSIX依赖是有一个已知的时期(time_t)0对应。 也许你可以找到解决方法,如果你真的需要…例如在time_t 0时使用gmtimelocaltime调用..

编辑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纪元的秒数​​”的forms进行争论,并以当地时间输出结果时间。 您可以使用上面引用的公式将任何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; } 

哪个产生这个输出:

当地时间戳:1412554119

格林尼治标准时间戳:1412546919

小时差异:2

现在,你有UTC和本地时间之间的差异,以秒为单位。 这应该足以转换它。

你的代码的一个注释,aseq:你在这里不需要使用malloc(你也可以在堆栈上memset值,而malloc可能很昂贵,而堆栈分配通常要快得多),而且你不会释放它。 这是非常非常糟糕的做法。

另一件事:

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

如果您将sizeof(* tp)(或者,如果将tp放在堆栈上,sizeof(tp))传递给memset,那会更好。 这可以确保即使对象的类型发生变化,它仍将完全设置为memset。

总结:使用timegm()实现UTC中的故障日期(struct tm)到(本地)日历时间(time_t)的转换 – 与mktime()相反 – 但是timegm()不是标准function(逻辑是怎样的)。 C标准只给我们留下了time(),gmtime(),mktime()和difftime()。

在其他文档中找到的解决方法建议通过首先将环境变量TZ设置为空字符串,然后调用mktime()导致UTC日历时间,然后将TZ重置为其初始值来模拟timegm(),但这又是不标准。

基本上,据我所知,本地时间和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没有生效,但如果我将转换日期设置为,例如,当DST生效时 8/1/2018 0:00:00,则给出的解决方案将转换为本地时间,但不会将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[]; 

在你打电话时根据TZ env变量加载

  tzset(); 

如果你有时间

 struct tm date; date.tm_isdst = 0; 

使用mktime将其转换为time_t

 time_t utc = mktime( &date ); 

然后将其转换为当地时间

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

timezone是当前时区远离utc的秒数,daylight为1表示夏令时正在播放中,而0表示没有。

一个小小的注意事项:当我为微控制器编写这个并进行交叉编译时,它的time.h定义了那些带有初始下划线的变量。

有关time.h的信息,请参见手册页

我按照@Dachschaden的回答做了一个例子,它也显示了人类可读的输出,我删除了DST选项,以查看UTC和本地时间之间的差异。 这里是:

 #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 

diff是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之间的秒数(考虑夏令时)。

作为一个例子,在2017年12月30日,美国山地标准时间(没有夏令时),比美国标准时间晚7小时,在一分钟前工作得很好:

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