¿Cómo convertir de UTC a la hora local en C?

10 minutos de lectura

avatar de usuario
aseq

Es una pregunta simple, pero la solución parece estar lejos de ser simple. Me gustaría saber cómo convertir de UTC a la hora local. Estoy buscando una solución en C que sea estándar y que esté más o menos garantizada para funcionar en cualquier computadora en cualquier lugar.

He leído detenidamente los siguientes enlaces pero no encuentro solución allí:

Convertir una cadena que contiene la hora local en UTC en C

Conversión entre horas locales y GMT/UTC en C/C++

He probado una serie de variaciones, como (datetime es una cadena con hora y fecha en UTC):

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

O

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

No importa lo que intente, el tiempo de impresión termina siendo el mismo que UTC.

Editar 29-11-2013: basado en la respuesta muy útil de “R” a continuación, finalmente logré crear un ejemplo de trabajo. Descubrí que funcionaba correctamente en las dos zonas horarias en las que lo probé, CET y 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);
}

Debería compilarse sin problemas en la mayoría de los sistemas. Codifiqué una hora y fecha en UTC que luego se convertirá a la hora y fecha locales.

  • ¿Podemos asumir POSIX (ya que usas strptime) o simplemente C?

    – R.. GitHub DEJA DE AYUDAR A ICE

    31 de enero de 2012 a las 8:52

  • Lo siento, me lo perdí, sí, puedes asumir POSIX.

    – aseq

    2 de febrero de 2012 a las 9:41

avatar de usuario
Dachschaden

Ahm … Puede que solo sea un principiante en C, pero obtuve este ejemplo de trabajo:

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

Lo que produce esta salida:

Marca de tiempo local: 1412554119

Marca de tiempo GMT: 1412546919

Diferencia en horas: 2

Ahora tienes la diferencia entre la hora UTC y la hora local en segundos. Eso debería ser suficiente para convertirlo.

Una nota a su código, aseq: está usando malloc sin necesidad aquí (también puede establecer valores en la pila, y malloc puede ser costoso, mientras que la asignación de la pila suele ser mucho más rápida), y no lo libera. Eso es muy, muy mala práctica.

Otra cosa:

conjunto de memoria (tp, 0, tamaño de (estructura tm));

Sería mejor si pasara sizeof(*tp) (o, si pone tp en la pila, sizeof(tp)) a memset. Eso asegura que incluso si el tipo de su objeto cambia, seguirá siendo completamente memset.

  • Gracias, probé esto en ubuntu y funciona para verano e invierno en GMT+1 Regiones de Berlín

    – Óliver

    5 de enero de 2015 a las 23:10

  • Hoy ni siquiera lo escribiría así. La razón de esto es que mktime no es reentrante (toca la variable global TZ, si no me equivoco) y puede explotar en cualquier momento. Lo cual es un gran fracaso en mi opinión… pero, de nuevo, la gente que estandarizó la API tenía otras cosas de las que preocuparse. Sin embargo, no hay una forma sensata de hacerlo. R.. ya mostró una buena manera de calcular la fecha por ti mismo. Es desafortunado que tenga que continuar con esa función para una tarea tan mundana, pero por otro lado, lleva mucho tiempo publicar estándares más nuevos.

    – Dachschaden

    8 de enero de 2015 a las 6:20


  • Desde el páginas man para mktime(): un valor positivo significa que el horario de verano está en vigor; cero significa que el horario de verano no está en vigor; y un valor negativo significa que mktime() debería (utilizar la información de la zona horaria y las bases de datos del sistema para) intentar determinar si el horario de verano está en vigor a la hora especificada. Sin embargo, más adelante dice tm_isdst se establece (independientemente de su valor inicial) en un valor positivo o en 0, respectivamente, para indicar si DST está o no en vigor en el momento especificado pero el comportamiento parece ser confiar en el valor de tm_isdst en la entrada

    – Chisholm Kyle

    5 mayo 2016 a las 16:16


avatar de usuario
Nicolás Novak

Descubrí que la solución que dio el OP no funcionó en los casos en que se aplica el horario de verano. Por ejemplo, en mi caso, en el momento actual, el horario de verano no estaba vigente, pero si configuro la fecha inicial que debe convertirse a la hora local con DST, entonces no funcionaría, es decir, la fecha de hoy es el 1/3/2018 y el horario de verano no está vigente, pero si configuro la fecha para la conversión, digamos, 1/8/2018 0:00:00 cuando DST es en efecto, la solución dada se convertiría a la hora local, pero no tendría en cuenta el horario de verano. Encontré que inicializando e0 a la fecha y hora de la cadena de fecha/hora inicial y su miembro tm_isdst para -1 resuelve el problema. Luego creé el siguiente programa con funciones complementarias que puedes incluir en tu código. El formato inicial de fecha y hora es el mismo que usa MySQL, porque lo necesitaba para tales fines.

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

avatar de usuario
Denés Borsos

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

avatar de usuario
zhaixiaohu

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) eso no es C, b) explícame dónde está FindFirstFile() en el estándar C99. que mierda de respuesta

    – Adrián Cornish

    31 de enero de 2012 a las 8:37

  • Parecería que en realidad nunca leyó la pregunta. sin mencionar que su código se basa en una ruta particular y una estructura de directorio que está presente para funcionar. esfuércese más para trabajar dentro de los parámetros de las preguntas la próxima vez.

    – mattr-

    5 de marzo de 2012 a las 22:14

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad