Menu

Time en Golang: Now, Sleep, Format, Parse y duraciones

Cómo trabajar con fechas y horas en Go: time.Now y time.Sleep, aritmética con Duration, el layout de referencia 2006-01-02 15:04:05 para Format y Parse, zonas horarias, timestamps Unix, y timers y tickers.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Lo básico en un programa

time.Now() devuelve la hora local actual, time.Sleep pausa la goroutine actual, time.Since mide el tiempo transcurrido y time.Date construye un momento concreto. El resto de esta página trata cada pieza por separado. Los ejemplos usan fechas fijas y UTC para que su salida sea la misma dondequiera que los ejecutes.

Sleep y Duration

Un time.Duration es un int64 que cuenta nanosegundos. El paquete define constantes para construir valores legibles:

ConstanteValor
time.Nanosecond1
time.Microsecond1000 ns
time.Millisecond1000 µs
time.Second1000 ms
time.Minute60 s
time.Hour60 min

No existe time.Day, porque un día no siempre dura 24 horas cuando una zona horaria cambia por el horario de verano. Usa AddDate para los días de calendario (más abajo).

Dos bugs habituales vienen de que Duration sea un entero:

  • time.Sleep(5) duerme 5 nanosegundos. Multiplica siempre por una unidad: time.Sleep(5 * time.Second).
  • time.Sleep(n * time.Second) no compila cuando n es una variable int. Conviértela: time.Duration(n) * time.Second. Una constante como 5 funciona sin conversión porque las constantes sin tipo se adaptan al tipo Duration.

ParseDuration acepta ns, us (o µs), ms, s, m y h, combinados como "2h45m" o "1.5s". No tiene unidad para los días.

Formato: el layout de referencia 2006-01-02

Go no usa %Y-%m-%d ni yyyy-MM-dd. Un layout es la hora de referencia

Mon Jan 2 15:04:05 MST 2006

escrita tal como quieres que se vea tu salida. Los valores se eligieron para que cada uno sea único: mes 1, día 2, hora 3 (o 15), minuto 4, segundo 5, año 6 (2006), desfase de zona -7 (-0700). Léelo como 01/02 03:04:05PM '06 -0700.

Los tokens de layout que más vas a necesitar:

TokenSignificadoEjemplo
2006 / 06año, 4 o 2 dígitos2026 / 26
01 / 1 / Jan / Januarymes03 / 3 / Mar / March
02 / 2 / _2día del mes (con ceros, sin relleno, con espacios)05 / 5 / " 5"
Mon / Mondaydía de la semanaThu / Thursday
15hora, formato de 24 horas09
03 / 3hora, formato de 12 horas09 / 9
04 / 4minuto07 / 7
05 / 5segundo03 / 3
PM / pmmarca AM o PMAM
.000 / .999fracciones de segundo (fijas / sin ceros finales).250 / .25
MSTabreviatura de la zonaUTC
-0700 / -07:00desfase numérico de la zona+0000 / +00:00
Z07:00como -07:00, pero imprime Z para UTCZ

Layouts predefinidos:

ConstanteLayout
time.RFC33392006-01-02T15:04:05Z07:00 (úsalo para APIs y JSON)
time.RFC3339Nano2006-01-02T15:04:05.999999999Z07:00
time.DateTime (Go 1.20)2006-01-02 15:04:05
time.DateOnly (Go 1.20)2006-01-02
time.TimeOnly (Go 1.20)15:04:05
time.Kitchen3:04PM
time.RFC1123Mon, 02 Jan 2006 15:04:05 MST (las fechas HTTP usan http.TimeFormat)

El error clásico es escribir un layout con los números equivocados, por ejemplo "2023-01-01". Go no lo rechaza. Copia los caracteres que no reconoce y sustituye los que sí: cada 2 es el día, 3 es la hora en formato de 12 horas y los dos 01 son el mes, así que el 5 de marzo a las 9:07 se formatea como 5059-03-03. Un layout como "YYYY-MM-DD" no contiene ningún token y se imprime tal cual. Si las fechas formateadas se ven raras, comprueba que el layout usa exactamente los valores de referencia.

Parsear strings a horas

time.Parse(layout, value) usa los mismos layouts y devuelve un error que tienes que comprobar:

Sin zona en la entrada, Parse devuelve UTC. El mismo string de hora de reloj puede significar instantes distintos según la zona, y por eso existe ParseInLocation. El mensaje de error nombra la parte que no coincidió, lo que ayuda al depurar un layout.

Zonas horarias

Un time.Time es un instante más una ubicación que se usa para mostrarlo. Cambiar la ubicación con In cambia cómo se imprime, no qué momento es.

  • time.UTC siempre está disponible. Guarda y transmite las horas en UTC (o en RFC 3339 con desfase) y convierte solo para mostrarlas.
  • time.Local es la zona de la máquina. En servidores y contenedores suele ser UTC, y en un portátil no, así que el código que depende de ella se comporta distinto en cada sitio.
  • time.LoadLocation("Europe/Berlin") lee la base de datos IANA del sistema operativo. Las imágenes de contenedor mínimas a menudo no la tienen, y entonces la llamada devuelve un error. El import vacío _ "time/tzdata" incrusta la base de datos en tu binario (unos 450 KB) para que siempre funcione. Maneja siempre el error.
  • time.FixedZone(name, offsetSeconds) crea una zona con un desfase constante. No tiene reglas de horario de verano, así que úsala para desfases que has recibido, no para regiones con nombre.

Aritmética y comparación

Cosas en las que fijarse en la salida:

  • AddDate(0, 1, 0) sobre el 31 de enero da el 3 de marzo, no el 28 de febrero. Go suma un mes para obtener "31 de febrero" y luego normaliza el exceso. Si necesitas "el mismo día del mes siguiente, ajustado al último día", escribe tú esa lógica.
  • Sub devuelve un Duration, que llega como mucho a unos 292 años. Para diferencias en días de calendario, compara las fechas a medianoche UTC y divide entre 24 horas.
  • Para obtener el inicio del día, reconstruye la hora a partir de Date(). t.Truncate(24 * time.Hour) redondea respecto a la hora cero en UTC, así que da una respuesta equivocada en cualquier zona que no sea UTC.

Compara con Equal, Before y After, nunca con ==. time.Now() incluye una lectura del reloj monotónico, que sirve para que time.Since siga siendo correcto si se ajusta el reloj del sistema. == compara también esa lectura y la ubicación, así que dos valores Time del mismo instante pueden ser distintos. Por la misma razón, no uses time.Time como clave de un map sin normalizarlo antes (t.UTC().Round(0) quita la lectura monotónica).

El time.Time cero es el 1 de enero del año 1, a las 00:00 UTC. Compruébalo con t.IsZero().

Medir el tiempo transcurrido

start := time.Now()
doWork()
log.Printf("doWork took %v", time.Since(start))

time.Since(start) equivale a time.Now().Sub(start), y time.Until(deadline) a deadline.Sub(time.Now()). Los dos usan el reloj monotónico cuando está disponible, así que son seguros frente a cambios del reloj del sistema. Para hacer benchmarks de código, usa el soporte de benchmarks de Go del paquete testing en lugar de medir a mano.

Timers y tickers

time.After(d) devuelve un channel que recibe una vez pasado d. Un time.Timer es lo mismo con un método Stop. Un time.Ticker entrega un valor cada periodo hasta que lo detienes.

Los tres ticks a los 20, 40 y 60 ms llegan mucho antes que el timer de 110 ms, así que el programa se detiene tras el tercer tick. time.AfterFunc(d, f) ejecuta f en su propia goroutine pasado d, lo que viene bien para trabajo diferido puntual.

Desde Go 1.23, los timers y tickers a los que ya nada hace referencia se recolectan aunque nunca llames a Stop, y el channel de un timer detenido o reiniciado ya no entrega un valor obsoleto. Llamar a Stop con defer sigue siendo la forma clara de decir que la vida del ticker ha terminado. Cuando un límite de tiempo se aplica a una operación entera y no a una sola espera, un context.WithTimeout suele leerse mejor que un timer.

Time en JSON

time.Time se serializa y deserializa automáticamente como strings RFC 3339 en JSON, con precisión de nanosegundos:

type Event struct {
	Name string    `json:"name"`
	At   time.Time `json:"at"`
}
// {"name":"deploy","at":"2026-09-23T14:30:00Z"}

Un string JSON con otro formato falla al deserializar. Para timestamps Unix o formatos propios, guarda un int64 o un string y convierte, o define un tipo con su propio UnmarshalJSON.

Errores comunes

  • time.Sleep(1) o time.Sleep(n) con un número a secas. Eso son nanosegundos.
  • Layouts con los dígitos equivocados. "2023-01-01" o "YYYY-MM-DD" no son layouts. Usa "2006-01-02".
  • Confundir minutos y meses. Los minutos son 04 y los meses 01. "15:01" imprime la hora y luego el mes, no el minuto.
  • Comparar con ==. Usa Equal.
  • Depender de time.Local. Cambia de una máquina a otra. Sé explícito con time.UTC o con una ubicación cargada.
  • Ignorar el error de LoadLocation o de Parse. Los dos fallan con entradas reales. Un Parse fallido devuelve la hora cero, que se imprime como el año 1, y un LoadLocation fallido devuelve una ubicación nil que hace que t.In(loc) provoque un panic.

Preguntas frecuentes

¿Cómo hago un sleep en Go?

Llama a time.Sleep con un time.Duration: time.Sleep(2 * time.Second) o time.Sleep(500 * time.Millisecond). Un número a secas como time.Sleep(2) compila pero duerme 2 nanosegundos, porque un Duration cuenta nanosegundos. Sleep solo bloquea la goroutine actual.

¿Por qué Go usa 2006-01-02 15:04:05 para los formatos de fecha?

Go formatea las fechas por ejemplo. El layout es la hora de referencia Mon Jan 2 15:04:05 MST 2006 escrita tal como quieres que se vea tu salida. Sus partes van en orden creciente según el formato estadounidense: mes 1, día 2, hora 3 (15 en formato de 24 horas), minuto 4, segundo 5, año 6 (2006), desfase de zona 7 (-0700). Así que "2006-01-02" significa año-mes-día y "02/01/2006" significa día/mes/año.

¿Cómo parseo un string de fecha en Go?

Usa time.Parse(layout, value) con un layout escrito con la hora de referencia: t, err := time.Parse("2006-01-02", "2026-09-23"). Comprueba siempre err. Si el string no incluye información de zona, el resultado está en UTC; usa time.ParseInLocation para interpretarlo en otra zona.

¿Cómo obtengo un timestamp Unix en Go?

time.Now().Unix() devuelve los segundos desde el 1 de enero de 1970 UTC como un int64. UnixMilli(), UnixMicro() y UnixNano() dan unidades más finas. Para ir en sentido contrario, usa time.Unix(sec, 0) o time.UnixMilli(ms).

¿Cómo comparo dos horas en Go?

Usa t1.Before(t2), t1.After(t2) y t1.Equal(t2). No uses ==: también compara la ubicación y la lectura del reloj monotónico, así que dos valores del mismo instante pueden ser distintos. t2.Sub(t1) da la diferencia como un Duration.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR