web-dev-qa-db-fra.com

Pourquoi le mois de janvier 0 dans Java Calendrier?

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 ...

286
Stéphane Bonniez

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:

  • Bases surprenantes (1900 comme année dans Date, certes pour les constructeurs déconseillés; 0 comme mois dans les deux)
  • La mutabilité - utiliser des types immuables rend beaucoup plus simple pour travailler avec ce qui est réellement des valeurs
  • Un ensemble de types insuffisant: il est agréable d’avoir 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.
  • Une API qui conduit à un code laid avec des constantes magiques, au lieu de méthodes clairement nommées
  • Une API sur laquelle il est très difficile de raisonner - toutes les activités relatives au moment du recalcul, etc.
  • Utilisation de constructeurs sans paramètre pour utiliser "maintenant" par défaut, ce qui conduit à un code difficile à tester
  • L'implémentation Date.toString() qui utilise toujours le fuseau horaire local du système (cela confondait de nombreux utilisateurs de Stack Overflow auparavant)
313
Jon Skeet

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.

40
arucker

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.

35
stesch

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.

22
piksel bitworks

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.

12
Alex Miller

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.

9
TheSmurf

Probablement parce que "struct tm" de C fait la même chose.

9
Paul Tomblin

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.

7
Dinah

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.)

4
Paul Brinkley

_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.*;)
_
4
Digital_Reality

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 classe old 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 utilisez GregorianCalendar. get(Calendar.MONTH));

GregorianCalendar a le caractère brut GregorianCalendar.get(Calendar.ZONE_OFFSET) et l'heure avancée GregorianCalendar. 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 et GregorianCalendar 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.

3
Edwin Dalorzo

tl; dr

_Month.FEBRUARY.getValue()  // February → 2.
_

2

Détails

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 Monthenum . 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 .


À propos de Java.time

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 .

2
Basil Bourque

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.

0
Tihamer

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.

0
Pål GD