Por que é o mês de janeiro 0 no Calendário Java?

Em java.util.Calendar , janeiro é definido como mês 0, não mês 1. Existe algum motivo específico para isso?

Eu tenho visto muitas pessoas se confundindo com isso …

É apenas parte da bagunça horrenda que é a API de data / hora do Java. Listar o que há de errado com isso levaria muito tempo (e tenho certeza de que não conheço metade dos problemas). É certo que trabalhar com datas e horas é complicado, mas aaargh de qualquer maneira.

Faça um favor a si mesmo e use o Joda Time , ou possivelmente o JSR-310 .

EDIT: Quanto às razões pelas quais – como observado em outras respostas, poderia muito bem ser devido a antigas APIs C, ou apenas uma sensação geral de começar tudo a partir de 0 … exceto que os dias começam com 1, é claro. Eu duvido que alguém fora da equipe de implementação original pudesse realmente indicar as razões – mas, novamente, eu pedia aos leitores que não se preocupassem tanto com o porquê das más decisões terem sido tomadas, quanto a olhar para toda a gama de maldade em java.util.Calendar e encontre algo melhor.

Um ponto que é a favor do uso de índices baseados em 0 é que ele facilita coisas como “matrizes de nomes”:

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

Claro, isso falha assim que você começa um calendar com 13 meses … mas pelo menos o tamanho especificado é o número de meses que você espera.

Esta não é uma boa razão, mas é uma razão …

EDIT: Como um comentário tipo de solicita algumas idéias sobre o que eu acho que está errado com a data / calendar:

  • Bases surpreendentes (1900 como a base anual em Date, reconhecidamente para construtores depreciados; 0 como a base mensal em ambos)
  • Mutabilidade – usando tipos imutáveis ​​torna muito mais simples trabalhar com valores realmente efetivos
  • Um conjunto insuficiente de tipos: é bom ter Date e Calendar como coisas diferentes, mas a separação de valores “local” vs “zoned” está ausente, como data / hora vs data vs hora
  • Uma API que leva a códigos feios com constantes mágicas, em vez de methods claramente nomeados
  • Uma API que é muito difícil de raciocinar – todos os negócios sobre quando as coisas são recalculadas, etc.
  • O uso de construtores sem parâmetros para usar o padrão “now”, o que leva a um código difícil de testar
  • A implementação de Date.toString() que sempre usa o fuso horário local do sistema (isso confundiu muitos usuários do Stack Overflow antes)

Os idiomas baseados em C copiam C em algum grau. A estrutura tm (definida em time.h ) possui um campo inteiro tm_mon com o intervalo (comentado) de 0-11.

Linguagens baseadas em C iniciam arrays no índice 0. Portanto, isso era conveniente para gerar uma string em uma matriz de nomes de meses, com tm_mon como o índice.

Porque fazer matemática com meses é muito mais fácil.

1 mês depois de dezembro é janeiro, mas para descobrir isso normalmente você teria que pegar o número do mês e fazer as contas

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

Eu sei! Eu posso consertar isso rapidamente usando um módulo de 12.

 (12 + 1) % 12 = 1 

Isso funciona bem por 11 meses até novembro …

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

Você pode fazer todo esse trabalho novamente, subtraindo 1 antes de adicionar o mês, então faça o seu módulo e, finalmente, adicione 1 novamente … também conhecido como trabalho em torno de um problema subjacente.

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

Agora vamos pensar sobre o problema com os 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 os meses funcionam da mesma forma e um trabalho não é necessário.

Tem havido muitas respostas para isso, mas vou dar a minha opinião sobre o assunto de qualquer maneira. A razão por trás desse comportamento estranho, como dito anteriormente, vem do POSIX C time.h onde os meses foram armazenados em um int com o intervalo 0-11. Para explicar porque, olhe para isto assim; anos e dias são considerados números na língua falada, mas os meses têm seus próprios nomes. Então, como janeiro é o primeiro mês, ele será armazenado como offset 0, o primeiro elemento da matriz. monthname[JANUARY] seria "January" . O primeiro mês do ano é o primeiro elemento da matriz do mês.

Os números do dia, por outro lado, uma vez que não têm nomes, armazená-los em um int como 0-30 seria confuso, adicionar um monte de instruções day+1 para a saída e, claro, ser propenso a um monte de bugs.

Dito isto, a inconsistência é confusa, especialmente em javascript (que também herdou esse “recurso”), uma linguagem de script onde isso deveria ser abstraído da langague.

TL; DR : Porque os meses têm nomes e dias do mês não.

Eu diria preguiça. Matrizes começam em 0 (todos sabem disso); os meses do ano são uma matriz, o que me leva a acreditar que algum engenheiro da Sun simplesmente não se deu ao trabalho de colocar essa pequena informação no código Java.

Provavelmente porque o “struct tm” de C faz o mesmo.

No Java 8, há uma nova API de data / hora JSR 310 que é mais sensata. O lead de especificação é o mesmo do autor principal do JodaTime e eles compartilham muitos conceitos e padrões similares.

Porque os programadores estão obcecados com índices baseados em 0. OK, é um pouco mais complicado do que isso: faz mais sentido quando você está trabalhando com uma lógica de baixo nível para usar a indexação baseada em 0. Mas, em geral, ainda vou continuar com a minha primeira frase.

Pessoalmente, tomei a estranheza da API do calendar Java como uma indicação de que eu precisava me divorciar da mentalidade cinput no Gregoriano e tentar programar mais agnosticamente a esse respeito. Especificamente, aprendi mais uma vez a evitar constantes codificadas por coisas como meses.

Qual das opções a seguir é mais provável de estar correta?

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

Isso ilustra uma coisa que me irrita um pouco sobre a Joda Time – pode incentivar os programadores a pensar em termos de constantes codificadas. (Apenas um pouco, no entanto. Não é como se Joda estivesse forçando os programadores a programar mal.)

java.util.Month

Java fornece outra maneira de usar índices baseados em 1 por meses. Use o enum java.time.Month . Um object é predefinido para cada um dos doze meses. Eles têm números atribuídos a cada 1-12 para janeiro-dezembro; chame getValue para o número.

Faça uso de Month.JULY (Dá 7) em vez de Calendar.JULY (Dá 6).

 (import java.time.*;) 

Para mim, ninguém explica melhor do que mindpro.com :

Gotchas

java.util.GregorianCalendar tem muito menos bugs e pegadinhas do que a old java.util.Date class old java.util.Date mas ainda não é um piquenique.

Se houvesse programadores quando o Horário de Verão foi proposto pela primeira vez, eles teriam o vetado como insano e intratável. Com o horário de verão, existe uma ambiguidade fundamental. No outono, quando você ajusta seus relógios de volta uma hora às 2 horas da manhã, há dois instantes diferentes no tempo, chamados de 1:30 AM, horário local. Você pode diferenciá-los apenas se registrar se pretendia horário de verão ou horário padrão com a leitura.

Infelizmente, não há como dizer ao GregorianCalendar que você pretendia. Você deve recorrer a contar a hora local com o manequim UTC TimeZone para evitar a ambigüidade. Os programadores geralmente fecham os olhos para esse problema e esperam que ninguém faça nada durante essa hora.

Bug do milênio. Os bugs ainda não estão fora das classs do Calendário. Mesmo no JDK (Java Development Kit) 1.3 existe um erro de 2001. Considere o seguinte 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 */ 

O bug desaparece às 7 da manhã de 2001/01/01 para o MST.

GregorianCalendar é controlado por um gigante de pilha de constantes mágicas int não tipificadas. Essa técnica destrói totalmente qualquer esperança de verificação de erros em tempo de compilation. Por exemplo, para obter o mês você usa GregorianCalendar. get(Calendar.MONTH)); GregorianCalendar. get(Calendar.MONTH));

GregorianCalendar tem o GregorianCalendar.get(Calendar.ZONE_OFFSET) bruto e o horário de verão GregorianCalendar. get( Calendar. DST_OFFSET) GregorianCalendar. get( Calendar. DST_OFFSET) , mas nenhuma maneira de obter o deslocamento de fuso horário real sendo usado. Você deve pegar esses dois separadamente e adicioná-los juntos.

GregorianCalendar.set( year, month, day, hour, minute) não define os segundos como 0.

DateFormat e GregorianCalendar não se encheckboxm corretamente. Você deve especificar o calendar duas vezes, uma vez indiretamente como uma data.

Se o usuário não tiver configurado seu fuso horário corretamente, ele será padronizado silenciosamente para PST ou GMT.

No GregorianCalendar, os meses são numerados a partir de janeiro = 0, em vez de 1 como todos os outros no planeta. No entanto, os dias começam em 1 e os dias da semana com domingo = 1, segunda-feira = 2,… sábado = 7. Ainda DateFormat. parse se comporta da maneira tradicional com janeiro = 1.

tl; dr

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

2

Detalhes

A resposta de Jon Skeet está correta.

Agora, temos um substituto moderno para as antigas classs de data e hora herdadas: as classs java.time .

java.time.Month

Entre essas classs está o enum do Month . Um enum carrega um ou mais objects predefinidos, objects que são automaticamente instanciados quando a class é carregada. No Month , temos uma dúzia desses objects, cada um com um nome: JANUARY , FEBRUARY , MARCH e assim por diante. Cada uma dessas é uma constante de class static final public . Você pode usar e passar esses objects para qualquer lugar em seu código. Exemplo: someMethod( Month.AUGUST )

Felizmente, eles têm uma numeração sadia, 1-12 em que 1 é janeiro e 12 é dezembro.

Obtenha um object Month para um determinado número de mês (1-12).

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

Indo na outra direção, peça a um object Month por seu número de mês.

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

Muitos outros methods úteis nesta class, como saber o número de dias em cada mês . A turma pode até gerar um nome localizado do mês.

Você pode obter o nome localizado do mês, em vários tamanhos ou abreviações.

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

février

Além disso, você deve passar objects desse enum em torno de sua base de código em vez de meros números inteiros . Isso fornece segurança de tipo, garante um intervalo válido de valores e torna seu código mais autodocumentado. Veja o Oracle Tutorial se não estiver familiarizado com o recurso enum surpreendentemente poderoso em Java.

Você também pode achar útil as classs Year e YearMonth .


Sobre o java.time

O framework java.time está embutido no Java 8 e posterior. Essas classs substituem as antigas classs herdadas de data e hora legadas , como java.util.Date , .Calendar e java.text.SimpleDateFormat .

O projeto Joda-Time , agora em modo de manutenção , aconselha a migration para java.time.

Para saber mais, veja o Oracle Tutorial . E pesquise o Stack Overflow para muitos exemplos e explicações. A especificação é JSR 310 .

Onde obter as classs java.time?

  • Java SE 8 e SE 9 e posterior
    • Construídas em.
    • Parte da API Java padrão com uma implementação integrada.
    • O Java 9 adiciona alguns resources e correções menores.
  • Java SE 6 e SE 7
    • Grande parte da funcionalidade java.time é transferida para o Java 6 e 7 no ThreeTen-Backport .
  • Android
    • O projeto ThreeTenABP adapta o ThreeTen-Backport (mencionado acima) especificamente para o Android.
    • Veja como usar… .

O projeto ThreeTen-Extra estende java.time com classs adicionais. Este projeto é um campo de testes para possíveis futuras adições ao java.time. Você pode encontrar algumas classs úteis aqui como Interval , YearWeek , YearQuarter e muito mais .

Além da resposta de preguiça de DannySmurf, acrescentarei que é para encorajar você a usar as constantes, como Calendar.JANUARY .

Não é exatamente definido como zero por si só, é definido como Calendar.January. É o problema de usar ints como constantes em vez de enums. Calendar.January == 0.

Porque a escrita de línguas é mais difícil do que parece, e lidar com o tempo em particular é muito mais difícil do que a maioria das pessoas pensa. Para uma pequena parte do problema (na verdade, não o Java), veja o vídeo do YouTube “O problema do tempo e fusos horários – Computerphile” em https://www.youtube.com/watch?v=-5wpm-gesOY . Não se surpreenda se sua cabeça cair de rir em confusão.

Porque tudo começa com 0. Esse é um fato básico da programação em Java. Se uma coisa fosse se desviar disso, então isso levaria a toda uma confusão. Não vamos discutir a formação deles e codificar com eles.