如何在C语言中将UTC时间转换为本地时间?

10

这是一个简单的问题,但解决方案似乎远非简单。我想知道如何将UTC时间转换为本地时间。我正在寻找C语言的标准解决方案,并且基本可以保证在任何计算机上的任何位置都能正常工作。

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

将包含本地时间的字符串转换为UTC时间(C语言)

在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 <time.h>
#include <stdio.h>
#include <stdlib.h>

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(因为您使用strptime)还是仅仅使用纯C语言? - R.. GitHub STOP HELPING ICE
抱歉,我漏掉了,是的,你可以假设使用POSIX。 - aseq
10个回答

8

如果你可以假设 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 <time.h>
#include <stdio.h>

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。

2
关于编辑:毫无疑问,通常情况下,在时代的纪元时刻UTC和本地时间之间的差异与您进行转换时的差异是不同的。也许在日本是这样。 - Steve Jessop
1
顺便说一句,C语言让你为了以合理的方式处理标准化时间而付出如此之多的努力真是太糟糕了。只要有一个mktime函数的UTC等效版本就可以避免整个问题。至少上述方法可以用来制作这样的函数... - R.. GitHub STOP HELPING ICE
是的,我知道你在说什么。这对我来说也很惊讶。也许是因为这种功能据说超出了C语言的范围... - aseq
啊,我迷失了对正在发生的事情的追踪,谢谢。关于gmtime()缺失的逆函数,我想他们希望你设置语言环境并使用mktime()。但实际上,在负责库代码中无法修改和还原全局状态,特别是在多线程应用程序中,因此存在严重缺乏。 - Steve Jessop
我根据这个答案更新了我的问题,并提供了一个可工作的代码示例,谢谢。 - aseq
显示剩余9条评论

5
啊嗯...我可能只是C语言的初学者,但我有一个可行的例子:
#include <time.h>
#include <stdio.h>
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

GMT时间戳:1412546919

小时差:2

现在你有了UTC和本地时间之间的差异(以秒为单位)。这应该足以进行转换。

对于你的代码,aseq,请注意:在这里无需使用malloc(您也可以在堆栈上memset值,而malloc可能很昂贵,而堆栈分配通常要快得多),并且您没有释放它。这是非常非常糟糕的做法。

另一件事:

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

如果你传递sizeof(* tp)(或者,如果你把tp放在堆栈上,sizeof(tp))给memset会更好。这确保即使您的对象类型更改,它仍将被完全memset。


谢谢,我已经在Ubuntu上尝试过了,在GMT+1柏林地区的夏令时和冬令时都可以工作。 - Oliver
1
今天我甚至不会这样写。原因是mktime不可重入(如果我没有弄错的话,会触及全局TZ变量),并且随时可能崩溃。在我看来,这是一个巨大的失败...但另一方面,标准化API的人还有其他要担心的事情。然而,没有明智的方法来做到这一点。R..已经展示了一个自己计算日期的好方法。很遗憾,你必须为这样一个平凡的任务带上那个函数,但另一方面,发布更新的标准需要很长时间。 - Dachschaden
mktime()的man页面中得知:正值表示DST生效;零表示DST未生效;负值表示mktime()应(使用时区信息和系统数据库)尝试确定指定时间是否处于DST状态。 然而,后面又说无论其初始值如何,tm_isdst都将设置为正值或0,以指示指定时间是否处于DST状态,但行为似乎是信任输入时的tm_isdst值。 - ChisholmKyle

3
总之:将UTC中的破损日期(struct tm)转换为本地日历时间(time_t),需要使用timegm()函数,它与mktime()相反,但timegm()不是标准函数(这多么不合理啊)。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;
}

如果你目前处于夏令时,这是错误的。mktime(gmtime(X))不能给你当前适用的偏移量,而是时区偏移量(因此如果夏令时开启,你可能会错过一个小时)。 - xryl669
另外,需要记住的是mktime不是可重入的(与localtime和gmtime不同,从函数签名中也不明显),因为它修改全局时区变量。因此,这段代码可能看起来可以工作,但是任何其他正在执行mktime的线程都可能完全破坏您的输出。 - xryl669

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

2
我认为这比那容易得多; time.h定义了三个变量:
extern int    daylight;
extern long   timezone;
extern char  *tzname[];

当您调用时,将基于TZ环境变量加载

 tzset();

如果您有一个UTC时间

struct tm date;
date.tm_isdst = 0;

使用mktime将其转换为time_t

time_t utc = mktime( &date );

然后将其转换为本地时间。
time_t local = utc - timezone + ( daylight?3600:0 );

时区是当前时区与UTC之间的秒数,夏令时为1表示正在使用夏令时,为0则不使用。

需要注意的是:当我将其编译到微控制器上并进行交叉编译时,time.h会在这些变量前加上下划线。

请参阅time.h的手册页面


1
我发现OP提供的解决方案在夏令时适用的情况下无法正常工作。例如,在我的情况下,当前时间不适用夏令时,但如果我设置应该转换为带夏令时的本地时间的初始日期,那么它就不会起作用,即今天的日期是2018年3月1日,夏令时未生效,但如果我将转换日期设置为,例如,2018年8月1日0:00:00,当夏令时生效时,给定的解决方案将转换为本地时间,但不会考虑夏令时。我发现将e0初始化为初始日期/时间字符串的日期和小时,并将其成员tm_isdst初始化为-1可以解决问题。然后,我创建了以下程序及其补充函数,您可以将其包含在您的代码中。初始日期和时间的格式与MySQL使用的相同,因为我需要它用于此类目的。
#include <stdio.h>
#include <time.h>
#include <string.h>

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

0
我按照@Dachschaden的答案做了一个示例,它还显示了人类可读的输出,并且我删除了DST选项,以便计算UTC和本地时间之间的秒差。这是代码:
#include <time.h>
#include <stdio.h>

#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小时的时区偏移差异)。

0
尝试这个,测试输出: utcEpochTime:1487652688,localEpochTime:1487699488,差异:46800

$ python
>>>46800 / 60 / 60
13

时差为13小时,这很好,因为我的时区是UTC+8。

#include <stdio.h>
#include <time.h>

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

0
一个简单而有效的方法:添加(或减去)您所在时区与UTC之间的秒数(考虑夏令时)。例如,在2017年12月30日,使用美国山区标准时间(无夏令时),该方法刚刚运行良好,该时区比UTC晚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

享受吧。


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

3
a) 那不是C语言 b) 请告诉我在C99标准中 FindFirstFile() 函数的位置。多么糟糕的回答。 - Adrian Cornish
你似乎从未真正阅读过问题,更不用说你的代码依赖于特定的路径和目录结构才能正常工作。下次请更努力地在问题的参数范围内工作。 - mattr-

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接