Fechas y horas en C con <time.h>: pasar de segundos a fecha, formatear con strftime, convertir entre zonas horarias, sumar días y calcular diferencias sin errores, medir cuánto tarda algo y las trampas de struct tm.
También te puede interesar
C maneja el tiempo con dos representaciones de <time.h>: time_t, un número de segundos desde el 1 de enero de 1970 a medianoche en UTC (la época Unix), y struct tm, con el año, el mes, el día y la hora por separado. Casi todo consiste en pasar de una a otra.
De segundos a fecha y de fecha a texto
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t t = 1798761600;
struct tm utc;
if (gmtime_r(&t, &utc) == nullptr) return 1;
char texto[64];
strftime(texto, sizeof texto, "%Y-%m-%d %H:%M:%S", &utc);
printf("%lld segundos = %s UTC\n", (long long) t, texto);
strftime(texto, sizeof texto, "%d/%m/%Y, día %j del año", &utc);
printf("%s\n", texto);
static const char *dias[] = {"domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"};
static const char *meses[] = {"enero", "febrero", "marzo", "abril", "mayo", "junio", "julio",
"agosto", "septiembre", "octubre", "noviembre", "diciembre"};
printf("Es %s, %d de %s de %d\n", dias[utc.tm_wday], utc.tm_mday, meses[utc.tm_mon], utc.tm_year + 1900);
return 0;
}salida1798761600 segundos = 2027-01-01 00:00:00 UTC 01/01/2027, día 001 del año Es viernes, 1 de enero de 2027
El ejemplo usa una fecha fija para que la salida sea siempre la misma; la hora actual se obtiene con time(nullptr). Tres trampas de struct tm que todo el mundo pisa alguna vez: el año se cuenta desde 1900 (tm_year vale 127 en 2027), los meses van de 0 a 11 y los días de la semana empiezan en domingo, con el 0. strftime escribe con formato sin pasarse del búfer, pero los nombres de días y meses (%A, %B) salen en el idioma del locale, que en muchos servidores y contenedores es el inglés: por eso el programa usa sus propios nombres.
- FormatoDaEjemplo
%Y-%m-%dfecha ISO 86012027-01-01%H:%M:%Shora de 24 h00:00:00%d/%m/%Yfecha al estilo español01/01/2027%jdía del año001%z,%Zdiferencia con UTC y nombre de la zona-0600 CST
Zonas horarias
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
static void mostrar(time_t t, const char *zona)
{
setenv("TZ", zona, 1);
tzset();
struct tm local;
localtime_r(&t, &local);
char texto[64];
strftime(texto, sizeof texto, "%Y-%m-%d %H:%M %z %Z", &local);
printf("%-20s %s\n", zona, texto);
}
int main(void)
{
time_t t = 1798761600;
mostrar(t, "UTC");
mostrar(t, "America/Mexico_City");
mostrar(t, "America/Bogota");
mostrar(t, "Europe/Madrid");
return 0;
}salidaUTC 2027-01-01 00:00 +0000 UTC America/Mexico_City 2026-12-31 18:00 -0600 CST America/Bogota 2026-12-31 19:00 -0500 -05 Europe/Madrid 2027-01-01 01:00 +0100 CET
El mismo instante, el mismo time_t, es una hora distinta en cada sitio. localtime_r convierte según la zona del sistema, que se cambia con la variable de entorno TZ y tzset. La información de cada zona (incluidos los cambios de horario de verano) viene de la base de datos tzdata del sistema, que hay que mantener actualizada. Usa siempre las versiones con _r (gmtime_r, localtime_r): las de sin _r devuelven un puntero a un struct tm compartido que la siguiente llamada sobrescribe, y no son seguras con hilos.
Sumar días y calcular diferencias
#include <stdio.h>
#include <time.h>
int main(void)
{
struct tm f = {.tm_year = 2026 - 1900, .tm_mon = 1, .tm_mday = 26, .tm_hour = 12};
f.tm_mday += 5;
timegm(&f); /* normaliza f: el 31 de febrero pasa a marzo */
char texto[32];
strftime(texto, sizeof texto, "%Y-%m-%d", &f);
printf("26 de febrero + 5 días = %s\n", texto);
struct tm inicio = {.tm_year = 2026 - 1900, .tm_mon = 9, .tm_mday = 30};
struct tm navidad = {.tm_year = 2026 - 1900, .tm_mon = 11, .tm_mday = 25};
double segundos = difftime(timegm(&navidad), timegm(&inicio));
printf("Del 30 de octubre a Navidad: %.0f días\n", segundos / 86400);
return 0;
}salida26 de febrero + 5 días = 2026-03-03 Del 30 de octubre a Navidad: 56 días
El truco para sumar días es cambiar el campo y dejar que la conversión lo normalice: el 31 de febrero se convierte en el 3 de marzo. timegm convierte un struct tm en UTC a time_t y normaliza de paso; existe en glibc, musl, macOS y los BSD, y C23 lo ha incorporado al estándar. La versión local, mktime, es estándar desde siempre, pero aplica la zona horaria y el horario de verano, y en los días del cambio de hora un día no tiene 86,400 segundos. Para fechas sin hora, trabajar en UTC evita esas sorpresas.
Medir cuánto tarda algo
#include <stdio.h>
#include <time.h>
int main(void)
{
struct timespec a, b;
clock_gettime(CLOCK_MONOTONIC, &a);
volatile double suma = 0;
for (int i = 0; i < 10000000; i++) suma += i * 0.5;
clock_gettime(CLOCK_MONOTONIC, &b);
double ms = (b.tv_sec - a.tv_sec) * 1e3 + (b.tv_nsec - a.tv_nsec) / 1e6;
printf("Diez millones de sumas: %s\n", ms > 0 ? "medido" : "error");
printf("(el tiempo exacto cambia en cada ejecución)\n");
return 0;
}salidaDiez millones de sumas: medido (el tiempo exacto cambia en cada ejecución)
Para medir intervalos se usa un reloj monótono, que nunca va hacia atrás, y no la hora del sistema, que puede cambiar en mitad de la medida si se sincroniza con un servidor de hora. clock_gettime con CLOCK_MONOTONIC es de POSIX; C11 añadió timespec_get, que en C23 admite también un reloj monótono, aunque no todas las bibliotecas lo implementan. (El programa no imprime los milisegundos para que la salida sea comprobable; en el tuyo, imprime ms.)
Un último aviso: en sistemas donde time_t es de 32 bits, el contador se desborda el 19 de enero de 2038, el llamado problema del año 2038. En Linux de 64 bits time_t ya es de 64 bits, pero en sistemas embebidos y formatos de archivo antiguos sigue siendo un riesgo real.
Todo el código de esta página se compiló con GCC 15.3 (C23, -Wall -Wextra, sin avisos) y se ejecutó en Linux, en un contenedor limpio sin acceso a la red, antes de publicarla; las salidas están copiadas de esa ejecución.