Java y las fechas. Menos mal que es historia antigua. En los viejos tiempos, todos dábamos por hecho que la clase Date original iba a funcionar sin más: marcas de tiempo simples, sin complicaciones. Luego explotó con los miedos al Y2K y métodos rarísimos, así que los antecesores de Oracle nos endilgaron Calendar como el salvador. Buen intento, pero torpe de narices. Ahora llega java.time.LocalDate como el tercer asalto, aclamada como la solución infalible. Excepto que no lo es. Ni de lejos.
¿Cambia esto todo? Para nada. Solo cambia un dolor de cabeza por otro, obligando a los programadores a restar 1 cada vez que pillan el nombre de un mes. ¿Quién sale ganando? Tú no, eso seguro, frente a tu error de índice de array a las 2 de la mañana.
Mira, llevo cubriendo Java desde las guerras de los applets: 20 años viendo a Sun, Oracle y ahora Eclipse pelearse por el timón. Las fechas siempre han sido el talón de Aquiles del lenguaje. Las zonas horarias cambian como los políticos, los segundos bisiestos se cuelan, y cada iteración de API finge domar el caos. ¿LocalDate? Moderna, inmutable, amiga del ISO. Genial en teoría. Pero asómate al capó con Month.getMonth() y el cinismo te da una bofetada.
¿Por qué arranca el mes de LocalDate en Java contando desde 1?
Aquí está el quid. Coge LocalDate.now() —digamos, 2026-04-07, como en la queja original—. Llama a getMonth().getValue() y ¡zas!: 4 para abril. Lógico, ¿no? Coincide con ISO 8601, donde enero es 1. Los no programadores asienten contentos. Pero los devs… nosotros venimos programados para todo basado en cero desde el C de K&R.
Tu array fiel de nombres de meses —Enero en 0, Febrero en 1— de repente no cuadra. ¿Indexas con getValue()? Aterrizas en Mayo en vez de Abril. El clásico off-by-one, la plaga de nuestra profesión.
Fecha: 2026-04-07 Mes (val): Mayo Mes (ord): Abril
Eso sale directo del demo de código en la fuente original. Ejecutado el 7 de abril, imprime Abril correctamente vía ordinal() (basado en 0, índice 3). ¿Pero getValue()? Obliga a un apaño: MESES[fecha.getMonth().getValue() - 1]. Feo. Y ahora metido en cada codebase nueva.
Ordinal() sigue funcionando —menos mal—, manteniendo enero=0. Pero ¿por qué partir la diferencia? Es como ponerle a tu coche un cuadro nuevo donde el odómetro arranca en 1 kilómetro. Intuitivo para los pasajeros, desesperante para los mecánicos.
Pero ojo: ¿no es mejor lo de basarlo en 1?
Hora de una tabla. Tomada y adaptada de la original:
| Método | Basado en | Ejemplo | Concepción |
|---|---|---|---|
| getValue() | 1 | Enero == 1 | Intuitiva |
| ordinal() | 0 | Enero == 0 | Costumbre |
Punto válido. Los humanos contamos enero como 1. Los niños lo aprenden así. Pero ¿el código? El localtime() de C mete tm_mon como 0. ¿Date.getMonth() de JavaScript? Basado en cero, súmale 1 para mostrar. Perl, PHP… lo mismo. Hasta el viejo Calendar.get(Calendar.MONTH) iba de 0 a 11.
¿Romperá esto tu código Java legado?
Respuesta corta: si migras a java.time, sí… pero a traición. Ese truquito rápido de nombres de meses que todo el mundo ha copiado y pegado durante décadas? Muerto. Vas a depurar meses españoles raros (o ingleses, según) saliendo en los logs. Predigo un pico de quejas en Stack Overflow para el Q2 de 2025, cuando los equipos actualicen por el Jakarta EE de turno.
Ángulo único que nadie ha tocado: esto huele a las guerras de Enum ordinal() en Java 5. Entonces la gente berreaba cuando los enums dejaron los valores numéricos por trucos de reflexión. Oracle no aprendió nada. Persiguen la ‘pureza’ —cumplimiento ISO— por encima de roturas pragmáticas. ¿Quién se frota las manos? Los consultores de Red Hat cobrando auditorías de fechas. ¿El HotSpot JVM de Oracle? Se vende igual.
¿Y la solución en código? Tonta, pero por todas partes. Cada tutorial, cada clase utils… reescribe. En el Java empresarial, donde las fechas pululan por nóminas y apps de cumplimiento, eso son horas facturables