Perché il mese 0 di gennaio nel calendario Java?


300

In java.util.Calendar, gennaio è definito come il mese 0, non il mese 1. C'è qualche motivo specifico per farlo?

Ho visto molte persone confondersi a riguardo ...


4
Non è quel tipo di dettaglio di implementazione, poiché esistono le costanti GENNAIO, FEBBRAIO ecc.? Le classi di date precedono il corretto supporto di java enum.
Gnud,

6
Ancora più fastidioso: perché c'è un undecember?
matt b

40
@gnud: No, non è un dettaglio di implementazione. Lo rende un problema quando ti è stato dato un numero intero in base "naturale" (cioè Jan = 1) e devi usarlo con l'API del calendario.
Jon Skeet,

1
@matt b: è per calendari non gregoriani (calendari lunari, ecc.) che hanno tredici mesi. Ecco perché è meglio non pensare in termini di numeri, ma lasciare che Calendar faccia la sua localizzazione.
Erickson,

7
L'argomento di 13 mesi non ha senso. In tal caso, perché non avere il mese in più 0 o 13?
Quinn Taylor,

Risposte:


323

Fa solo parte dell'orrendo casino che è l'API data / ora di Java. Elencare ciò che non va richiederebbe molto tempo (e sono sicuro di non conoscere la metà dei problemi). Certo, lavorare con date e orari è complicato, ma comunque comunque.

Fatevi un favore e usate invece Joda Time , o eventualmente JSR-310 .

EDIT: Per quanto riguarda i motivi per cui - come notato in altre risposte, potrebbe essere dovuto alle vecchie API C, o semplicemente alla sensazione generale di iniziare tutto da 0 ... tranne che i giorni iniziano con 1, ovviamente. Dubito che qualcuno al di fuori del team di implementazione originale possa davvero affermare le ragioni - ma ancora una volta, esorto i lettori a non preoccuparsi così tanto del perché sono state prese decisioni sbagliate, al fine di esaminare l'intera gamma di cattiveria java.util.Calendare trovare qualcosa di meglio.

Un punto, che è a favore dell'utilizzo di 0 basati su indici è che rende le cose come "array di nomi" più facile:

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

Naturalmente, questo non riesce non appena si ottiene un calendario con 13 mesi ... ma almeno la dimensione specificata è il numero di mesi previsti.

Questa non è una buona ragione, ma è una ragione ...

MODIFICA: Come commento una sorta di richiesta di alcune idee su ciò che penso sia sbagliato in Data / Calendario:

  • Basi sorprendenti (1900 come base dell'anno in Date, certamente per costruttori deprecati; 0 come base mensile in entrambi)
  • Mutabilità: l'utilizzo di tipi immutabili rende molto più semplice lavorare con valori realmente efficaci
  • Un insieme insufficiente di tipi: è bello avere Datee Calendarcome cose diverse, ma manca la separazione dei valori "locali" vs "suddivisi in zone", così come data / ora vs data vs ora
  • Un'API che porta a codice brutto con costanti magiche, invece di metodi chiaramente denominati
  • Un'API di cui è molto difficile ragionare - tutto il business su quando le cose vengono ricalcolate, ecc
  • L'uso di costruttori senza parametri su "ora" predefinito, che porta a codice difficile da testare
  • L' Date.toString()implementazione che utilizza sempre il fuso orario locale del sistema (che ha confuso molti utenti Stack Overflow prima d'ora)

14
... e che succede a deprecare tutti gli utili metodi Date semplici? Ora devo usare quell'orribile oggetto Calendar in modi complicati per fare cose che prima erano semplici.
Brian Knoblauch,

3
@Brian: sento il tuo dolore. Ancora una volta, Joda Time è più semplice :) (Il fattore di immutabilità rende anche le cose molto più piacevoli con cui lavorare.)
Jon Skeet,

8
Non hai risposto alla domanda.
Zeemee,

2
@ user443854: ho elencato alcuni punti in una modifica - vedi se questo aiuta.
Jon Skeet,

2
Se si utilizza Java 8, è possibile abbandonare la classe Calendar e passare alla nuova ed elegante API DateTime . La nuova API include anche un DateTimeFormatter immutabile / thread-safe che è un grande miglioramento rispetto al problematico e costoso SimpleDateFormat.
ccpizza,

43

Perché fare matematica con i mesi è molto più semplice.

1 mese dopo dicembre è gennaio, ma per capirlo normalmente dovresti prendere il numero del mese e fare matematica

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

Lo so! Posso risolverlo rapidamente usando un modulo di 12.

(12 + 1) % 12 = 1

Funziona benissimo per 11 mesi fino a novembre ...

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

Puoi fare di nuovo tutto questo sottraendo 1 prima di aggiungere il mese, quindi esegui il modulo e infine aggiungi di nuovo 1 ... alias aggira un problema di fondo.

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

Ora pensiamo al problema con mesi 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

Tutti i mesi funzionano allo stesso modo e non è necessario risolvere il problema.


5
Questo è soddisfacente. Almeno c'è un valore per questa follia!
moljac024,

"Un sacco di numeri magici" - nah, è solo uno che appare due volte.
user123444555621

Ritornare indietro di un mese è comunque un po 'orribile, grazie allo sfortunato uso di C di un operatore "resto" piuttosto che di "modulo". Inoltre, non sono sicuro di quanto spesso si debba eseguire un bump di un mese senza regolare l'anno, e avere mesi che vanno 1-12 non crea alcun problema con `while (mese> 12) {month- = 12; anno ++;}
supercat

2
Perché funzioni sane come DateTime.AddMonths sono troppo difficili per essere implementate correttamente in una libreria, dobbiamo fare i calcoli che ti hai descritto ... Mmmmmkay
nsimeonov

8
Non capisco questi voti - ((11 - 1 + 1) % 12) + 1 = 12è solo (11 % 12) + 1per mesi 1..12 devi solo aggiungere 1 dopo aver fatto il modulo. Nessuna magia richiesta.
mfitzp,

35

Le lingue basate su C copiano C in una certa misura. La tmstruttura (definita in time.h) ha un campo intero tm_moncon l'intervallo (commentato) di 0-11.

I linguaggi basati su C iniziano le matrici all'indice 0. Quindi questo è stato utile per emettere una stringa in una matrice di nomi di mese, con tm_moncome indice.


22

Ci sono state molte risposte a questo, ma darò comunque la mia opinione sull'argomento. Il motivo dietro questo strano comportamento, come affermato in precedenza, deriva dal POSIX C in time.hcui i mesi sono stati memorizzati in un int con l'intervallo 0-11. Per spiegare il perché, guardalo in questo modo; anni e giorni sono considerati numeri nella lingua parlata, ma i mesi hanno i loro nomi. Quindi, poiché gennaio è il primo mese, verrà archiviato come offset 0, il primo elemento dell'array. monthname[JANUARY]sarebbe "January". Il primo mese dell'anno è l'elemento array del primo mese.

D'altra parte, i numeri dei giorni, dato che non hanno nomi, memorizzarli in un int come 0-30 sarebbe confuso, aggiungere molte day+1istruzioni per l'output e, naturalmente, essere inclini a molti bug.

Detto questo, l'incoerenza è fonte di confusione, specialmente in javascript (che ha anche ereditato questa "caratteristica"), un linguaggio di scripting in cui questo dovrebbe essere sottratto lontano dalla lingua.

TL; DR : Perché i mesi hanno nomi e i giorni del mese no.


1
"mesi hanno nomi e giorni no." Hai mai sentito parlare di "venerdì"? ;) OK, immagino che volevi dire "... i giorni del mese no" - forse sarebbe meglio modificare la tua (altrimenti buona) risposta. :-)
Andrew Thompson,

0/0/0000 è reso meglio come "00-Jan-0000" o come "00-XXX-0000"? IMHO, un sacco di codice sarebbe stato più pulito se ci fossero tredici "mesi" ma al mese 0 è stato assegnato un nome fittizio.
supercat

1
questo è un risultato interessante, ma 0/0/0000 non è una data valida. come renderesti 40/40/0000?
Piksel Bitworks,

12

In Java 8, esiste una nuova API JSR 310 Data / Ora che è più sana. Il lead delle specifiche è lo stesso dell'autore principale di JodaTime e condividono molti concetti e modelli simili.


2
La nuova API Date Time fa ora parte di Java 8
mschenk74,

9

Direi pigrizia. Le matrici iniziano da 0 (lo sanno tutti); i mesi dell'anno sono un array, il che mi porta a credere che alcuni ingegneri della Sun non si siano preoccupati di inserire questo piccolo valore nel codice Java.


9
No, non lo farei. È più importante ottimizzare l'efficienza dei propri clienti rispetto ai propri programmatori. Dal momento che questo cliente trascorre del tempo qui a chiedere, non ci sono riusciti.
TheSmurf

2
Non è assolutamente correlato all'efficienza: non è come se i mesi fossero archiviati in un array e ne avresti bisogno 13 per rappresentare 12 mesi. Si tratta di non rendere l'API così intuitiva come avrebbe dovuto essere in primo luogo. Josh Bloch straccia la data e il calendario in "Efficace Java". Pochissime API sono perfette e le API data / ora in Java hanno lo sfortunato ruolo di essere quelle che sono state stupide. Questa è la vita, ma non facciamo finta che non abbia nulla a che fare con l'efficienza.
Quinn Taylor,

1
Perché allora non contare i giorni da 0 a 30? È solo incoerente e sciatto.
Juangui Jordán,


8

Perché i programmatori sono ossessionati dagli indici basati su 0. OK, è un po 'più complicato di così: ha più senso quando lavori con la logica di livello inferiore per utilizzare l'indicizzazione basata su 0. Ma nel complesso, continuerò comunque con la mia prima frase.


1
Questo è un altro di quei modi di dire / abitudini che risalgono al linguaggio assembler o al linguaggio macchina in cui tutto viene fatto in termini di offset, non di indici. La notazione di array è diventata una scorciatoia per l'accesso ai blocchi contigui, a partire dall'offset 0.
Ken Gentle,

4

Personalmente, ho preso la stranezza dell'API del calendario Java come indicazione che avevo bisogno di separarmi dalla mentalità centrata sul Gregoriano e provare a programmare in modo più agnostico a tale riguardo. In particolare, ho imparato ancora una volta a evitare costanti codificate per cose come mesi.

Quale delle seguenti è più probabile che sia corretta?

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

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

Questo illustra una cosa che mi infastidisce un po 'di Joda Time: può incoraggiare i programmatori a pensare in termini di costanti codificate. (Solo un po ', però. Non è come se Joda stesse forzando i programmatori a programmare male.)


1
Ma quale schema è più probabile che ti dia mal di testa quando non hai una costante nel tuo codice - hai un valore che è il risultato di una chiamata al servizio web o altro.
Jon Skeet,

Anche quella chiamata al servizio web dovrebbe usare quella costante, ovviamente. :-) Lo stesso vale per qualsiasi chiamante esterno. Una volta stabilito che esistono più standard, diventa evidente la necessità di farne rispettare uno. (Spero di aver capito il tuo commento ...)
Paul Brinkley il

3
Sì, dovremmo applicare lo standard che quasi ogni altra cosa al mondo usa quando si esprimono i mesi: lo standard basato su 1.
Jon Skeet,

La parola chiave qui è "quasi". Ovviamente, Jan = 1, ecc. Sembra naturale in un sistema di data con un uso estremamente ampio, ma perché permetterci di fare un'eccezione per evitare costanti codificate, anche in questo caso?
Paul Brinkley,

3
Perché semplifica la vita. Lo fa e basta. Non ho mai riscontrato un problema unico con un sistema basato su 1 mese. Ho visto un sacco di bug con l'API Java. Ignorare ciò che fanno tutti gli altri nel mondo non ha senso.
Jon Skeet,

4

Per me nessuno lo spiega meglio di mindpro.com :

Trabocchetti

java.util.GregorianCalendarha molti meno bug e trucchi rispetto alla old java.util.Dateclasse, ma non è ancora un picnic.

Se ci fossero stati programmatori quando fu proposta la prima ora legale, l'avrebbero posto come pazzo e intrattabile. Con l'ora legale c'è un'ambiguità fondamentale. In autunno quando torni indietro di un'ora alle 2 del mattino, ci sono due istanti diversi nel tempo entrambi chiamati 1:30 AM ora locale. Puoi distinguerli solo se registri se intendi l'ora legale o l'ora solare con la lettura.

Sfortunatamente, non c'è modo di dire GregorianCalendarquale intendevi. È necessario ricorrere a comunicare l'ora locale con il fittizio TimeCone UTC per evitare l'ambiguità. I programmatori di solito chiudono gli occhi su questo problema e sperano solo che nessuno faccia nulla durante questa ora.

Bug del millennio. I bug non sono ancora fuori dalle classi di Calendar. Anche in JDK (Java Development Kit) 1.3 esiste un bug del 2001. Considera il seguente codice:

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 */

Il bug scompare alle 07:00 del 2001/01/01 per MST.

GregorianCalendarè controllato da un gigante di pile di costanti magiche int non tipizzate. Questa tecnica distrugge totalmente ogni speranza di controllo degli errori in fase di compilazione. Ad esempio per ottenere il mese che usi GregorianCalendar. get(Calendar.MONTH));

GregorianCalendarha l' GregorianCalendar.get(Calendar.ZONE_OFFSET)ora legale e l'ora legale GregorianCalendar. get( Calendar. DST_OFFSET), ma non è possibile ottenere l'offset del fuso orario effettivo. È necessario ottenere questi due separatamente e sommarli.

GregorianCalendar.set( year, month, day, hour, minute) non imposta i secondi su 0.

DateFormate GregorianCalendarnon si mesh correttamente. È necessario specificare il Calendario due volte, una volta indirettamente come Data.

Se l'utente non ha configurato correttamente il proprio fuso orario, verrà impostato automaticamente su PST o GMT.

In GregorianCalendar, i mesi sono numerati a partire da gennaio = 0, anziché 1 come fanno tutti gli altri sul pianeta. Eppure i giorni iniziano a 1 come i giorni della settimana con domenica = 1, lunedì = 2, ... sabato = 7. Tuttavia DateFormat. L'analisi si comporta in modo tradizionale con gennaio = 1.


4

java.util.Month

Java offre un altro modo per utilizzare indici basati su 1 per mesi. Usa l' java.time.Monthenum. Un oggetto è predefinito per ciascuno dei dodici mesi. Hanno numeri assegnati a ciascuno 1-12 per gennaio-dicembre; chiamare getValueil numero.

Usa Month.JULY(Ti dà 7) invece di Calendar.JULY(Ti dà 6).

(import java.time.*;)

3

tl; dr

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

2

Dettagli

La risposta di Jon Skeet è corretta.

Ora abbiamo un moderno rimpiazzo per quelle fastidiose vecchie classi di data-ora: le classi java.time .

java.time.Month

Tra quelle classi c'è l' enum . Un enum contiene uno o più oggetti predefiniti, oggetti che vengono istanziati automaticamente al caricamento della classe. Su abbiamo una dozzina di tali oggetti, ogni dato un nome: , , , e così via. Ognuna di queste è una costante di classe. Puoi usare e passare questi oggetti ovunque nel tuo codice. Esempio:Month MonthJANUARYFEBRUARYMARCHstatic final publicsomeMethod( Month.AUGUST )

Fortunatamente, hanno una numerazione sana, 1-12 dove 1 è gennaio e 12 è dicembre.

Ottieni un Monthoggetto per un determinato numero di mese (1-12).

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

Andando nella direzione opposta, chiedi a un Monthoggetto il numero del mese.

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

Molti altri metodi utili su questa classe, come conoscere il numero di giorni in ogni mese . La classe può persino generare un nome localizzato del mese.

È possibile ottenere il nome localizzato del mese, in varie lunghezze o abbreviazioni.

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

février

Inoltre, dovresti passare oggetti di questo enum intorno alla tua base di codice piuttosto che semplici numeri interi . In questo modo si garantisce la sicurezza dei tipi, si garantisce un intervallo di valori valido e si rende il codice più autocompattante. Vedi Oracle Tutorial se non hai familiarità con la funzione enum sorprendentemente potente di Java.

Potresti anche trovare utili le classi Yeare YearMonth.


Informazioni su java.time

Il framework java.time è integrato in Java 8 e versioni successive. Queste classi soppiantare la vecchia fastidiosi legacy classi data-time come java.util.Date, .Calendar, e java.text.SimpleDateFormat.

Il progetto Joda-Time , ora in modalità manutenzione , consiglia la migrazione a java.time.

Per saperne di più, consulta il tutorial Oracle . E cerca Stack Overflow per molti esempi e spiegazioni. La specifica è JSR 310 .

Dove ottenere le classi java.time?

Il progetto ThreeTen-Extra estende java.time con classi aggiuntive. Questo progetto è un banco di prova per possibili aggiunte future a java.time. Si possono trovare alcune classi utili, come per esempio Interval, YearWeek, YearQuartere altro .


0

Non è esattamente definito come zero di per sé, è definito come Calendar.January. È il problema dell'utilizzo di ints come costanti invece di enum. Calendar.January == 0.


1
I valori sono gli stessi. Le API possono anche restituire 0, è identico alla costante. Calendar.JANUARY avrebbe potuto essere definito come 1 - questo è il punto. Un enum sarebbe una buona soluzione, ma i veri enum non sono stati aggiunti alla lingua fino a Java 5 e Date è in circolazione dall'inizio. È un peccato, ma non puoi davvero "riparare" un'API così fondamentale una volta che il codice di terze parti lo utilizza. Il meglio che si può fare è fornire una nuova API e deprecare quella vecchia per incoraggiare le persone ad andare avanti. Grazie, Java 7 ...
Quinn Taylor,

0

Perché la scrittura della lingua è più difficile di quanto sembri, e gestire il tempo in particolare è molto più difficile di quanto la maggior parte della gente pensi. Per una piccola parte del problema (in realtà, non Java), vedi il video di YouTube "The Problem with Time & Timezones - Computerphile" su https://www.youtube.com/watch?v=-5wpm-gesOY . Non essere sorpreso se la tua testa cade dal ridere in confusione.


-1

Oltre alla risposta di pigrizia di DannySmurf, aggiungerò che è per incoraggiarti a usare le costanti, come Calendar.JANUARY.


5
Va benissimo quando scrivi esplicitamente il codice per un determinato mese, ma è un dolore quando hai il mese in forma "normale" da una fonte diversa.
Jon Skeet,

1
È anche una seccatura quando stai cercando di stampare quel valore del mese in un modo particolare - ne aggiungi sempre 1.
Brian Warshaw,

-2

Perché tutto inizia con 0. Questo è un fatto fondamentale della programmazione in Java. Se una cosa dovesse deviare da quella, ciò porterebbe a tutta una confusione. Non discutiamo della loro formazione e codifichiamo con loro.


2
No, la maggior parte delle cose nel mondo reale inizia con 1. Gli offset iniziano con 0 e il mese dell'anno non è un offset, è uno dei dodici, proprio come il giorno del mese è uno dei 31 o 30 o 29 o 28. Trattare il mese come un offset è solo capriccioso, specialmente se allo stesso tempo non trattiamo il giorno del mese allo stesso modo. Quale sarebbe la ragione di questa differenza?
SantiBailors

nel mondo reale inizia con 1, nel mondo Java inizia con 0. MA ... Penso che sia perché: - per calcolare il giorno della settimana non può essere compensato per un paio di calcoli senza aggiungere un altro paio di passaggi a esso ... - inoltre mostra i giorni completi del mese, se necessario (senza confusione o necessità di controllare febbraio) - Per il mese ti costringe a produrre in un formato data che dovrebbe essere usato in entrambi i modi. Inoltre, poiché il numero di mesi in un anno è regolare e i giorni in un mese non hanno senso se è necessario dichiarare gli array e utilizzare un offset per adattarsi meglio all'array.
Syrrus
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.