manejo-fechas-horas-timers-go

Fechas, horas y temporizadores en Go con el paquete time

  • 5 min

time.Time es el tipo de Go para representar un instante concreto en el tiempo. A partir de ahí podemos formatear fechas, medir duraciones y programar temporizadores.

El tiempo es complicado: años bisiestos, cambios de horario y zonas que se modifican por decisiones políticas.

El paquete estándar time representa instantes con resolución de nanosegundos (la precisión real del reloj depende del sistema). Además, tiene una forma peculiar de definir formatos de fecha.

Vamos a medir duraciones, convertir zonas horarias y entender por qué el número 2006 aparece en los formatos de Go.

El instante actual: time.Time

El tipo principal es time.Time. Representa un instante y puede incluir una lectura monotónica para medir intervalos dentro del mismo proceso.

package main

import (
    "fmt"
    "time"
)

func main() {
    // Obtener el instante actual
    ahora := time.Now()

    fmt.Println("Ahora:", ahora)
    // Salida: 2023-10-27 10:00:00.000000 +0200 CEST m=+0.000

    // Obtener partes específicas
    fmt.Println("Año:", ahora.Year())
    fmt.Println("Mes:", ahora.Month())
    fmt.Println("Día:", ahora.Day())
}
Copied!

Formateo y parsing: la fecha de referencia

En Java o Python, si quieres formatear una fecha usas códigos como YYYY-MM-DD HH:mm:ss. En Go, NO.

En Go, para definir un formato, debes escribir cómo se vería una fecha de referencia específica en ese formato.

La fecha de referencia es:

Lunes, 2 de Enero de 2006, a las 15:04:05 (MST)

¿Por qué esta fecha tan rara? Porque si la ordenas al estilo americano, los números son: 1 2 3 4 5 6.

  • Mes: 01
  • Día: 02
  • Hora: 03 (PM) -> 15
  • Minuto: 04
  • Segundo: 05
  • Año: 2006

Formatear un instante

t := time.Now()

// Quiero formato: Año-Mes-Día
fmt.Println(t.Format("2006-01-02"))

// Quiero formato: Día/Mes/Año Hora:Minuto
fmt.Println(t.Format("02/01/2006 15:04"))

// Quiero formato: Hora PM
fmt.Println(t.Format("03:04 PM"))
Copied!

Interpretar un texto

Para convertir un texto en fecha, usamos time.Parse.

layout := "2006-01-02"
texto := "2023-12-31"

fecha, err := time.Parse(layout, texto)
if err != nil {
    panic(err)
}
fmt.Println(fecha) // 2023-12-31 00:00:00 +0000 UTC
Copied!

Por defecto, time.Parse asume UTC. Si quieres parsear una fecha en la zona horaria local o específica, usa time.ParseInLocation.

Aritmética de tiempo y duraciones

Go tiene un tipo específico para los intervalos de tiempo: time.Duration. Internamente es un int64 que cuenta nanosegundos, pero Go nos regala constantes para operar fácilmente: time.Second, time.Minute, time.Hour.

Sumar y Restar

ahora := time.Now()

// ¿Qué hora será dentro de 2 horas y media?
futuro := ahora.Add(2*time.Hour + 30*time.Minute)

// ¿Qué hora fue hace 10 minutos?
pasado := ahora.Add(-10 * time.Minute)
Copied!

Calcular la diferencia (Sub y Since)

Para medir cuánto tarda algo:

inicio := time.Now()

// ... operación costosa ...
time.Sleep(2 * time.Second)

// Opción 1: Restar instantes
duracion := time.Now().Sub(inicio)

// Opción 2: Usar Since (Más idiomático)
duracion = time.Since(inicio)

fmt.Println("Ha tardado:", duracion) // "Ha tardado: 2.001s"
Copied!

Si ambos valores conservan su lectura monotónica, Sub y Since la usan. Así, un ajuste del reloj del sistema no altera la medición.

Zonas Horarias (Location)

Manejar zonas horarias es importante. Un servidor en AWS puede estar en UTC, pero tus usuarios en España quieren ver la hora en CET/CEST.

Para cambiar la zona de un objeto time.Time, usamos el método .In().

func main() {
    t := time.Now() // Hora local del sistema

    // Cargar zona horaria (requiere base de datos de zonas en el SO o en Go)
    locNY, err := time.LoadLocation("America/New_York")
    if err != nil {
        fmt.Println("No se pudo cargar la zona:", err)
        return
    }

    // Convertir el instante a la hora de NY
    tNY := t.In(locNY)

    fmt.Println("Local:", t)
    fmt.Println("NY:   ", tNY)
}
Copied!

Timers y tickers

El paquete time se integra perfectamente con los Canales de Go, permitiéndonos gestionar eventos temporales.

time.Sleep

Detiene la goroutine actual durante al menos la duración indicada. No bloquea por sí solo las demás goroutines.

time.Sleep(1 * time.Second)
Copied!

time.After: un canal de un solo uso

Devuelve un canal que recibe un valor después del tiempo indicado. Muy útil en select para Timeouts.

canal := make(chan string)

select {
case msg := <-canal:
    fmt.Println(msg)
case <-time.After(3 * time.Second):
    fmt.Println("Timeout: Se acabó el tiempo de espera")
}
Copied!

time.NewTicker: eventos repetidos

Si necesitas ejecutar algo cada X tiempo (un heartbeat, una limpieza de caché), usa un Ticker.

ticker := time.NewTicker(1 * time.Second)
// Lo detenemos cuando ya no queremos recibir más ticks.
defer ticker.Stop()

done := make(chan struct{})

go func() {
    time.Sleep(5 * time.Second)
    close(done)
}()

for {
    select {
    case <-done:
        fmt.Println("Ticker detenido")
        return
    case t := <-ticker.C:
        fmt.Println("Tick a las", t)
    }
}
Copied!

Desde Go 1.23 el recolector puede recuperar tickers que ya no tienen referencias, aunque no se haya llamado a Stop. Aun así, Stop sigue expresando que ya no queremos más eventos. Si el receptor va más lento que el intervalo, el ticker puede ajustar o descartar ticks para compensar.