Dans Java.util.Calendar
, janvier est défini comme le mois 0 et non le mois 1. Y a-t-il une raison spécifique à cela?
J'ai vu beaucoup de gens devenir confus à ce sujet ...
C'est juste une partie du fouillis horrible qu'est l'API Java date/heure. Il serait très long d’énumérer ce qui ne va pas avec cela (et je suis sûr que je ne connais pas la moitié des problèmes). Certes, travailler avec des dates et des heures est délicat, mais aaargh quand même.
Faites-vous une faveur et utilisez Joda Time à la place, ou éventuellement JSR-31 .
EDIT: Quant aux raisons pour lesquelles - comme indiqué dans d’autres réponses, cela pourrait bien être dû à de vieilles API C, ou simplement à un sentiment général de tout commencer à partir de 0 ... sauf que les jours commencent par 1, bien sûr. Je doute que quiconque en dehors de l'équipe de mise en œuvre d'origine puisse réellement indiquer les raisons - mais encore une fois, j'invite les lecteurs à ne pas trop s'inquiéter de pourquoi de mauvaises décisions ont été prises, au lieu de regarder toute la gamme de la méchanceté dans Java.util.Calendar
et trouver quelque chose de mieux.
Un point qui est en faveur de l'utilisation d'index de base 0 est qu'il facilite les choses telles que "les tableaux de noms":
// I "know" there are 12 months
String[] monthNames = new String[12]; // and populate...
String name = monthNames[calendar.get(Calendar.MONTH)];
Bien sûr, cela échoue dès que vous obtenez un calendrier de 13 mois ... mais au moins la taille spécifiée est le nombre de mois que vous attendez.
Ce n'est pas une bonne bonne raison, mais c'est une raison ...
EDIT: En guise de commentaire, demande quelques idées sur ce que je pense être le problème avec Date/Calendrier:
Date
et Calendar
différentes, mais la séparation des valeurs "local" et "zoné" est manquante, de même que la date/heure, la date et l’heure.Date.toString()
qui utilise toujours le fuseau horaire local du système (cela confondait de nombreux utilisateurs de Stack Overflow auparavant)Parce que faire des maths avec des mois est beaucoup plus facile.
1 mois après décembre, c'est janvier, mais pour le comprendre normalement, vous devez prendre le numéro du mois et faire des calculs
12 + 1 = 13 // What month is 13?
Je connais! Je peux résoudre ce problème rapidement en utilisant un module de 12.
(12 + 1) % 12 = 1
Cela fonctionne très bien pendant 11 mois jusqu'en novembre ...
(11 + 1) % 12 = 0 // What month is 0?
Vous pouvez faire fonctionner tout cela à nouveau en soustrayant 1 avant d’ajouter le mois, puis faites votre module et enfin rajoutez 1 de nouveau ... c’est-à-dire que vous travaillez autour d’un problème sous-jacent.
((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers!
Pensons maintenant au problème des mois 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
Tous les mois fonctionnent de la même manière et un travail de contournement n’est pas nécessaire.
Les langues basées sur le C copient le C dans une certaine mesure. La structure tm
(définie dans time.h
) a un champ entier tm_mon
avec la plage (commentée) de 0 à 11.
Les langages basés sur C démarrent les tableaux à l'index 0. Cela était donc pratique pour afficher une chaîne dans un tableau de noms de mois, avec tm_mon
comme index.
Il y a eu beaucoup de réponses à cela, mais je vais quand même donner mon point de vue sur le sujet. Comme indiqué précédemment, ce comportement étrange vient de POSIX C time.h
, où les mois ont été stockés dans un int de plage comprise entre 0 et 11. Pour expliquer pourquoi, regardez comme ça; les années et les jours sont considérés comme des nombres dans la langue parlée, mais les mois ont leur propre nom. Donc, parce que janvier est le premier mois, il sera stocké avec le décalage 0, le premier élément du tableau. monthname[JANUARY]
serait "January"
. Le premier mois de l'année est l'élément de tableau du premier mois.
Les numéros de jour, d’autre part, étant donné qu’ils n’ont pas de nom, les stocker dans un entier égal à 0-30 serait déroutant, ajoutez beaucoup d’instructions day+1
pour la sortie et, bien sûr, soyez sujet à beaucoup bogues.
Cela étant dit, l’incohérence est source de confusion, en particulier en javascript (qui a également hérité de cette "fonctionnalité"), un langage de script dans lequel il devrait être abstrait bien loin de la langue.
TL; DR: Parce que les mois ont des noms et que les jours du mois n'en ont pas.
Dans Java 8, il existe une nouvelle API Date/Time JSR 31 qui est plus saine. Le responsable des spécifications est le même que l'auteur principal de JodaTime et partage de nombreux concepts et modèles similaires.
Je dirais la paresse. Les tableaux commencent à 0 (tout le monde le sait); les mois de l'année sont un tableau, ce qui m'amène à penser qu'un ingénieur de Sun n'a tout simplement pas pris la peine d'insérer cette petite astuce dans le code Java.
Probablement parce que "struct tm" de C fait la même chose.
Parce que les programmeurs sont obsédés par les index basés sur 0. OK, c'est un peu plus compliqué que cela: c'est plus logique de travailler avec une logique de niveau inférieur pour utiliser l'indexation basée sur 0. Mais en gros, je vais rester avec ma première phrase.
Personnellement, j'ai pris l'étrangeté de l'API de calendrier Java comme une indication de la nécessité de me séparer de la mentalité centrée sur le grégorien et d'essayer de programmer de manière plus agnostique à cet égard. Plus précisément, j'ai de nouveau appris à éviter les constantes codées en dur, par exemple, des mois.
Lequel des éléments suivants est le plus susceptible d'être correct?
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
Cela illustre une chose qui m'énerve un peu à propos de Joda Time: cela peut encourager les programmeurs à penser en termes de constantes codées en dur. (Seulement un peu, cependant. Ce n'est pas comme si Joda était obligeant les programmeurs à programmer mal.)
Java.util.Month
_Java vous offre un autre moyen d'utiliser 1 index basé sur plusieurs mois. Utilisez le Java.time.Month
enum. Un objet est prédéfini pour chacun des douze mois. Ils ont des numéros attribués à chaque 1-12 pour Janvier-Décembre; appelez getValue
pour le numéro.
Utilisez _Month.JULY
_ (vous en donne 7) au lieu de _Calendar.JULY
_ (vous en donne 6).
_(import Java.time.*;)
_
Pour moi, personne ne l'explique mieux que mindpro.com :
Les pièges
Java.util.GregorianCalendar
a beaucoup moins de bogues et de pièges que la classeold Java.util.Date
mais ce n'est toujours pas un pique-nique.S'il y avait eu des programmeurs lorsque l'heure avancée avait été proposée pour la première fois, ils l'auraient opposé à son veto comme étant insensé et insoluble. Avec l'heure d'été, il y a une ambiguïté fondamentale. À l'automne, lorsque vous réglez vos horloges d'une heure à 2 heures du matin, il y a deux instants différents, tous deux appelés 1h30 du matin, heure locale. Vous ne pouvez les distinguer que si vous enregistrez si vous avez prévu l'heure d'été ou l'heure standard avec la lecture.
Malheureusement, il n’ya aucun moyen de dire à
GregorianCalendar
ce que vous vouliez. Vous devez recourir à l'heure locale avec le fuseau horaire UTC factice pour éviter toute ambiguïté. Les programmeurs ferment généralement les yeux sur ce problème et espèrent que personne ne fera rien pendant cette heure.Bug du millénaire. Les bogues ne sont toujours pas sortis des classes du calendrier. Même dans JDK (Java Development Kit) 1.3, il existe un bogue de 2001. Considérons le code suivant:
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 */
Le bogue disparaît à 07h00 le 01/01/2001 pour MST.
GregorianCalendar
est contrôlé par un gigantesque empilement de constantes magiques non typées. Cette technique détruit totalement tout espoir de vérification d'erreur au moment de la compilation. Par exemple, pour obtenir le mois où vous utilisezGregorianCalendar. get(Calendar.MONTH));
GregorianCalendar
a le caractère brutGregorianCalendar.get(Calendar.ZONE_OFFSET)
et l'heure avancéeGregorianCalendar. get( Calendar. DST_OFFSET)
, mais aucun moyen d'obtenir le décalage de fuseau horaire utilisé. Vous devez obtenir ces deux séparément et les additionner.
GregorianCalendar.set( year, month, day, hour, minute)
ne règle pas les secondes à 0.
DateFormat
etGregorianCalendar
ne maillent pas correctement. Vous devez spécifier le calendrier deux fois, une fois indirectement en tant que date.Si l'utilisateur n'a pas configuré son fuseau horaire correctement, il passera silencieusement à PST ou à GMT.
Dans GregorianCalendar, les mois sont numérotés à partir de janvier = 0, plutôt que 1, comme le font tous les autres habitants de la planète. Cependant, les jours commencent à 1, de même que les jours de la semaine avec dimanche = 1, lundi = 2,… samedi = 7. Pourtant DateFormat. parse se comporte de manière traditionnelle avec January = 1.
_Month.FEBRUARY.getValue() // February → 2.
_
2
Le Réponse de Jon Skeet est correct.
Nous avons maintenant un remplacement moderne pour ces anciennes classes de date/heure problématiques: les classes Java.time .
Java.time.Month
_Parmi ces classes, on trouve Month
enum . Une énumération contient un ou plusieurs objets prédéfinis, des objets qui sont automatiquement instanciés lors du chargement de la classe. Sur Month
, nous avons une douzaine d'objets de ce type, chacun ayant reçu un nom: JANUARY
, FEBRUARY
, MARCH
, etc. Chacun de ceux-ci est une constante de classe _static final public
_. Vous pouvez utiliser et transmettre ces objets n'importe où dans votre code. Exemple: someMethod( Month.AUGUST )
Heureusement, ils ont une numérotation saine, 1-12 où 1 est janvier et 12 est décembre.
Obtenez un objet Month
pour un numéro de mois particulier (1-12).
_Month month = Month.of( 2 ); // 2 → February.
_
Dans l'autre sens, demandez à un objet Month
son numéro de mois.
_int monthNumber = Month.FEBRUARY.getValue(); // February → 2.
_
Beaucoup d'autres méthodes pratiques sur cette classe, telles que savoir le nombre de jours de chaque mois . La classe peut même générer un nom localisé du mois.
Vous pouvez obtenir le nom localisé du mois, sous différentes longueurs ou abréviations.
_String output =
Month.FEBRUARY.getDisplayName(
TextStyle.FULL ,
Locale.CANADA_FRENCH
);
_
février
En outre, vous devriez passer des objets de cette énumération autour de votre base de code plutôt que de simples nombres entiers . Cela garantit la sécurité de type, garantit une plage de valeurs valide et rend votre code plus auto-documenté. Voir Tutoriel Oracle si vous n’êtes pas familiarisé avec la facilité étonnamment puissante d’enum en Java.
Vous pouvez également trouver utile les classes Year
et YearMonth
.
Le cadre Java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent les anciennes classes ennuyeuses anciennes date-heure telles que Java.util.Date
, .Calendar
, & Java.text.SimpleDateFormat
.
Le projet Joda-Time , actuellement en mode maintenance , conseille la migration vers Java.time.
Pour en savoir plus, voir le Tutoriel Oracle . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 31 .
Où obtenir les classes Java.time?
Le projet ThreeTen-Extra étend Java.time avec des classes supplémentaires. Ce projet est un terrain d’essai pour d’éventuels ajouts à Java.time. Vous pouvez trouver ici des classes utiles telles que Interval
, YearWeek
, YearQuarter
, et plus .
Parce que l'écriture de la langue est plus difficile qu'il n'y paraît et que le temps de traitement en particulier est beaucoup plus difficile que ne le pensent la plupart des gens. Pour une petite partie du problème (en réalité, pas Java), voir la vidéo de YouTube "Le problème de l'heure et des fuseaux horaires - Computerphile" à l'adresse https://www.youtube.com/watch ? v = -5wpm-gesOY . Ne soyez pas surpris si votre tête tombe de rire dans la confusion.
Ce n'est pas exactement défini comme zéro en soi, mais comme Calendar.January. C'est le problème d'utiliser ints comme constantes au lieu d'énums. Calendar.January == 0.