Por que o mês de janeiro é 0 no Java Calendar?

300

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

Vi muitas pessoas ficando confusas com isso ...

Stéphane Bonniez
fonte
4
Não é esse tipo de detalhe de implementação, já que as constantes JANEIRO, FEVEREIRO etc. existem? As classes de data são anteriores ao suporte adequado ao java enum.
gnud
6
Ainda mais irritante - por que existe um Undecember?
mate b
40
@gnud: Não, não é um detalhe de implementação. É doloroso quando você recebe um número inteiro na base "natural" (ou seja, Jan = 1) e precisa usá-lo com a API do calendário.
Jon Skeet
1
@matt b: é para calendários não gregorianos (calendários lunares, etc.) com treze meses. É por isso que é melhor não pensar em termos de números, mas deixe o Calendar fazer sua localização.
Erickson
7
O argumento de 13 meses não faz sentido. Nesse caso, por que o mês extra não é 0 ou 13?
Quinn Taylor

Respostas:

323

É 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 horários é complicado, mas 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, pode ser devido a APIs C antigas, ou apenas uma sensação geral de começar tudo de 0 ... exceto que os dias começam com 1, é claro. Duvido que alguém de fora da equipe de implementação original possa realmente explicar os motivos - mas, novamente, exortaria os leitores a não se preocuparem tanto com o porquê de terem sido tomadas más decisões, a olhar para toda a gama de maldade java.util.Calendare encontrar 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)];

Obviamente, isso falha assim que você recebe um calendário 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 comentário, solicite algumas idéias sobre o que acho errado com Data / Calendário:

  • Bases surpreendentes (1900 como a base do ano em Date, reconhecidamente para construtores obsoletos; 0 como a base do mês em ambos)
  • Mutabilidade - o uso de tipos imutáveis ​​facilita muito o trabalho com valores realmente efetivos
  • Um conjunto insuficiente de tipos: é bom ter Datee Calendarcomo coisas diferentes, mas a separação dos valores "local" x "zoneado" está ausente, assim como data / hora vs data vs hora
  • Uma API que leva a um código feio com constantes mágicas, em vez de métodos claramente nomeados
  • Uma API 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 padronizar para "agora", o que leva a códigos difíceis de testar
  • A Date.toString()implementação que sempre usa o fuso horário local do sistema (que confunde muitos usuários do Stack Overflow antes de agora)
Jon Skeet
fonte
14
... e o que há com a descontinuação de todos os métodos simples e úteis do Date? Agora tenho que usar esse objeto horrível do Calendário de maneiras complicadas para fazer coisas que costumavam ser simples.
Brian Knoblauch
3
@ Brian: Eu sinto sua dor. Mais uma vez, Joda O tempo é mais simples :) (O fator imutabilidade torna as coisas muito mais agradável para trabalhar, também.)
Jon Skeet
8
Você não respondeu a pergunta.
585 Zeemee
2
@ user443854: listei alguns pontos em uma edição - veja se isso ajuda.
9119 Jon Skeet
2
Se você estiver usando o Java 8, poderá abandonar a classe Calendar e mudar para a nova e elegante API DateTime . A nova API também inclui um DateTimeFormatter imutável / seguro para threads, que é uma grande melhoria em relação ao SimpleDateFormat problemático e caro.
Ccpizza
43

Porque fazer contas 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 contas

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

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

(12 + 1) % 12 = 1

Isso funciona muito bem por 11 meses até novembro ...

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

Você pode fazer tudo isso funcionar novamente subtraindo 1 antes de adicionar o mês, faça seu módulo e, finalmente, adicione 1 novamente ... ou seja, trabalhe com um problema subjacente.

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

Agora, vamos pensar no problema com os meses de 0 a 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 maneira e uma solução alternativa não é necessária.

Arucker
fonte
5
Isso é satisfatório. Pelo menos há algum valor nessa loucura!
precisa saber é o seguinte
"Muitos números mágicos" - nah, é apenas um que aparece duas vezes.
user123444555621
Voltar um mês ainda é meio nojento, graças ao uso infeliz de C de um operador "restante" em vez de "módulo". Também não tenho certeza de quantas vezes alguém realmente precisa bater um mês sem ajustar o ano, e passar os meses 1 a 12 não cria problemas com `while (mês> 12) {month- = 12; ano ++;}
supercat
2
Como funções sãs como DateTime.AddMonths são muito difíceis de serem implementadas corretamente em uma lib, temos que fazer as contas que você descreveu ... Mmmmmkay
nsimeonov 30/03/16
8
Eu não entendo esses upvotes - ((11 - 1 + 1) % 12) + 1 = 12é apenas (11 % 12) + 1por meses 1..12, você só precisa adicionar o 1 depois de fazer o módulo. Nenhuma mágica é necessária.
Mfitzp
35

Os idiomas baseados em C copiam C até certo ponto. A tmestrutura (definida em time.h) possui um campo inteiro tm_moncom o intervalo (comentado) de 0 a 11.

Os idiomas baseados em C iniciam matrizes no índice 0. Portanto, isso era conveniente para gerar uma string em uma matriz de nomes de meses, com tm_mono índice.

stesch
fonte
22

Houve muitas respostas para isso, mas, de qualquer maneira, darei minha opinião sobre o assunto. A razão por trás desse comportamento estranho, conforme declarado anteriormente, vem do POSIX C, time.honde os meses foram armazenados em um int com o intervalo de 0 a 11. Para explicar o porquê, veja assim; anos e dias são considerados números na língua falada, mas os meses têm seus próprios nomes. Portanto, como janeiro é o primeiro mês, ele será armazenado como deslocamento 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 dos dias, por outro lado, como eles não têm nomes, armazená-los em um int de 0 a 30 seriam confusos, acrescentam muitas day+1instruções para a saída e, é claro, tendem a ter muitos bugs.

Dito isto, a inconsistência é confusa, especialmente em javascript (que também herdou esse "recurso"), uma linguagem de script em que isso deve ser abstraído longe do idioma.

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

piksel bitworks
fonte
1
"meses têm nomes e dias não." Já ouviu falar de 'Friday'? ;) OK, acho que você quis dizer '..dias do mês não' '- talvez valha a pena editar sua resposta (caso contrário, boa). :-)
Andrew Thompson
0/0/0000 é melhor renderizado como "00-Jan-0000" ou como "00-XXX-0000"? IMHO, muito código teria sido mais limpo se houvesse treze "meses", mas o mês 0 recebeu um nome falso.
Supercat
1
é uma tomada interessante, mas 0/0/0000 não é uma data válida. como você renderizaria 40/40/0000?
piksel bitworks
12

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

Alex Miller
fonte
2
A nova API Date Time agora faz parte do Java 8
mschenk74
9

Eu diria preguiça. As matrizes começam em 0 (todo mundo sabe disso); os meses do ano são uma matriz, o que me leva a acreditar que algum engenheiro da Sun simplesmente não se incomodou em colocar esse detalhe no código Java.

O Smurf
fonte
9
Não, eu não faria. É mais importante otimizar a eficiência dos clientes do que os programadores. Como esse cliente está passando um tempo perguntando aqui, eles falharam nisso.
TheSmurf
2
É totalmente independente da eficiência - não é como se os meses fossem armazenados em uma matriz e você precisaria de 13 para representar 12 meses. É uma questão de não tornar a API tão amigável quanto deveria ter sido em primeiro lugar. Josh Bloch fala sobre data e calendário em "Java eficaz". Poucas APIs são perfeitas, e as APIs de data / hora em Java têm o papel infeliz de serem as que foram enganadas. Isso é vida, mas não vamos fingir que tem algo a ver com eficiência.
Quinn Taylor
1
Por que não contar dias de 0 a 30, então? É apenas inconsistente e desleixado.
Juangui Jordán
9

Provavelmente porque o "struct tm" de C faz o mesmo.

Paul Tomblin
fonte
8

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

Dinah
fonte
1
Esta é outra daquelas expressões idiomáticas / hábitos que vão caminho de volta para assembler ou linguagem de máquina onde tudo é feito em termos de deslocamentos, não indexa. Notação de matriz tornou-se um atalho para acessar blocos contíguos, começando no índice 0.
Ken Gentil
4

Pessoalmente, tomei a estranheza da API do calendário Java como uma indicação de que precisava me divorciar da mentalidade gregoriana e tentar programar de maneira mais agnóstica a esse respeito. Especificamente, aprendi mais uma vez a evitar constantes codificadas por coisas como meses.

Qual das alternativas a seguir tem mais probabilidade 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 o 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.)

Paul Brinkley
fonte
1
Mas qual esquema tem mais chances de causar dor de cabeça quando você não tem uma constante no seu código - você tem um valor que é o resultado de uma chamada de serviço da Web ou qualquer outra coisa.
Jon Skeet
Essa chamada de serviço da web também deve estar usando essa constante, é claro. :-) O mesmo vale para qualquer chamador externo. Depois que estabelecemos que existem vários padrões, a necessidade de impor um se torna evidente. (Eu espero que eu entendi o seu comentário ...)
Paul Brinkley
3
Sim, devemos aplicar o padrão que quase todo o resto do mundo usa ao expressar meses - o padrão baseado em 1.
Jon Skeet
A palavra-chave aqui sendo "quase". Obviamente, Jan = 1 etc. parece natural em um sistema de datas com uso extremamente amplo, mas por que nos permitir abrir uma exceção para evitar constantes codificadas, mesmo neste caso?
Paul Brinkley
3
Porque isso facilita a vida. Apenas faz. Eu nunca encontrei um problema pontual com um sistema mensal baseado em 1. Eu já vi muitos desses erros com a API Java. Ignorar o que todo mundo faz simplesmente não faz sentido.
Jon Skeet
4

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

Pegadinhas

java.util.GregorianCalendartem muito menos bugs e truques do que a old java.util.Dateclasse, mas ainda não é um piquenique.

Se houvesse programadores quando o horário de verão foi proposto, eles o vetariam como insano e intratável. Com o horário de verão, há uma ambiguidade fundamental. No outono, quando você atrasa o relógio uma hora às 2 da manhã, existem dois instantes diferentes no horário, ambos chamados 1:30 da manhã, 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 saber GregorianCalendarqual você pretendia. Você deve recorrer à hora local com o fuso horário UTC para evitar a ambiguidade. 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 erros ainda não estão fora das classes 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 07:00 em 01/01/2001 para o MST.

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

GregorianCalendartem o GregorianCalendar.get(Calendar.ZONE_OFFSET)horário bruto e o horário de verão GregorianCalendar. get( Calendar. DST_OFFSET), mas não há como usar o deslocamento de fuso horário real. Você deve obter esses dois separadamente e adicioná-los.

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

DateFormate GregorianCalendarnão faça a malha corretamente. Você deve especificar o calendário duas vezes, uma vez indiretamente como uma data.

Se o usuário não tiver configurado seu fuso horário corretamente, o padrão será o PST ou o GMT.

No Calendário Gregoriano, os meses são numerados a partir de janeiro = 0, em vez de 1, como todo mundo no planeta. No entanto, os dias começam em 1, assim como os dias da semana com domingo = 1, segunda-feira = 2, ... sábado = 7. No entanto, DateFormat. a análise se comporta da maneira tradicional com janeiro = 1.

Edwin Dalorzo
fonte
4

java.util.Month

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

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

(import java.time.*;)
Realidade Digital
fonte
3

tl; dr

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

2

Detalhes

A resposta de Jon Skeet está correta.

Agora, temos um substituto moderno para as antigas classes problemáticas de data e hora herdadas: as classes java.time .

java.time.Month

Entre essas classes está o enum . Uma enum carrega um ou mais objetos predefinidos, objetos que são instanciados automaticamente quando a classe é carregada. Em temos uma dúzia de tais objetos, cada um recebeu um nome: , , , e assim por diante. Cada um desses é uma constante de classe. Você pode usar e passar esses objetos em qualquer lugar do seu código. Exemplo:Month MonthJANUARYFEBRUARYMARCHstatic final publicsomeMethod( Month.AUGUST )

Felizmente, eles têm numeração sã, 1-12, onde 1 é janeiro e 12 é dezembro.

Obtenha um Monthobjeto para um número de mês específico (1 a 12).

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

Indo na outra direção, peça ao Monthobjeto o número do mês.

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

Muitos outros métodos úteis nessa classe, como saber o número de dias em cada mês . A classe pode até gerar um nome localizado do mês.

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

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

février

Além disso, você deve passar objetos dessa enum ao redor da sua base de código, em vez de meros números inteiros . Fazer isso fornece segurança de tipo, garante um intervalo válido de valores e torna seu código mais auto-documentado. Consulte o Tutorial do Oracle se não estiver familiarizado com o recurso enum surpreendentemente poderoso em Java.

Você também pode achar úteis as classes Yeare YearMonth.


Sobre java.time

A estrutura java.time é integrada ao Java 8 e posterior. Essas classes suplantar os velhos problemáticos legados aulas de data e hora, como java.util.Date, .Calendar, e java.text.SimpleDateFormat.

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

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

Onde obter as classes java.time?

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

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

Basil Bourque
fonte
0

Não é exatamente definido como zero em si, é definido como Calendar.January. É o problema de usar ints como constantes em vez de enumerações. Calendar.January == 0.

Pål GD
fonte
1
Os valores são um e o mesmo. As APIs também podem retornar 0, é idêntico à constante. Calendar.JANUARY poderia ter sido definido como 1 - esse é o ponto. Uma enumeração seria uma solução agradável, mas as enumerações verdadeiras não foram adicionadas à linguagem até o Java 5, e o Date existe desde o início. É lamentável, mas você realmente não pode "consertar" uma API tão fundamental assim que o código de terceiros a usa. O melhor que pode ser feito é fornecer uma nova API e descontinuar a antiga para incentivar as pessoas a seguir em frente. Obrigado, Java 7 ...
Quinn Taylor
0

Como escrever idiomas é 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 realidade, não Java), consulte o vídeo do YouTube "O problema com o tempo e os 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.

Tihamer
fonte
-1

Além da resposta de preguiça de DannySmurf, acrescentarei que é para encorajá-lo a usar constantes, como Calendar.JANUARY.

Powerlord
fonte
5
Tudo está muito bem quando você está escrevendo explicitamente o código para um determinado mês, mas é uma dor quando você recebe o mês no formato "normal" de uma fonte diferente.
Jon Skeet
1
Também é uma dor quando você está tentando imprimir o valor desse mês de alguma maneira específica - você está sempre adicionando 1 a ele.
Brian Warshaw
-2

Porque tudo começa com 0. Esse é um fato básico da programação em Java. Se algo se desviasse disso, isso levaria a toda uma confusão. Não vamos discutir a formação deles e codificá-los.

Syrrus
fonte
2
Não, a maioria das coisas no mundo real começa com 1. As compensações começam com 0 e o mês do ano não é compensado, é um dos doze, assim como o dia do mês é um dos 31 ou 30 ou 29 ou 28. Tratar o mês como um deslocamento é apenas caprichoso, especialmente se ao mesmo tempo não tratarmos o dia do mês da mesma maneira. Qual seria o motivo dessa diferença?
SantiBailors
no mundo real, comece com 1, no mundo Java, comece com 0. MAS ... acho que é porque: - para calcular o dia da semana, não é possível compensar algumas cálculos sem adicionar mais algumas etapas a it ... - além disso, mostra os dias completos do mês, se necessário (sem confusão ou necessidade de verificar fev.) - Durante o mês obriga a saída em um formato de data que deve ser usado de qualquer maneira. Além disso, como o número de meses em um ano é regular e os dias em um mês não são, faz sentido se você precisar declarar matrizes e usar um deslocamento para se ajustar melhor à matriz.
Syrrus