In java.util.Calendar
ist Januar als Monat 0 definiert, nicht als Monat 1. Gibt es einen bestimmten Grund dafür?
Ich habe viele Leute gesehen, die darüber verwirrt waren ...
In java.util.Calendar
ist Januar als Monat 0 definiert, nicht als Monat 1. Gibt es einen bestimmten Grund dafür?
Ich habe viele Leute gesehen, die darüber verwirrt waren ...
Antworten:
Es ist nur ein Teil des schrecklichen Chaos, das die Java-API für Datum und Uhrzeit darstellt. Das Auflisten, was daran falsch ist, würde sehr lange dauern (und ich bin sicher, dass ich die Hälfte der Probleme nicht kenne). Zugegeben, mit Datums- und Uhrzeitangaben zu arbeiten ist schwierig, aber trotzdem.
Tun Sie sich selbst einen Gefallen und verwenden Sie stattdessen Joda Time oder möglicherweise JSR-310 .
EDIT: Was die Gründe angeht, warum - wie in anderen Antworten erwähnt - dies möglicherweise auf alte C-APIs zurückzuführen ist oder nur auf das allgemeine Gefühl, alles mit 0 zu beginnen ... außer dass die Tage natürlich mit 1 beginnen. Ich bezweifle, dass jemand außerhalb des ursprünglichen Implementierungsteams wirklich Gründe angeben kann - aber ich möchte die Leser erneut auffordern, sich nicht so viele Gedanken darüber zu machen, warum schlechte Entscheidungen getroffen wurden, sondern die gesamte Bandbreite der Gemeinheit zu betrachten java.util.Calendar
und etwas Besseres zu finden.
Ein Punkt, der ist für den Einsatz von 0 basierte Indizes ist , dass es Dinge wie „Arrays von Namen“ erleichtert:
// 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 entspricht der Anzahl der Monate, die Sie erwarten.
Das ist kein guter Grund, aber es ist ein Grund ...
BEARBEITEN: Als Kommentar werden einige Ideen dazu angefordert, was meiner Meinung nach mit Datum / Kalender falsch ist:
Date
und Calendar
als verschiedene Dinge, aber die Trennung von "lokalen" vs "zonierten" Werten fehlt, ebenso wie Datum / Uhrzeit vs Datum vs ZeitDate.toString()
Implementierung, die immer die lokale Zeitzone des Systems verwendet (das hat viele Stack Overflow-Benutzer bisher verwirrt)Weil es viel einfacher ist, mit Monaten zu rechnen.
1 Monat nach Dezember ist Januar, aber um dies normal herauszufinden, müssten Sie die Monatszahl nehmen und rechnen
12 + 1 = 13 // What month is 13?
Ich weiß! Ich kann dies schnell beheben, indem ich einen Modul von 12 verwende.
(12 + 1) % 12 = 1
Dies funktioniert gut für 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 Ihren Modul ausführen und schließlich wieder 1 hinzufügen ... auch bekannt als Umgehung eines zugrunde liegenden Problems.
((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers!
Lassen Sie 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 Umgehung ist nicht erforderlich.
((11 - 1 + 1) % 12) + 1 = 12
ist nur (11 % 12) + 1
für Monate 1..12, Sie müssen nur die 1 hinzufügen, nachdem Sie das Modulo gemacht haben. Keine Magie erforderlich.
C-basierte Sprachen kopieren C bis 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 bei Index 0. Dies war praktisch, um eine Zeichenfolge in einem Array von Monatsnamen mit tm_mon
als Index auszugeben.
Es gab viele Antworten darauf, aber ich werde trotzdem meine Meinung zu diesem Thema äußern. Der Grund für dieses seltsame Verhalten liegt, wie bereits erwähnt, bei POSIX C, time.h
wo die Monate in einem Int mit dem Bereich 0-11 gespeichert wurden. Um zu erklären, warum, sehen Sie es so an; Jahre und Tage werden in gesprochener Sprache als Zahlen betrachtet, aber Monate haben ihre eigenen Namen. Da Januar der erste Monat ist, wird er als Offset 0, das erste Array-Element, gespeichert. monthname[JANUARY]
wäre "January"
. Der erste Monat im Jahr ist das Array-Element des ersten Monats.
Die Tageszahlen hingegen sind verwirrend, da sie keine Namen haben und sie in einem int day+1
von 0 bis 30 speichern. Sie fügen viele Anweisungen für die Ausgabe hinzu und sind natürlich anfällig für viele Fehler.
Abgesehen davon ist die Inkonsistenz verwirrend, insbesondere in Javascript (das auch dieses "Feature" geerbt hat), einer Skriptsprache, in der dies weit entfernt von der Sprache abstrahiert werden sollte.
TL; DR : Weil Monate Namen haben und Tage des Monats nicht.
Ich würde Faulheit sagen. Arrays beginnen bei 0 (das weiß jeder); Die Monate des Jahres sind ein Array, was mich glauben lässt, dass sich ein Ingenieur bei Sun einfach nicht die Mühe gemacht hat, diese eine Kleinigkeit in den Java-Code zu integrieren.
Wahrscheinlich, weil Cs "struct tm" dasselbe tut.
Weil Programmierer von 0-basierten Indizes besessen sind. OK, es ist etwas komplizierter: Es ist sinnvoller, wenn Sie mit Logik auf niedrigerer Ebene arbeiten, die 0-basierte Indizierung zu verwenden. Aber im Großen und Ganzen bleibe ich immer noch bei meinem ersten Satz.
Persönlich nahm ich die Seltsamkeit der Java-Kalender-API als Hinweis darauf, dass ich mich von der gregorianischen Denkweise trennen und versuchen musste, diesbezüglich agnostischer zu programmieren. Insbesondere habe ich noch einmal gelernt, fest codierte Konstanten für Dinge wie Monate zu vermeiden.
Welche der folgenden Aussagen ist wahrscheinlicher?
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
Dies zeigt eine Sache, die mich ein wenig an Joda Time ärgert - es kann Programmierer dazu ermutigen, in fest codierten Konstanten zu denken. (Nur ein bisschen. Es ist nicht so, als ob Joda Programmierer dazu zwingt , schlecht zu programmieren.)
Für mich erklärt es niemand besser als mindpro.com :
Fallstricke
java.util.GregorianCalendar
hat weit weniger Käfer und Fallstricke als dieold java.util.Date
Klasse, aber es ist immer noch kein Picknick.Hätte es Programmierer gegeben, als die Sommerzeit zum ersten Mal vorgeschlagen wurde, hätten sie ein Veto gegen sie eingelegt, da sie verrückt und unlösbar waren. Bei der Sommerzeit besteht eine grundlegende Unklarheit. Im Herbst, wenn Sie Ihre Uhren um eine Stunde um 2 Uhr morgens zurückstellen, gibt es zwei verschiedene Zeitpunkte, die beide als 1:30 Uhr Ortszeit bezeichnet werden. Sie können sie nur unterscheiden, wenn Sie mit dem Messwert aufzeichnen, ob Sie Sommerzeit oder Standardzeit beabsichtigt haben.
Leider können Sie nicht sagen,
GregorianCalendar
welche Sie beabsichtigt haben. Um die Mehrdeutigkeit zu vermeiden, müssen Sie die Ortszeit mit der Dummy-UTC-Zeitzone angeben. Programmierer schließen normalerweise die Augen vor diesem Problem und hoffen nur, dass in dieser Stunde niemand etwas tut.Millennium Bug. Die Fehler gehören immer noch nicht zu den Kalenderklassen. Selbst in JDK (Java Development Kit) 1.3 gibt es einen Fehler von 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 am 01.01.2001 um 7 Uhr morgens für MST.
GregorianCalendar
wird von einem Riesenhaufen untypisierter int magischer Konstanten kontrolliert. Diese Technik zerstört völlig jede Hoffnung auf eine Fehlerprüfung zur Kompilierungszeit. Zum Beispiel, um den Monat zu erhalten, den Sie verwendenGregorianCalendar. get(Calendar.MONTH));
GregorianCalendar
hat die Roh-GregorianCalendar.get(Calendar.ZONE_OFFSET)
und die SommerzeitGregorianCalendar. get( Calendar. DST_OFFSET)
, aber keine Möglichkeit, den tatsächlich verwendeten Zeitzonenversatz zu erhalten. Sie müssen diese beiden separat erhalten und addieren.
GregorianCalendar.set( year, month, day, hour, minute)
setzt die Sekunden nicht auf 0.
DateFormat
undGregorianCalendar
nicht richtig ineinander greifen. Sie müssen den Kalender zweimal angeben, einmal indirekt als Datum.Wenn der Benutzer seine Zeitzone nicht richtig konfiguriert hat, wird standardmäßig entweder PST oder GMT verwendet.
Im Gregorianischen Kalender werden die Monate ab Januar = 0 nummeriert und nicht wie alle anderen auf dem Planeten mit 1. Die Tage beginnen jedoch bei 1, ebenso wie die Wochentage mit Sonntag = 1, Montag = 2,… Samstag = 7. Noch DateFormat. parse verhält sich traditionell mit Januar = 1.
java.util.Month
Java bietet Ihnen eine weitere Möglichkeit, 1-basierte Indizes monatelang zu verwenden. Verwenden Sie die java.time.Month
Aufzählung. Für jeden der zwölf Monate ist ein Objekt vordefiniert. Sie haben Nummern von 1 bis 12 für Januar bis Dezember; Rufen Sie getValue
für die Nummer.
Verwenden Sie Month.JULY
(Gibt Ihnen 7) anstelle von Calendar.JULY
(Gibt Ihnen 6).
(import java.time.*;)
Month.FEBRUARY.getValue() // February → 2.
2
Die Antwort von Jon Skeet ist richtig.
Jetzt haben wir einen modernen Ersatz für diese problematischen alten alten Datums- und Zeitklassen : die java.time- Klassen.
java.time.Month
Unter diesen Klassen ist die Aufzählung . Eine Aufzählung enthält ein oder mehrere vordefinierte Objekte, Objekte, die beim Laden der Klasse automatisch instanziiert werden. Auf wir ein Dutzend solcher Objekte haben, da jeder einen Namen: , , , und so weiter. Jedes davon ist eine Klassenkonstante. Sie können diese Objekte überall in Ihrem Code verwenden und übergeben. Beispiel:Month
Month
JANUARY
FEBRUARY
MARCH
static final public
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
Objekt für eine bestimmte Monatsnummer (1-12).
Month month = Month.of( 2 ); // 2 → February.
Wenn Sie in die andere Richtung gehen, fragen Sie ein Month
Objekt nach seiner Monatsnummer.
int monthNumber = Month.FEBRUARY.getValue(); // February → 2.
Viele andere nützliche Methoden für diesen Kurs, z. B. die Anzahl der Tage pro Monat . Die Klasse kann sogar einen lokalisierten Namen des Monats generieren .
Sie können den lokalisierten Namen des Monats in verschiedenen Längen oder Abkürzungen abrufen.
String output =
Month.FEBRUARY.getDisplayName(
TextStyle.FULL ,
Locale.CANADA_FRENCH
);
février
Außerdem sollten Sie Objekte dieser Aufzählung um Ihre Codebasis herum übergeben und nicht nur Ganzzahlen . Dies bietet Typensicherheit, stellt einen gültigen Wertebereich sicher und macht Ihren Code selbstdokumentierender. Weitere Informationen finden Sie im Oracle-Lernprogramm, wenn Sie mit der überraschend leistungsstarken Enum-Funktion in Java nicht vertraut sind.
Sie können auch die Year
und YearMonth
Klassen nützlich finden .
Das java.time- Framework ist in Java 8 und höher integriert. Diese Klassen verdrängen die lästigen alten Legacy - Datum-Zeit - Klassen wie java.util.Date
, .Calendar
, & java.text.SimpleDateFormat
.
Das Joda-Time- Projekt, das sich jetzt im Wartungsmodus befindet , empfiehlt die Migration zu java.time.
Weitere Informationen finden Sie im Oracle-Lernprogramm . Suchen Sie im Stapelüberlauf nach vielen Beispielen und Erklärungen. Die Spezifikation ist JSR 310 .
Woher bekomme ich die java.time-Klassen?
Das ThreeTen-Extra- Projekt erweitert java.time um zusätzliche Klassen. Dieses Projekt ist ein Testfeld für mögliche zukünftige Ergänzungen von java.time. Sie können einige nützliche Klassen hier wie finden Interval
, YearWeek
, YearQuarter
, und mehr .
Es ist nicht genau als Null an sich definiert, sondern als Kalender.Januar. Es ist das Problem, Ints als Konstanten anstelle von Enums zu verwenden. Kalender.Januar == 0.
Weil das Schreiben von Sprachen schwieriger ist als es aussieht und insbesondere der Umgang mit Zeit viel schwieriger ist 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 . Seien Sie nicht überrascht, wenn Ihr Kopf vom verwirrten Lachen abfällt.
Zusätzlich zu DannySmurfs Antwort auf Faulheit möchte ich hinzufügen, dass es Sie ermutigen soll, die Konstanten zu verwenden, wie z Calendar.JANUARY
.
Weil alles mit 0 beginnt. Dies ist eine grundlegende Tatsache der Programmierung in Java. Wenn eine Sache davon abweichen würde, würde dies zu einer ganzen Reihe von Verwirrung führen. Lassen Sie uns nicht über ihre Entstehung streiten und mit ihnen codieren.