【问题标题】:How to convert from UTC to local time in C?如何在 C 中从 UTC 转换为本地时间?
【发布时间】:2012-02-22 23:48:47
【问题描述】:

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

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

Converting string containing localtime into UTC in C

Converting Between Local Times and GMT/UTC in 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);

无论我尝试什么,printtime 最终都与 UTC 相同。

2013 年 11 月 29 日编辑:根据下面“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?
  • 抱歉错过了,是的,你可以假设 POSIX。

标签: c time posix utc datetime-conversion


【解决方案1】:

我认为这比那更容易; 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 );

timezone 是当前时区距离 UTC 的秒数,夏令时为 1 表示夏令时有效,零表示不有效。

一个小警告:当我为微控制器编写代码并进行交叉编译时,是 time.h 用初始下划线定义了这些变量。

See the man page for time.h

【讨论】:

    【解决方案2】:

    我发现 OP 提供的解决方案在适用 DST 的情况下不起作用。例如,在我的情况下,在当前时间,DST 没有生效,但是如果我设置了应该转换为本地时间的初始日期with DST,那么它将不起作用,即今天的日期是 2018 年 3 月 1 日并且 DST 无效,但是如果我将转换日期设置为 2018 年 8 月 1 日 0:00:00 时 DST 生效,那么给出的解决方案将转换为当地时间,但不会考虑 DST。我发现将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;
    }
    

    【讨论】:

      【解决方案3】:

      一种简单而有效的方法:添加(或减去)您的时区和 UTC 之间的秒数(考虑夏令时)。

      举个例子,在一分钟前,即 2017 年 12 月 30 日,美国山区标准时间(无 DST)比 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
      

      享受吧。

      【讨论】:

        【解决方案4】:
        //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;}
                }
            }
        

        【讨论】:

          【解决方案5】:

          试试这个,测试输出: utcEpochTime: 1487652688, localEpochTime: 1487699488, diff: 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;
          }
          

          【讨论】:

            【解决方案6】:

            我按照@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 小时的时区偏移差异)。

            【讨论】:

              【解决方案7】:

              啊...我可能只是 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 柏林地区的夏季和冬季
              • 今天我什至不会这样写。这样做的原因是因为 mktime 不是可重入的(如果我没记错的话,它会触及全局 TZ 变量),并且随时可能爆炸。在我看来,这是一个巨大的失败……但话又说回来,标准化 API 的人还有其他事情要担心。但是,没有明智的方法可以做到这一点。 R.. 已经展示了一种自行计算日期的好方法。不幸的是,您必须为如此平凡的任务携带该功能,但另一方面,发布新标准需要很长时间。
              • 来自man pages for mktime()正值表示夏令时生效;零表示 DST 无效;负值意味着 mktime() 应该(使用时区信息和系统数据库)尝试确定 DST 是否在指定时间生效。 但是,稍后它说 tm_isdst 已设置(无论其初始值)分别为正值或 0,以指示 DST 在指定时间是否生效但行为似乎是在进入时信任tm_isdst 的值跨度>
              【解决方案8】:

              总而言之:使用 timegm() 将 UTC 中的分解日期 (struct tm) 转换为(本地)日历时间 (time_t) - 与 mktime() 相反 - 但 timegm() 是不是标准功能(逻辑如何)。 C 标准只剩下 time()、gmtime()、mktime() 和 difftime()。

              在其他文档中找到的解决方法建议通过首先将环境变量 TZ 设置为空字符串来模拟 timegm(),然后调用 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;
              }
              

              【讨论】:

              • 如果您当前处于 DST,这是错误的。 mktime(gmtime(X)) 不会为您提供当前适用的偏移量,而是时区偏移量(因此,如果 DST 开启,您可能会在夏季错过一小时)。
              • 另外,重要的是要记住 mktime 不是可重入的(与 localtime 和 gmtime 不同,它从函数签名中并不明显),因为它修改了全局时区变量。因此,此代码可能似乎可以工作,但任何其他正在执行 mktime 的线程都可能完全破坏您的输出。
              【解决方案9】:

              如果您可以假设 POSIX(因此 time_t 的 POSIX 规范为自纪元以来的秒数),我将首先使用 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 返回的偏移量与 seconds-since-the-epoch 值不会溢出 int。解决此问题的方法是使用long long 偏移量和不断添加不大于INT_MAX/2(或小于INT_MIN/2) 的增量并调用mktime 以重新归一化直到偏移量达到0 的循环。

              【讨论】:

              • 关于编辑:通常情况下,UTC 和本地时间之间的时间差与您转换时的差值相同,这肯定不是真的。也许在日本。
              • @Steve:不需要相同。您需要计算它的唯一原因是从本地时间的“纪元”调整偏移量(以秒为单位),以便您可以将非规范化的 struct tm 设为本地时间格式,然后将其传递给 mktime 进行规范化。
              • 感谢您的回答。我会试一试,然后告诉你结果如何。
              • 顺便说一句,C 让你如此费力地以一种理智的方式使用标准化的时间工作,这真的很恶心。只需拥有与 mktime 函数等效的 UTC 就可以避免整个问题。至少可以用上面的方法来做出这样的功能……
              • 是的,我知道你在说什么。这让我很惊讶。也许是因为这样的功能据说超出了 C 的范围......
              【解决方案10】:
              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);   
              } 
              

              【讨论】:

              • a) 那不是 C,b) 向我解释 FindFirstFile() 在 C99 标准中的位置。什么烂答案
              • 您似乎从未真正阅读过这个问题。更不用说您的代码依赖于存在的特定路径和目录结构才能工作。下次努力在问题参数范围内工作。
              猜你喜欢
              • 2016-01-24
              • 2013-08-13
              • 1970-01-01
              • 2013-03-12
              • 2019-03-30
              • 2021-10-10
              • 2020-02-08
              • 1970-01-01
              相关资源
              最近更新 更多