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())
}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"))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 UTCPor 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)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"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)
}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)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")
}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)
}
}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.