El 1 de enero no siempre cae en la primera semana del año

Y otras trampas relacionadas.

You can read the English version of this post here.

El año pasado en el trabajo un día estaba programando algo relacionado con recopilación de datos. Le agregaba una marca temporal (timestamp) a las filas en la base de datos y, por razones que no vienen al caso, también ponía en columnas separadas el año, la semana y el mes de la marca temporal.

Suena fácil. El código en Python era básicamente este:

from datetime import datetime

timestamp = datetime.now()  # 2026-03-14 14:17:08.052495

year = timestamp.year  # 2026
month = timestamp.month  # 3
week = timestamp.isocalendar().week  # 11

Cuando escribí el código no me paré a pensar en los casos límite. Seguro a que si le pides a tu LLM de preferencia que te dé un fragmento de código que obtenga el año, la semana y el mes a partir de una marca temporal, producirá algo muy similar.

Esto parecía funcionar bien, y de hecho el código ejecutó sin problemas durante muchos meses. Sin embargo, en enero de este año noté que los datos alrededor del principio de año estaban mal. Por alguna razón, los últimos días de diciembre tenían los valores: año 2025, mes 12, semana 1. ¿¿Cómo??

Esto hacía parecer que los registros se habían añadido en la primera semana de 2025. Sí, el valor del mes era 12, pero ¡la primera semana del año no está en diciembre!

Ensayalo tú mismo:

timestamp = datetime.strptime("2025-12-31 09:30:00", "%Y-%m-%d %H:%M:%S")

year = timestamp.year  # 2025
month = timestamp.month  # 12
week = timestamp.isocalendar().week  # 1

Así que empecé buscar cuál era el problema, y pronto descubrí que existe una página entera de Wikipedia dedicada a esto. Fue entonces cuando me di cuenta de que mis suposiciones estaban totalmente equivocadas.

El problema radica en cómo se define la primera semana del año. Y existe un estándar para eso: el ISO 8601. Si hubiera leído detenidamente la documentación de la función isocalendar(), que obtiene la semana a partir de la fecha, me habría dado cuenta de todo esto mucho antes.

Entonces, ¿cuál es la primera semana del año? #

Yo había asumido sin pensarlo mucho que:

  • El 1 de enero siempre cae en la primera semana del año.
  • El 31 de diciembre siempre cae en la última semana del año.

Pero resulta que esto es totalmente erróneo. El estándar ISO 8601 dice que una semana es un periodo de 7 días, que comienza un lunes y termina un domingo. Muy bien. Pero entonces surge el problema: a menos que el 31 de diciembre caiga en domingo (y el 1 de enero en lunes), ambos días pertenecen a la misma semana.

Dicho así parece bastante obvio. Pero ¿a qué semana pertenecen entonces? ¿A la primera semana del año nuevo, a la última del año que termina…? De nuevo, el estándar ISO tiene la respuesta. Traduzco directamente de Wikipedia:

Si el 1 de enero cae en lunes, martes, miércoles o jueves, pertenece a la semana 1. Si cae en viernes, forma parte de la semana 53 del año anterior. Si cae en sábado, forma parte de la última semana del año anterior, que se numera como 52 en un año común y 53 en un año bisiesto. Si cae en domingo, forma parte de la semana 52 del año anterior.

Si el 31 de diciembre cae en lunes, martes o miércoles, pertenece a la semana 1 del año siguiente. Si cae en jueves, pertenece a la semana 53 del año que termina. Si cae en viernes, pertenece a la semana 52 del año que termina en años comunes y a la semana 53 en años bisiestos. Si cae en sábado o domingo, pertenece a la semana 52 del año que termina.

Bastante complicado. Pero en esencia significa lo siguiente:

  • La primera semana del año es la que incluye el primer jueves del año.
  • En algunos casos, los últimos días de diciembre corresponden a la primera semana del año nuevo.
  • En algunos casos, los primeros días de enero corresponden a la última semana del año anterior.

¡Por eso mi código asignaba esos registros a la semana 1! Pero debería ser la primera semana del 2026, no del 2025. Resulta que debería haber usado el año ISO en lugar del año calendario. Así quedaría:

timestamp = datetime.strptime("2025-12-31 09:30:00", "%Y-%m-%d %H:%M:%S")

year = timestamp.isocalendar().year  # 2026
week = timestamp.isocalendar().week  # 1

Suena algo contraintuitivo que algunas fechas de 2025 correspondan al año 2026. Pero ese es el precio que hay que pagar si queremos usar los números de semana.

Sin embargo todavía no hemos resuelto el problema del mes. Resulta que no existe un “mes ISO”, así que no nos queda de otra que usar timestamp.month. Esto significa que incluso si hubiera usado el año ISO en mi código, habría obtenido datos incorrectos: año 2026, semana 1, mes 12.

¿Y entonces qué podemos hacer? #

Supongo que hay 3 opciones.

1. Usa el calendario ISO, olvídate del mes #

Si te importa más la semana que el mes, simplemente usa el calendario ISO. Producirá resultados consistentes, pero debes tener en cuenta que algunas fechas se asignarán al “otro” año.

dates = [
  "2025-12-30 09:30:00",
  "2025-12-31 09:30:00",
  "2026-01-01 09:30:00",
  "2026-01-02 09:30:00",
]

for d in dates:
  timestamp = datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
  year = timestamp.isocalendar().year
  week = timestamp.isocalendar().week
  print(f"Year: {year}, Week: {week}")


# Year: 2026, Week: 1
# Year: 2026, Week: 1
# Year: 2026, Week: 1
# Year: 2026, Week: 1

Para el caso contrario, puedes probar con algunas fechas cercanas al 1 de enero de 2021. Verás que se asignan a la semana 53 de 2020.

2. Usa el año y el mes del calendario, olvídate de la semana #

Esta opción evita las complicaciones del estándar ISO. Las fechas del año 2025 se asignan al 2025 y punto.

dates = [
  "2025-12-30 09:30:00",
  "2025-12-31 09:30:00",
  "2026-01-01 09:30:00",
  "2026-01-02 09:30:00",
]

for d in dates:
  timestamp = datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
  year = timestamp.year
  month = timestamp.month
  print(f"Year: {year}, Month: {month}")

# Year: 2025, Month: 12
# Year: 2025, Month: 12
# Year: 2026, Month: 1
# Year: 2026, Month: 1

3. Ajustar el mes #

Si realmente quieres conservar los tres, tendrás que ajustar el mes para que coincida con el año y la semana ISO. Si usas esta opción, recuerda usar el año ISO.

dates = [
  "2025-12-30 09:30:00",
  "2025-12-31 09:30:00",
  "2026-01-01 09:30:00",
  "2026-01-02 09:30:00",
]

for d in dates:
  timestamp = datetime.strptime(d, "%Y-%m-%d %H:%M:%S")
  year = timestamp.isocalendar().year
  week = timestamp.isocalendar().week
  month = timestamp.month

  if week == 1 and month == 12:
    # Esta fecha de diciembre corresponde a la primera semana de enero
    month = 1
  elif week > 50 and month == 1:
    # Esta fecha de enero corresponde a la última semana de diciembre
    month = 12

  print(f"Year: {year}, Month: {month}, Week: {week}")

# Year: 2026, Month: 1, Week: 1
# Year: 2026, Month: 1, Week: 1
# Year: 2026, Month: 1, Week: 1
# Year: 2026, Month: 1, Week: 1

Creo que esto funciona bien, pero aun así recomiendo usarlo con precaución. Si algo nos ha enseñado este post es que el calendario es traicionero.

Extra #

Consulta en esta lista otras ideas erróneas que solemos tener los programadores sobre fechas y horas. Quizás “El 1 de enero siempre cae en la primera semana del año” debería estar en ella.