Gestión de tiempo y fechas con la librería time.h

Cuando programas en C, el tiempo no es un valor único y simple, sino que se maneja de dos formas: como un punto en el tiempo (una fecha específica) o como una duración (cuánto tiempo ha pasado). Para esto, el lenguaje nos proporciona la librería <time.h>.

Para entender cómo funciona, imagina que el tiempo es un contador gigante de segundos que empezó a correr el 1 de enero de 1970 a las 00:00:00 UTC (esto se conoce como la Unix Epoch o Época Unix). El tipo de dato time_t es simplemente un número entero que guarda cuántos segundos han pasado desde ese momento. Es una forma muy eficiente para que la computadora compare fechas: para saber si una fecha es posterior a otra, solo tiene que ver qué número es más grande.

Sin embargo, los humanos no leemos “1715678400 segundos desde 1970”; nosotros leemos “2024-05-14 10:00:00”. Para esto usamos la struct tm, que es una estructura (un grupo de variables relacionadas) que desglosa ese número gigante en piezas manejables como segundos, minutos, horas, día, mes y año. Aquí es donde debes tener mucho cuidado: en la struct tm, los meses van del 0 al 11 (enero es 0) y los años se cuentan a partir de 1900 (para representar 2024, el valor de la variable tm_year debe ser 124).

Si necesitas medir cuánto tarda en ejecutarse un programa, tienes dos opciones. Puedes usar clock(), que mide el tiempo de CPU (el tiempo real que el procesador ha estado ocupado trabajando específicamente en tu programa, sin contar el tiempo que el programa estuvo esperando a que el usuario hiciera clic). Pero si lo que quieres es saber cuánto tiempo ha pasado en el reloj de tu pared (el tiempo real transcurrido), lo correcto en C11 es usar timespec_get, que incluso te da precisión de nanosegundos gracias a la estructura struct timespec.

Si intentas manejar fechas con un tipo de dato time_t de solo 32 bits, te encontrarás con el “Problema del año 2038”: el contador se llenará y el sistema creerá que volvimos a 1902. Por eso, los sistemas modernos usan 64 bits para este valor.

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

int main() {
    // 1. Obtener el tiempo actual (punto en el tiempo)
    time_t ahora = time(NULL);

    // 2. Convertir el tiempo actual a una estructura legible (tiempo desglosado)
    // localtime devuelve un puntero a una estructura struct tm
    struct tm *info_actual = localtime(&ahora);

    // 3. Mostrar la fecha actual usando strftime para formatear
    // strftime escribe la fecha formateada en un buffer (un array de char)
    char buffer[64];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", info_actual);
    printf("Fecha y hora actual: %s\n", buffer);

    // 4. Crear una fecha específica (1 de enero de 2020)
    struct tm fecha_especifica = {0}; // Inicializamos a cero para evitar basura
    fecha_especifica.tm_sec = 0;
    fecha_especifica.tm_min = 0;
    fecha_especifica.tm_hour = 0;
    fecha_especifica.tm_mday = 1;      // Día 1
    fecha_especifica.tm_mon = 0;       // Enero (0)
    fecha_especifica.tm_year = 2020 - 1900; // Año 2020 (20)

    // mktime convierte una struct tm de nuevo a time_t
    time_t t_especifica = mktime(&fecha_especifica);
    printf("Timestamp de la fecha elegida: %ld\n", (long)t_especifica);

    // 5. Medir el tiempo transcurrido (Wall time con C11)
    struct timespec inicio, fin;
    // Usamos TIME_SPAN para medir el tiempo real transcurrido
    timespec_get(&inicio, TIME_SPAN);

    // Simulamos un trabajo pesado con un bucle
    for (volatile long i = 0; i < 100000000; i++);

    timespec_get(&fin, TIME_SPAN);

    // Calculamos la diferencia en segundos y nanosegundos
    double segundos = (double)fin.tv_sec - inicio.tv_sec;
    long nanosegundos = fin.tv_nsec - inicio.tv_nsec;

    printf("El bucle tardó: %.3f segundos y %ld ns\n", segundos, nanosegundos);

    return 0;
}

Desglose del ejemplo

En el código anterior, primero usamos time(NULL) para capturar el momento exacto en que se ejecuta la línea. Ese valor se almacena en ahora, que es de tipo time_t.

Para que podamos entender ahora, la función localtime toma ese número de segundos y lo “descompone” en la estructura struct tm llamada info_actual. La función strftime es fundamental para la salida de datos; no es un simple printf, sino una herramienta especializada para convertir estructuras de tiempo en texto legible mediante códigos como %Y (año de 4 dígitos) o %H (hora).

Cuando queremos definir una fecha manual, creamos fecha_especifica. Fíjate en la línea fecha_especifica.tm_year = 2020 - 1900;. Este es el estándar de la librería: siempre restamos 1900 para que la estructura sea coherente. Luego, mktime hace el trabajo inverso: toma ese objeto desglosado y lo convierte de nuevo en un único número time_t mediante t_especifica.

Finalmente, para la medición de rendimiento, hemos usado timespec_get con la constante TIME_SPAN. A diferencia de clock(), que te diría cuánto tiempo ha estado el procesador trabajando (y puede ser mucho mayor si tienes muchos procesos corriendo), timespec_get nos da el tiempo real de reloj. La estructura timespec tiene dos campos: tv_sec para segundos y tv_nsec para los nanosegundos, permitiéndonos una precisión mucho mayor que el simple segundo de time_t.

El error frecuente

Un error muy común al trabajar con struct tm es olvidar que los campos tm_mon (mes) y tm_year (año) no usan una escala humana estándar.

struct tm error_fecha = {0};
error_fecha.tm_mday = 15;
error_fecha.tm_mon = 5;      // El programador piensa: "Mayo es el mes 5"
error_fecha.tm_year = 24;    // El programador piensa: "Estamos en el año 24"

time_t t = mktime(&error_fecha);

Si ejecutas esto, mktime interpretará que quieres el mes Junio (porque el mes 5 es junio en el índice 0-11) y el año 1924 (porque el año 24 es 1900 + 24). Esto genera errores lógicos silenciosos muy difíciles de encontrar. Además, si usas clock() para medir el rendimiento de una función que solo hace sleep(), clock() reportará casi cero, porque el proceso no usó la CPU mientras dormía; para medir esperas, siempre usa timespec_get.

133

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio