Warum ist Januarmonat 0 im Java-Kalender?

In java.util.Calendar wird Januar als Monat 0 und nicht als Monat 1 definiert. Gibt es einen bestimmten Grund dafür?

Ich habe viele Leute gesehen, die darüber verwirrt wurden …

Es ist nur ein Teil des schrecklichen Chaos, das die Java-API ist. Aufzulisten, was falsch daran ist, würde sehr lange dauern (und ich bin mir sicher, dass ich die Hälfte der Probleme nicht kenne). Zugegeben, arbeiten mit Daten und Zeiten ist schwierig, aber aaogh sowieso.

Tun Sie sich selbst einen Gefallen und verwenden Sie stattdessen Joda Time oder möglicherweise JSR-310 .

EDIT: Wie für die Gründe warum – wie in anderen Antworten erwähnt, könnte es gut sein, aufgrund der alten C-APIs, oder nur ein allgemeines Gefühl, alles von 0 beginnen … außer dass die Tage mit 1 beginnen, natürlich. Ich bezweifle, dass irgendjemand außerhalb des ursprünglichen Implementierungsteams wirklich Gründe angeben kann – aber wiederum möchte ich die Leser auffordern, sich nicht so viele Gedanken darüber zu machen, warum schlechte Entscheidungen getroffen wurden, um die ganze Bandbreite der Gemeinheiten in java.util.Calendar und finde etwas besseres.

Ein Punkt, der für die Verwendung von 0-basierten Indizes ist, ist, dass es Dinge wie “Arrays von Namen” einfacher macht:

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

Dies schlägt natürlich fehl, sobald Sie einen Kalender mit 13 Monaten erhalten … aber mindestens die angegebene Größe ist die Anzahl der Monate, die Sie erwarten.

Das ist kein guter Grund, aber es ist ein Grund …

BEARBEITEN: Als eine Art Kommentar fragt einige Ideen über was ich denke falsch mit Datum / Kalender:

  • Überraschende Grundlagen (1900 als Jahresbasis in Date, zugegebenermaßen für veraltete Konstruktoren; 0 als Monatsbasis in beiden)
  • Mutabilität – die Verwendung unveränderlicher Typen macht es viel einfacher, mit wirklich effektiven Werten zu arbeiten
  • Eine unzureichende Menge von Typen: Es ist schön, Date und Calendar als verschiedene Dinge zu haben, aber die Trennung von “lokalen” und “in Zonen” -Werten fehlt, ebenso wie Datum / Zeit und Datum / Zeit
  • Eine API, die zu hässlichem Code mit magischen Konstanten anstelle von eindeutig benannten Methoden führt
  • Eine API, die sehr schwer zu verstehen ist – das ganze Geschäft, wenn Dinge neu berechnet werden usw
  • Die Verwendung von parameterlosen Konstruktoren wird standardmäßig auf “jetzt” gesetzt, was zu schwer zu testendem Code führt
  • Die Date.toString() Implementierung, die immer die lokale Zeitzone des Systems verwendet (was viele Stack Overflow-Benutzer bisher verwirrt hat)

C-basierte Sprachen kopieren C zu einem gewissen Grad. Die tm Struktur (definiert in time.h ) hat ein ganzzahliges Feld tm_mon mit dem (kommentierten) Bereich von 0-11.

C-basierte Sprachen starten Arrays mit dem Index 0. Dies war praktisch, um eine Zeichenfolge in einem Array von Monatsnamen mit tm_mon als Index tm_mon .

Denn Mathe mit Monaten ist viel einfacher.

1 Monat nach Dezember ist Januar, aber um das herauszufinden, müssten Sie normalerweise die Monatsnummer nehmen und rechnen

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

Ich kenne! Ich kann das schnell beheben, indem ich einen Modul von 12 verwende.

 (12 + 1) % 12 = 1 

Das funktioniert gut 11 Monate bis November …

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

Sie können all dies wieder zum Laufen bringen, indem Sie 1 subtrahieren, bevor Sie den Monat hinzufügen, dann tun Sie Ihren Modulus und fügen schließlich wieder 1 hinzu … aka um ein zugrunde liegendes Problem herum zu arbeiten.

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

Lasst uns nun über das Problem mit den Monaten 0 – 11 nachdenken.

 (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 

Alle Monate arbeiten gleich und eine Arbeit ist nicht notwendig.

Darauf gab es viele Antworten, aber ich werde mich trotzdem zu diesem Thema äußern. Der Grund für dieses seltsame Verhalten kommt, wie bereits erwähnt, von der POSIX C time.h wobei die Monate in einem int mit dem Bereich 0-11 gespeichert wurden. Um es zu erklären, schau es dir so an. Jahre und Tage gelten als Zahlen in der gesprochenen Sprache, aber Monate haben ihre eigenen Namen. Da Januar der erste Monat ist, wird er als Offset 0 gespeichert, das erste Array-Element. monthname[JANUARY] wäre "January" . Der erste Monat im Jahr ist das Arrayelement des ersten Monats.

Die Tagesnummern auf der anderen Seite, da sie keine Namen haben, speichern sie in einem Int wie 0-30 wäre verwirrend, fügen Sie eine Menge day+1 statementen für die Ausgabe und natürlich anfällig für viele Bugs.

Vor diesem Hintergrund ist die Inkonsistenz verwirrend, insbesondere in JavaScript (das auch dieses “Feature” geerbt hat), einer Skriptsprache, in der diese weit entfernt von der Sprache entfernt werden sollte.

TL; DR : Weil Monate Namen und Tage des Monats nicht haben.

Ich würde Faulheit sagen. Arrays beginnen bei 0 (jeder weiß das); Die Monate des Jahres sind ein Array, das mich glauben lässt, dass ein Ingenieur bei Sun sich nicht darum gekümmert hat, diesen ein wenig Feingefühl in den Java-Code zu schreiben.

Wahrscheinlich weil Cs “struct tm” das gleiche tut.

In Java 8 gibt es eine neue Date / Time API JSR 310 , die vernünftiger ist. Die Spezifikationsleitung ist die selbe wie der Hauptautor von JodaTime und sie teilen viele ähnliche Konzepte und Muster.

Weil Programmierer mit 0-basierten Indizes besessen sind. OK, es ist ein bisschen komplizierter als das: Es macht mehr Sinn, wenn Sie mit niedriger Logik arbeiten, um 0-basierte Indizierung zu verwenden. Aber im Großen und Ganzen bleibe ich bei meinem ersten Satz.

Persönlich nahm ich die Fremdartigkeit der Java-Kalender-API als ein Indiz, dass ich mich von der gregorianischen Zentralität lösen und versuchen sollte, in dieser Hinsicht eher agnostisch zu programmieren. Insbesondere habe ich noch einmal gelernt, hartkodierte Konstanten für Dinge wie Monate zu vermeiden.

Welche der folgenden Aussagen trifft eher zu?

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

Dies illustriert eine Sache, die mich ein wenig an Joda Time ärgert – sie könnte Programmierer dazu ermutigen, in Begriffen von fest codierten Konstanten zu denken. (Nur ein bisschen. Es ist nicht so, als ob Joda Programmierer dazu zwingt , schlecht zu programmieren.)

java.util.Month

Java bietet Ihnen eine weitere Möglichkeit, 1 basierte Indizes für Monate zu verwenden. Verwenden Sie die java.time.Month . Ein Objekt ist für jeden der zwölf Monate vordefiniert. Sie haben Nummern von 1-12 für Januar-Dezember; Rufen Sie getValue für die Nummer auf.

Nutzen Sie Month.JULY (gibt Ihnen 7) anstelle von Calendar.JULY (gibt Ihnen 6).

 (import java.time.*;) 

Für mich erklärt das niemand besser als mindpro.com :

Gotchas

java.util.GregorianCalendar hat weit weniger Fehler und Fehler als die old java.util.Date class, aber es ist immer noch kein Picknick.

Hätte es Programmierer gegeben, als Daylight Saving Time zum ersten Mal vorgeschlagen wurde, hätten sie es als wahnsinnig und hartnäckig abgelehnt. Bei der Sommerzeit gibt es eine grundlegende Mehrdeutigkeit. Im Herbst, wenn Sie Ihre Uhren eine Stunde um 2 Uhr zurückstellen, gibt es zwei verschiedene Zeitpunkte in der Zeit, die beide 1:30 Uhr Ortszeit genannt werden. Sie können sie nur unterscheiden, wenn Sie aufzeichnen, ob Sie mit dem Lesen Sommerzeit oder Standardzeit haben wollten.

Leider gibt es keine Möglichkeit, GregorianCalendar mitzuteilen, was Sie beabsichtigt haben. Sie müssen die Ortszeit mit der Dummy UTC TimeZone angeben, um die Mehrdeutigkeit zu vermeiden. Programmierer schließen normalerweise ihre Augen für dieses Problem und hoffen nur, dass niemand während dieser Stunde irgendetwas tut.

Millennium-Fehler. Die Fehler sind immer noch nicht aus den Kalenderklassen. Selbst im JDK (Java Development Kit) 1.3 gibt es einen Bug 2001. Betrachten Sie den folgenden Code:

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

Der Fehler verschwindet um 07.00 Uhr am 01.01.2001 für MST.

GregorianCalendar wird von einem riesigen Haufen untypisierter int-magischer Konstanten gesteuert. Diese Technik zerstört jede Hoffnung auf Kompilierungserrors. Um zum Beispiel den Monat zu bekommen, in dem Sie GregorianCalendar. get(Calendar.MONTH)); GregorianCalendar. get(Calendar.MONTH));

GregorianCalendar hat die rohe GregorianCalendar.get(Calendar.ZONE_OFFSET) und die Sommerzeit GregorianCalendar. get( Calendar. DST_OFFSET) GregorianCalendar. get( Calendar. DST_OFFSET) , aber keine Möglichkeit den aktuellen Zeitzonen-Offset zu erhalten. Sie müssen diese beiden separat erhalten und sie zusammenfügen.

GregorianCalendar.set( year, month, day, hour, minute) setzt die Sekunden nicht auf 0.

DateFormat und GregorianCalendar greifen nicht richtig ineinander. Sie müssen den Kalender zweimal angeben, einmal indirekt als Datum.

Wenn der Benutzer seine Zeitzone nicht korrekt konfiguriert hat, wird er entweder auf PST oder GMT eingestellt.

Im Gregorianischen Kalender werden die Monate beginnend mit Januar = 0 gezählt und nicht wie alle anderen auf dem Planeten. Die Tage beginnen jedoch um 1, ebenso wie die Wochentage mit Sonntag = 1, Montag = 2, … Samstag = 7. Aber DateFormat. Parse verhält sich auf traditionelle Weise mit Januar = 1.

tl; dr

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

2

Einzelheiten

Die Antwort von Jon Skeet ist richtig.

Jetzt haben wir einen modernen Ersatz für diese lästigen alten Legacy-Date-Time-classn: die java.time- classn.

java.time.Month

Zu diesen classn gehört der Month enum . Eine Enumeration enthält ein oder mehrere vordefinierte Objekte, Objekte, die beim Laden der class automatisch instanziiert werden. Im Month wir ein Dutzend solcher Objekte, von denen jedes einen Namen erhält: JANUARY , FEBRUARY , MARCH und so weiter. Jede davon ist eine static final public classnkonstante. Sie können diese Objekte überall in Ihrem Code verwenden und übergeben. Beispiel: someMethod( Month.AUGUST )

Glücklicherweise haben sie eine vernünftige Nummerierung, 1-12, wobei 1 Januar und 12 Dezember ist.

Holen Sie sich ein Month für eine bestimmte Monatsnummer (1-12).

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

Gehen Sie in die andere Richtung und fragen Sie ein Month nach seiner Monatsnummer.

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

Viele andere praktische Methoden in dieser class, wie zum Beispiel die Anzahl der Tage in jedem Monat . Die class kann sogar einen lokalisierten Namen des Monats generieren .

Sie können den lokalisierten Namen des Monats in verschiedenen Längen oder Abkürzungen erhalten.

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

février

Außerdem sollten Sie Objekte dieses Enums um Ihre Codebasis übergeben und nicht nur ganze Zahlen . Dadurch wird die Typensicherheit gewährleistet, ein gültiger Wertebereich sichergestellt und der Code selbstdokumentierender. Wenn Sie mit der überraschend leistungsstarken Enum-function in Java nicht vertraut sind, lesen Sie Oracle Tutorial .

Sie können auch die classn Year und YearMonth nützlich finden.


Über java.time

Das Framework java.time ist in Java 8 und höher integriert. Diese classn ersetzen die problematischen alten Legacy- Date-Time-classn wie java.util.Date , .Calendar , & java.text.SimpleDateFormat .

Das Joda-Time- Projekt, jetzt im Wartungsmodus , rät zur Migration auf java.time.

Weitere Informationen finden Sie im Oracle-Lernprogramm . Und suchen Sie nach Stack Overflow für viele Beispiele und Erklärungen. Spezifikation ist JSR 310 .

Wo erhalten Sie die java.time classn?

  • Java SE 8 und SE 9 und später
    • Eingebaut.
    • Teil der Standard-Java-API mit einer gebündelten Implementierung.
    • Java 9 fügt einige kleinere functionen und Korrekturen hinzu.
  • Java SE 6 und SE 7
    • Ein großer Teil der java.time-functionalität wird in ThreeTen-Backport zurück zu Java 6 & 7 portiert .
  • Android
    • Das ThreeTenABP- Projekt passt ThreeTen-Backport (oben erwähnt) speziell für Android an.
    • Siehe Verwendung von ….

Das ThreeTen-Extra- Projekt erweitert java.time um zusätzliche classn. Dieses Projekt ist ein Testfeld für mögliche zukünftige Erweiterungen von java.time. Sie können hier einige nützliche classn wie Interval , YearWeek , YearQuarter und mehr finden .

Zusätzlich zu DannySmurfs Antwort auf Faulheit möchte ich hinzufügen, dass es dich ermutigt, die Konstanten wie Calendar.JANUARY .

Es ist nicht genau definiert als null per se, es ist als Kalender.Januar definiert. Es ist das Problem der Verwendung von Ints als Konstanten anstelle von Enums. Kalender.Januar == 0.

Weil das Schreiben von Sprache schwieriger ist, als es aussieht, und insbesondere die Bearbeitungszeit ist viel schwieriger, als die meisten Leute denken. Einen kleinen Teil des Problems (in Wirklichkeit nicht Java) finden Sie im YouTube-Video “Das Problem mit Zeit und Zeitzonen – Computerphile” unter https://www.youtube.com/watch?v=5wpm-gesOY . Sei nicht überrascht, wenn dein Kopf vom Lachen vor Verwirrung abfällt.

Weil alles mit 0 beginnt. Dies ist eine grundlegende Tatsache der Programmierung in Java. Wenn eine Sache davon abweichen würde, würde das zu einem ganzen Hauch von Verwirrung führen. Lasst uns nicht über die Bildung von ihnen diskutieren und mit ihnen kommunizieren.