Em java.util.Calendar
janeiro, é 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 ...
Em java.util.Calendar
janeiro, é 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 ...
Respostas:
É 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.Calendar
e 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:
Date
e Calendar
como coisas diferentes, mas a separação dos valores "local" x "zoneado" está ausente, assim como data / hora vs data vs horaDate.toString()
implementação que sempre usa o fuso horário local do sistema (que confunde muitos usuários do Stack Overflow antes de agora)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.
((11 - 1 + 1) % 12) + 1 = 12
é apenas (11 % 12) + 1
por meses 1..12, você só precisa adicionar o 1 depois de fazer o módulo. Nenhuma mágica é necessária.
Os idiomas baseados em C copiam C até certo ponto. A tm
estrutura (definida em time.h
) possui um campo inteiro tm_mon
com 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_mon
o índice.
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.h
onde 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+1
instruçõ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.
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.
Provavelmente porque o "struct tm" de C faz o mesmo.
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.
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.)
Para mim, ninguém explica melhor do que mindpro.com :
Pegadinhas
java.util.GregorianCalendar
tem muito menos bugs e truques do que aold java.util.Date
classe, 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
GregorianCalendar
qual 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ê usaGregorianCalendar. get(Calendar.MONTH));
GregorianCalendar
tem oGregorianCalendar.get(Calendar.ZONE_OFFSET)
horário bruto e o horário de verãoGregorianCalendar. 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.
DateFormat
eGregorianCalendar
nã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.
java.util.Month
Java fornece uma outra maneira de usar índices baseados em 1 por meses. Use o java.time.Month
enum. Um objeto é predefinido para cada um dos doze meses. Eles têm números atribuídos a cada 1-12 para janeiro-dezembro; ligue getValue
para o número.
Faça uso de Month.JULY
(Dá-lhe 7) em vez de Calendar.JULY
(Dá-lhe 6).
(import java.time.*;)
Month.FEBRUARY.getValue() // February → 2.
2
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
Month
JANUARY
FEBRUARY
MARCH
static final public
someMethod( Month.AUGUST )
Felizmente, eles têm numeração sã, 1-12, onde 1 é janeiro e 12 é dezembro.
Obtenha um Month
objeto 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 Month
objeto 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 Year
e YearMonth
.
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?
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 .
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.
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.
Além da resposta de preguiça de DannySmurf, acrescentarei que é para encorajá-lo a usar constantes, como Calendar.JANUARY
.
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.