Как конфертировать из UTC в местное время в C? - PullRequest
9 голосов
/ 31 января 2012

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

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

Преобразование строки, содержащей местное время, в 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 <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, которые затем будут преобразованы в местное время и дату.

Ответы [ 10 ]

7 голосов
/ 31 января 2012

Если вы можете принять 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, чтобы канонизировать его.

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

Edit 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.

5 голосов
/ 06 октября 2014

Ааа ... Я мог бы быть новичком в 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

Метка времени по Гринвичу: 1412546919

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

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

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

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

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

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

3 голосов
/ 16 июля 2012

Подводя итог: преобразование разбитой даты (struct tm) в UTC в (местное) календарное время (time_t) достигается с помощью timegm () - противоположность mktime () - НО 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;
}
2 голосов
/ 10 августа 2017
//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;}
        }
    }
1 голос
/ 05 июля 2018

Я думаю, что это проще, чем это;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 указывает, что летнее время находится в игре, а ноль - нет.

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

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

1 голос
/ 01 марта 2018

Я обнаружил, что решение, данное ОП, не работает в случаях, когда применяется DST.Например, в моем случае, в настоящее время DST не действует, но если я установлю начальную дату, которая должна быть преобразована в местное время с DST, то это не будет работать, то есть сегодняшняя дата3/1/2018 и DST не действует, но если я установлю дату для конвертации, скажем, 01.08.2008 0:00:00, когда 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;
}
0 голосов
/ 31 декабря 2017

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

В качестве примера, который прекрасно работал минуту назад, 30 декабря 2017 года, по стандартному времени США в горах (без перехода на летнее время), что на 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

Наслаждайтесь.

0 голосов
/ 21 февраля 2017

попробуйте это, проверьте вывод: Время utcEpoch: 1487652688, время LocalEpoch: 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 голосов
/ 05 мая 2016

Я последовал за ответом @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 голосов
/ 31 января 2012
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);   
} 
...