¿Por qué es el mes de enero 0 en Java Calendar?

En java.util.Calendar , enero se define como el mes 0, no el mes 1. ¿Hay alguna razón específica para eso?

He visto a mucha gente confundirse sobre eso …

Es solo parte del horrible desastre que es la API de fecha / hora de Java. Enumerar lo que está mal con él tomaría mucho tiempo (y estoy seguro de que no sé la mitad de los problemas). Admitir que trabajar con fechas y horas es complicado, pero de todos modos.

Hazte un favor y usa Joda Time en su lugar, o posiblemente JSR-310 .

EDITAR: En cuanto a las razones por las que, como se señala en otras respuestas, podría deberse a antiguas API C, o simplemente a una sensación general de comenzar todo desde 0 … excepto que los días comienzan con 1, por supuesto. Dudo que alguien ajeno al equipo de implementación original pueda realmente explicar las razones, pero una vez más, les pido a los lectores que no se preocupen tanto sobre por qué se tomaron malas decisiones, como para ver toda la gama de maldad en java.util.Calendar y encuentra algo mejor

Un punto que está a favor del uso de índices basados ​​en 0 es que facilita cosas como “matrices de nombres”:

 // I "know" there are 12 months String[] monthNames = new String[12]; // and populate... String name = monthNames[calendar.get(Calendar.MONTH)]; 

Por supuesto, esto falla tan pronto como obtienes un calendario con 13 meses … pero al menos el tamaño especificado es el número de meses que esperas.

Esta no es una buena razón, pero es una razón …

EDITAR: Como comentario, solicito algunas ideas sobre lo que creo que está mal con Fecha / Calendario:

  • Bases sorprendentes (1900 como el año base en fecha, sin duda para los constructores en desuso, 0 como el mes base en ambos)
  • Mutabilidad: el uso de tipos inmutables hace que sea mucho más simple trabajar con valores realmente efectivos
  • Un conjunto insuficiente de tipos: es bueno tener Date y Calendar como cosas diferentes, pero la separación de los valores “locales” frente a “zonificados” no se encuentra, así como la fecha / hora frente a la fecha frente al tiempo
  • Una API que conduce a un código feo con constantes mágicas, en lugar de métodos claramente nombrados
  • Una API que es muy difícil de razonar: todo el negocio sobre cuándo se vuelven a calcular las cosas, etc.
  • El uso de constructores sin parámetros para el valor predeterminado “ahora”, que lleva a código difícil de probar
  • La implementación de Date.toString() que siempre utiliza la zona horaria local del sistema (eso ha confundido a muchos usuarios de Stack Overflow antes de ahora)

Los lenguajes basados ​​en C copian C hasta cierto punto. La estructura tm (definida en time.h ) tiene un campo entero tm_mon con el rango (comentado) de 0-11.

Los lenguajes basados ​​en C inician las matrices en el índice 0. Por lo tanto, esto era conveniente para generar una cadena en una matriz de nombres de mes, con tm_mon como índice.

Porque hacer matemáticas con meses es mucho más fácil.

1 mes después de diciembre es enero, pero para resolver esto normalmente, debería tomar el número del mes y hacer las matemáticas

 12 + 1 = 13 // What month is 13? 

¡Lo sé! Puedo arreglar esto rápidamente usando un módulo de 12.

 (12 + 1) % 12 = 1 

Esto funciona bien durante 11 meses hasta noviembre …

 (11 + 1) % 12 = 0 // What month is 0? 

Puedes hacer que todo esto vuelva a funcionar al restar 1 antes de agregar el mes, luego hacer tu módulo y finalmente agregar 1 de nuevo … también trabajar alrededor de un problema subyacente.

 ((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers! 

Ahora pensemos en el problema con los meses 0 – 11.

 (0 + 1) % 12 = 1 // February (1 + 1) % 12 = 2 // March (2 + 1) % 12 = 3 // April (3 + 1) % 12 = 4 // May (4 + 1) % 12 = 5 // June (5 + 1) % 12 = 6 // July (6 + 1) % 12 = 7 // August (7 + 1) % 12 = 8 // September (8 + 1) % 12 = 9 // October (9 + 1) % 12 = 10 // November (10 + 1) % 12 = 11 // December (11 + 1) % 12 = 0 // January 

Todos los meses funcionan de la misma manera y no se necesita una solución alternativa.

Ha habido muchas respuestas a esto, pero daré mi opinión sobre el tema de todos modos. La razón detrás de este comportamiento extraño, como se indicó anteriormente, proviene de POSIX C time.h donde los meses se almacenaron en un int con el rango 0-11. Para explicar por qué, míralo así; los años y los días se consideran números en el lenguaje hablado, pero los meses tienen sus propios nombres. Entonces, debido a que enero es el primer mes, se almacenará como offset 0, el primer elemento de la matriz. monthname[JANUARY] sería "January" . El primer mes del año es el elemento del primer mes.

Los números del día, por otro lado, ya que no tienen nombres, almacenarlos en un int como 0-30 sería confuso, agregar muchas instrucciones de day+1 para la salida y, por supuesto, ser propenso a una gran cantidad de errores.

Dicho esto, la incoherencia es confusa, especialmente en javascript (que también ha heredado esta “característica”), un lenguaje de scripting donde este debe abstraerse del legado.

TL; DR : Porque los meses tienen nombres y los días del mes no.

Yo diría pereza. Las matrices comienzan en 0 (todos lo saben); los meses del año son un conjunto, lo que me lleva a creer que algún ingeniero de Sun simplemente no se molestó en poner este pequeño detalle en el código de Java.

Probablemente porque la “struct tm” de C hace lo mismo.

En Java 8, hay una nueva fecha / hora API JSR 310 que es más sensata. El lead de especificación es el mismo que el autor principal de JodaTime y comparten muchos conceptos y patrones similares.

Porque los progtwigdores están obsesionados con los índices basados ​​en 0. OK, es un poco más complicado que eso: tiene más sentido cuando se trabaja con lógica de nivel inferior para usar la indexación basada en 0. Pero, en general, seguiré con mi primera frase.

Personalmente, tomé la rareza de la API del calendario de Java como una indicación de que tenía que divorciarme de la mentalidad gregoriana y tratar de progtwigr de manera más agnóstica en ese sentido. Específicamente, aprendí una vez más a evitar las constantes codificadas por cosas como meses.

¿Cuál de los siguientes es más probable que sea correcto?

 if (date.getMonth() == 3) out.print("March"); if (date.getMonth() == Calendar.MARCH) out.print("March"); 

Esto ilustra una cosa que me molesta un poco acerca de Joda Time: puede alentar a los progtwigdores a pensar en términos de constantes codificadas. (Solo un poco, sin embargo. No es que Joda obligue a los progtwigdores a progtwigr mal).

java.util.Month

Java le proporciona otra forma de usar 1 índices basados ​​en meses durante meses. Use el java.time.Month enum. Un objeto está predefinido para cada uno de los doce meses. Tienen números asignados a cada 1-12 para enero-diciembre; llama a getValue para el número.

Haga uso de Month.JULY (le da 7) en lugar de Calendar.JULY (le da 6).

 (import java.time.*;) 

Para mí, nadie lo explica mejor que mindpro.com :

Gotchas

java.util.GregorianCalendar tiene muchos menos errores y errores que la old java.util.Date clase old java.util.Date , pero todavía no es un día de campo.

Si hubiera progtwigdores cuando se propuso por primera vez el horario de verano, lo habrían vetado como insano e intratable. Con el horario de verano, existe una ambigüedad fundamental. En el otoño, cuando configura sus relojes una hora a las 2 a.m., hay dos instantes diferentes en el tiempo, ambos llamados hora local 1:30 a.m. Puede diferenciarlos solo si registra si tenía previsto el horario de verano o el horario estándar con la lectura.

Desafortunadamente, no hay forma de decirle a GregorianCalendar cuál fue tu intención. Debe recurrir a decirle la hora local con el UTC TimeZone ficticio para evitar la ambigüedad. Los progtwigdores generalmente cierran los ojos a este problema y solo esperan que nadie haga nada durante esta hora.

Error del milenio. Los errores aún no están fuera de las clases de Calendario. Incluso en JDK (Java Development Kit) 1.3 hay un error de 2001. Considera el siguiente código:

 GregorianCalendar gc = new GregorianCalendar(); gc.setLenient( false ); /* Bug only manifests if lenient set false */ gc.set( 2001, 1, 1, 1, 0, 0 ); int year = gc.get ( Calendar.YEAR ); /* throws exception */ 

El error desaparece a las 7 a.m. en 2001/01/01 para MST.

GregorianCalendar está controlado por un gigante de stacks de constantes mágicas int sin tipo. Esta técnica destruye totalmente cualquier esperanza de comprobación de errores en tiempo de comstackción. Por ejemplo, para obtener el mes que usa GregorianCalendar. get(Calendar.MONTH)); GregorianCalendar. get(Calendar.MONTH));

GregorianCalendar tiene el crudo GregorianCalendar.get(Calendar.ZONE_OFFSET) y el horario de verano GregorianCalendar. get( Calendar. DST_OFFSET) GregorianCalendar. get( Calendar. DST_OFFSET) , pero no hay forma de que se use el desplazamiento de zona horaria real. Debe obtener estos dos por separado y agréguelos.

GregorianCalendar.set( year, month, day, hour, minute) no establece los segundos en 0.

DateFormat y GregorianCalendar no se engranan correctamente. Debe especificar el Calendario dos veces, una vez indirectamente como una Fecha.

Si el usuario no ha configurado correctamente su zona horaria, se ejecutará de forma predeterminada en PST o GMT.

En GregorianCalendar, los meses se numeran comenzando en enero = 0, en lugar de 1 como lo hacen todos los demás en el planeta. Sin embargo, los días comienzan en 1 como lo hacen los días de la semana con domingo = 1, lunes = 2, … Sábado = 7. Sin embargo, DateFormat. el análisis se comporta de la manera tradicional con enero = 1.

tl; dr

 Month.FEBRUARY.getValue() // February → 2. 

2

Detalles

La respuesta de Jon Skeet es correcta.

Ahora tenemos un reemplazo moderno para esas viejas clases problemáticas de fecha y hora heredadas: las clases java.time .

java.time.Month

Entre esas clases está el Month enum . Una enumeración lleva uno o más objetos predefinidos, objetos que se instancian automáticamente cuando se carga la clase. En Month tenemos una docena de dichos objetos, cada uno con un nombre: JANUARY , FEBRUARY , MARCH , etc. Cada una de ellas es una constante de clase static final public . Puede usar y pasar estos objetos a cualquier parte de su código. Ejemplo: someMethod( Month.AUGUST )

Afortunadamente, tienen una buena cantidad, 1-12, donde 1 es enero y 12 es diciembre.

Obtenga un objeto Month para un número de mes en particular (1-12).

 Month month = Month.of( 2 ); // 2 → February. 

Yendo en la otra dirección, pida un objeto Month para su número de mes.

 int monthNumber = Month.FEBRUARY.getValue(); // February → 2. 

Muchos otros métodos útiles en esta clase, como conocer la cantidad de días en cada mes . La clase puede incluso generar un nombre localizado del mes.

Puede obtener el nombre localizado del mes, en varias longitudes o abreviaturas.

 String output = Month.FEBRUARY.getDisplayName( TextStyle.FULL , Locale.CANADA_FRENCH ); 

février

Además, debe pasar objetos de esta enumeración alrededor de su base de código en lugar de simples números enteros . Al hacerlo, proporciona seguridad de tipo, garantiza un rango válido de valores y hace que su código sea más autodocumentado. Consulte el Tutorial de Oracle si no está familiarizado con la instalación enum sorprendentemente poderosa en Java.

También puede encontrar útiles las clases de Year y YearMonth .


Acerca de java.time

El marco java.time está integrado en Java 8 y posterior. Estas clases suplantan a las antiguas clases heredadas de fecha y hora, como java.util.Date , .Calendar y java.text.SimpleDateFormat .

El proyecto Joda-Time , ahora en modo de mantenimiento , aconseja la migración a java.time.

Para obtener más información, consulte el Tutorial de Oracle . Y busque Stack Overflow para obtener muchos ejemplos y explicaciones. La especificación es JSR 310 .

¿Dónde obtener las clases de java.time?

  • Java SE 8 y SE 9 y posteriores
    • Incorporado.
    • Parte de la API Java estándar con una implementación integrada.
    • Java 9 agrega algunas características y correcciones menores.
  • Java SE 6 y SE 7
    • Gran parte de la funcionalidad de java.time se transfiere a Java 6 y 7 en ThreeTen-Backport .
  • Androide
    • El proyecto ThreeTenABP adapta ThreeTen-Backport (mencionado anteriormente) específicamente para Android.
    • Vea Cómo usar …

El proyecto ThreeTen-Extra amplía java.time con clases adicionales. Este proyecto es un terreno de prueba para posibles adiciones futuras a java.time. Puede encontrar algunas clases útiles aquí, como Interval , YearWeek , YearQuarter y más .

Además de la respuesta de pereza de DannySmurf, agregaré que es para alentarlo a usar las constantes, como Calendar.JANUARY .

No se define exactamente como cero per se, se define como Calendar.January. Es el problema de usar ints como constantes en lugar de enums. Calendar.January == 0.

Debido a que la escritura del lenguaje es más difícil de lo que parece, y el tiempo de manejo en particular es mucho más difícil de lo que la mayoría de la gente piensa. Para una pequeña parte del problema (en realidad, no de Java), consulte el video de YouTube “El problema con el tiempo y las zonas horarias: informático” en https://www.youtube.com/watch?v=-5wpm-gesOY . No se sorprenda si su cabeza se cae de la risa en la confusión.

Porque todo comienza con 0. Este es un hecho básico de la progtwigción en Java. Si una cosa se desviara de eso, eso llevaría a una gran confusión. No discutamos la formación de ellos y el código con ellos.