Pourquoi le mois de janvier 0 dans le calendrier Java?

300

En java.util.Calendar, janvier est défini comme le mois 0, pas le mois 1. Y a-t-il une raison spécifique à cela?

J'ai vu beaucoup de gens devenir confus à ce sujet ...

Stéphane Bonniez
la source
4
N'est-ce pas ce genre de détail d'implémentation, puisque les constantes JANVIER, FÉVRIER etc. existent? Les classes de date sont antérieures à la prise en charge appropriée de l'énumération java.
gnud
6
Encore plus ennuyeux - pourquoi y a-t-il un mois de décembre?
mat
40
@gnud: Non, ce n'est pas un détail d'implémentation. Cela en fait un problème lorsque vous avez reçu un entier en base "naturelle" (c'est-à-dire Jan = 1) et que vous devez l'utiliser avec l'API de calendrier.
Jon Skeet
1
@matt b: c'est pour les calendriers non grégoriens (calendriers lunaires, etc.) qui ont treize mois. C'est pourquoi il vaut mieux ne pas penser en termes de nombres, mais laissez Calendar faire sa localisation.
erickson
7
L'argument de 13 mois n'a aucun sens. Si c'est le cas, pourquoi ne pas avoir le mois supplémentaire 0 ou 13?
Quinn Taylor

Réponses:

323

Cela fait juste partie du désordre horrible qu'est l'API Java date / heure. Énumérer ce qui ne va pas prendrait beaucoup de temps (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 de toute façon.

Faites-vous plaisir et utilisez Joda Time à la place, ou peut - être JSR-310 .

EDIT: Quant aux raisons pour lesquelles - comme indiqué dans d'autres réponses, cela pourrait bien être dû à d'anciennes 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 quelqu'un en dehors de l'équipe de mise en œuvre d'origine puisse vraiment expliquer les raisons - mais encore une fois, j'exhorte les lecteurs à ne pas trop s'inquiéter des raisons pour lesquelles de mauvaises décisions ont été prises, que de regarder toute la gamme des méchancetés java.util.Calendaret de trouver quelque chose de mieux.

Un point qui est favorable à l'utilisation base 0 index est qu'il fait des choses comme « tableaux de noms » plus facile:

// 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 avec 13 mois ... mais au moins la taille spécifiée est le nombre de mois que vous attendez.

Ce n'est pas une bonne raison, mais c'est une raison ...

EDIT: En tant que commentaire, demande des idées sur ce que je pense ne pas être correct avec la date / le calendrier

  • Des bases surprenantes (1900 comme base annuelle dans Date, certes pour les constructeurs déconseillés; 0 comme base mensuelle dans les deux)
  • Mutabilité - l'utilisation de types immuables facilite grandement l'utilisation de valeurs réellement efficaces
  • Un ensemble de types insuffisant: c'est bien d'avoir Dateet Calendarcomme des choses différentes, mais la séparation des valeurs "locales" vs "zonées" est manquante, tout comme la date / heure vs la date vs heure
  • Une API qui conduit à du code laid avec des constantes magiques, au lieu de méthodes clairement nommées
  • Une API qui est très difficile à raisonner - toutes les affaires sur le moment où les choses sont recalculées, etc.
  • L'utilisation de constructeurs sans paramètres par défaut à "maintenant", ce qui conduit à un code difficile à tester
  • L' Date.toString()implémentation qui utilise toujours le fuseau horaire local du système (ce qui a perturbé de nombreux utilisateurs de Stack Overflow auparavant)
Jon Skeet
la source
14
... et quoi de neuf avec toutes les méthodes simples et utiles de Date? Maintenant, je dois utiliser cet horrible objet Calendar de manière compliquée pour faire des choses qui étaient auparavant simples.
Brian Knoblauch
3
@Brian: Je ressens ta douleur. Encore une fois, Joda Time est plus simple :) (Le facteur d'immuabilité rend les choses beaucoup plus agréables à travailler aussi.)
Jon Skeet
8
Vous n'avez pas répondu à la question.
Zeemee
2
@ user443854: J'ai énuméré certains points dans une modification - voyez si cela aide.
Jon Skeet
2
Si vous utilisez Java 8, vous pouvez abandonner la classe Calendar et passer à la nouvelle et élégante API DateTime . La nouvelle API comprend également un DateTimeFormatter immuable / threadsafe qui est une grande amélioration par rapport au SimpleDateFormat problématique et coûteux.
ccpizza
43

Parce que faire des mathématiques avec des mois est beaucoup plus facile.

1 mois après décembre est janvier, mais pour comprendre cela normalement, vous devrez 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 tout cela à nouveau en soustrayant 1 avant d'ajouter le mois, puis faites votre module et enfin ajoutez 1 à nouveau ... alias contourner un problème sous-jacent.

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

Réfléchissons 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 contournement n'est pas nécessaire.

arucker
la source
5
C'est satisfaisant. Au moins, il y a de la valeur à cette folie!
moljac024
"Beaucoup de nombres magiques" - non, c'est juste un qui apparaît deux fois.
user123444555621
Revenir en arrière d'un mois est toujours un peu compliqué, cependant, grâce à l'utilisation malheureuse de C d'un opérateur "restant" plutôt que de "module". Je ne sais pas non plus combien de fois on a vraiment besoin de bump un mois sans ajuster l'année, et avoir des mois allant de 1 à 12 ne crée aucun problème avec `while (month> 12) {month- = 12; année ++;}
supercat
2
Parce que les fonctions sensées comme DateTime.AddMonths sont trop difficiles à implémenter correctement dans une bibliothèque, nous devons faire le calcul que vous avez décrit vous-même ... Mmmmmkay
nsimeonov
8
Je ne comprends pas ces votes positifs - ((11 - 1 + 1) % 12) + 1 = 12c'est juste (11 % 12) + 1pour les mois 1 à 12, il vous suffit d'ajouter le 1 après avoir fait le modulo. Aucune magie requise.
mfitzp
35

Les langages basés sur C copient C dans une certaine mesure. La tmstructure (définie dans time.h) a un champ entier tm_monavec la plage (commentée) de 0 à 11.

Les langages basés sur C commencent les tableaux à l'index 0. Cela était donc pratique pour sortir une chaîne dans un tableau de noms de mois, avec tm_moncomme index.

stesch
la source
22

Il y a eu beaucoup de réponses à cela, mais je vais quand même donner mon avis sur le sujet. La raison derrière ce comportement étrange, comme indiqué précédemment, vient du POSIX C time.hoù les mois étaient stockés dans un int avec la plage 0-11. Pour expliquer pourquoi, regardez-le comme ceci; les années et les jours sont considérés comme des nombres dans la langue parlée, mais les mois ont leurs propres noms. Donc, comme janvier est le premier mois, il sera stocké comme 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, car ils n'ont pas de nom, les stocker dans un entier comme 0-30 serait source de confusion, ajouterait beaucoup d' day+1instructions pour la sortie et, bien sûr, serait sujet à beaucoup de bugs.

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 où cela devrait être abstrait loin de la langague.

TL; DR : Parce que les mois ont des noms et les jours du mois n'en ont pas.

piksel bitworks
la source
1
"les mois ont des noms et les jours non." Avez-vous déjà entendu parler de «vendredi»? ;) OK, je suppose que vous vouliez dire «les jours du mois ne le font pas» - il serait peut-être utile de modifier votre (sinon bonne) réponse. :-)
Andrew Thompson
Est-ce que 0/0/0000 est mieux rendu comme "00-Jan-0000" ou comme "00-XXX-0000"? À mon humble avis, beaucoup de code aurait été plus propre s'il y avait treize "mois", mais le mois 0 a reçu un nom fictif.
supercat
1
c'est une prise intéressante, mais 0/0/0000 n'est pas une date valide. comment rendriez-vous 40/40/0000?
piksel bitworks
12

En Java 8, il y a une nouvelle API Date / Heure JSR 310 qui est plus sensée. Le responsable de la spécification est le même que l'auteur principal de JodaTime et ils partagent de nombreux concepts et modèles similaires.

Alex Miller
la source
2
La nouvelle API Date Time fait désormais partie de Java 8
mschenk74
9

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 à croire qu'un ingénieur de Sun n'a pas pris la peine de mettre cette petite touche dans le code Java.

Le Schtroumpf
la source
9
Non, je ne le ferais pas. Il est plus important d'optimiser l'efficacité de ses clients que de ses programmeurs. Comme ce client passe du temps à le demander, il a échoué.
TheSmurf
2
Cela n'a rien à voir avec l'efficacité - ce n'est pas comme si des mois étaient stockés dans un tableau et vous auriez besoin de 13 pour représenter 12 mois. Il s'agit de ne pas rendre l'API aussi conviviale qu'elle aurait dû l'être en premier lieu. Josh Bloch diffuse la date et le calendrier dans "Java efficace". Très peu d'API sont parfaites, et les API de date / heure en Java ont le rôle malheureux d'être celles qui ont été gaffées. C'est la vie, mais ne prétendons pas que cela a quelque chose à voir avec l'efficacité.
Quinn Taylor
1
Pourquoi ne pas compter les jours de 0 à 30 alors? C'est juste incohérent et bâclé.
Juangui Jordán du
9

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

Paul Tomblin
la source
8

Parce que les programmeurs sont obsédés par les index basés sur 0. OK, c'est un peu plus compliqué que cela: cela a plus de sens lorsque vous travaillez avec une logique de niveau inférieur pour utiliser l'indexation basée sur 0. Mais dans l'ensemble, je m'en tiendrai à ma première phrase.

Dinah
la source
1
Ceci est un autre de ces expressions idiomatiques / habitudes qui vont ainsi retourner à l' assembleur ou le langage machine où tout est fait en termes de compensations, pas d' index. La notation de tableau est devenue un raccourci pour accéder aux blocs contigus, à partir du décalage 0.
Ken Gentle
4

Personnellement, j'ai pris l'étrangeté de l'API du calendrier Java comme une indication que je devais me séparer de la mentalité grégorienne et essayer de programmer de manière plus agnostique à cet égard. Plus précisément, j'ai réappris à éviter les constantes codées en dur pour des choses comme des mois.

Lequel des énoncés 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 au sujet 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 obligeait les programmeurs à mal programmer.)

Paul Brinkley
la source
1
Mais quel schéma est le plus susceptible de vous donner mal à la tête lorsque vous n'avez pas de constante dans votre code - vous avez une valeur qui est le résultat d'un appel de service Web ou autre.
Jon Skeet
Cet appel de service Web devrait également utiliser cette constante, bien sûr. :-) Il en va de même pour tout appelant externe. Une fois que nous avons établi que plusieurs normes existent, la nécessité de les appliquer devient évidente. (J'espère avoir bien compris votre commentaire ...)
Paul Brinkley
3
Oui, nous devons appliquer la norme que presque tout le monde utilise pour exprimer les mois - la norme basée sur 1.
Jon Skeet
Le mot clé ici étant "presque". De toute évidence, Jan = 1, etc. semble naturel dans un système de date avec une utilisation extrêmement large, mais pourquoi nous permettre de faire une exception pour éviter les constantes codées en dur, même dans ce cas?
Paul Brinkley
3
Parce que cela facilite la vie. C'est juste le cas. Je n'ai jamais rencontré de problème ponctuel avec un système d'un mois. J'ai vu de nombreux bugs de ce type avec l'API Java. Ignorer ce que tout le monde dans le monde fait n'a aucun sens.
Jon Skeet
4

Pour moi, personne ne l'explique mieux que mindpro.com :

Gotchas

java.util.GregorianCalendara beaucoup moins de bugs et de pièges que la old java.util.Dateclasse mais ce n'est toujours pas un pique-nique.

S'il y avait eu des programmeurs lorsque l'heure d'été a été proposée pour la première fois, ils l'auraient opposé son veto comme insensé et intraitable. Avec l'heure d'été, il existe 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 1:30 AM heure locale. Vous ne pouvez les différencier que si vous enregistrez si vous avez l'intention de passer à l'heure d'été ou à l'heure standard avec la lecture.

Malheureusement, il n'y a aucun moyen de dire GregorianCalendarce que vous vouliez. Vous devez recourir à lui dire l'heure locale avec le factice UTC TimeZone pour éviter l'ambiguïté. Les programmeurs ferment généralement les yeux sur ce problème et espèrent juste que personne ne fera rien pendant cette heure.

Bug du millénaire. Les bogues ne sont toujours pas sortis des classes de calendrier. Même dans JDK (Java Development Kit) 1.3, il existe un bogue de 2001. Considérez 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 bug disparaît à 7h le 01/01/2001 pour MST.

GregorianCalendarest contrôlé par un géant de tas de constantes magiques int. Cette technique détruit totalement tout espoir de vérification des erreurs au moment de la compilation. Par exemple pour obtenir le mois que vous utilisez GregorianCalendar. get(Calendar.MONTH));

GregorianCalendara le brut GregorianCalendar.get(Calendar.ZONE_OFFSET)et l'heure d'été GregorianCalendar. get( Calendar. DST_OFFSET), mais aucun moyen d'obtenir le décalage de fuseau horaire réel utilisé. Vous devez obtenir ces deux séparément et les ajouter ensemble.

GregorianCalendar.set( year, month, day, hour, minute) ne met pas les secondes à 0.

DateFormat et GregorianCalendar ne s'engrènent pas correctement. Vous devez spécifier le calendrier deux fois, une fois indirectement en tant que date.

Si l'utilisateur n'a pas configuré correctement son fuseau horaire, il passera par défaut discrètement à PST ou GMT.

Dans GregorianCalendar, les mois sont numérotés à partir de janvier = 0, plutôt que 1 comme tout le monde sur la planète. Pourtant, les jours commencent à 1, tout comme les jours de la semaine avec dimanche = 1, lundi = 2,… samedi = 7. Pourtant DateFormat. l'analyse se comporte de manière traditionnelle avec janvier = 1.

Edwin Dalorzo
la source
4

java.util.Month

Java vous offre une autre façon d'utiliser des index basés sur 1 pendant des mois. Utilisez l' java.time.Monthénumération. 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; appeler getValuele numéro.

Utilisez Month.JULY(vous donne 7) au lieu de Calendar.JULY(vous donne 6).

(import java.time.*;)
Digital_Reality
la source
3

tl; dr

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

2

Détails

La réponse de Jon Skeet est correcte.

Nous avons maintenant un remplacement moderne pour ces anciennes classes de date-heure gênantes : les classes java.time .

java.time.Month

Parmi ces classes se trouve l' énumération . Une énumération contient un ou plusieurs objets prédéfinis, objets qui sont automatiquement instanciés lors du chargement de la classe. Sur nous avons une douzaine de ces objets, chacun donné un nom: , , et ainsi de suite. Chacun d'eux est une constante de classe. Vous pouvez utiliser et transmettre ces objets n'importe où dans votre code. Exemple:Month MonthJANUARYFEBRUARYMARCHstatic final publicsomeMethod( Month.AUGUST )

Heureusement, ils ont une numérotation saine, 1-12 où 1 est janvier et 12 est décembre.

Obtenez un Monthobjet pour un numéro de mois particulier (1-12).

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

Dans l'autre sens, demandez à un Monthobjet son numéro de mois.

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

Beaucoup d'autres méthodes pratiques sur cette classe, comme connaître 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, en différentes longueurs ou abréviations.

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

février

En outre, vous devez 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é du type, garantit une plage de valeurs valide et rend votre code plus auto-documenté. Voir le didacticiel Oracle si vous ne connaissez pas la fonction d'énumération étonnamment puissante de Java.

Vous pouvez également trouver utiles les classes Yearet YearMonth.


À propos de java.time

Le framework java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent les anciens gênants hérités des classes date-heure tels que java.util.Date, .Calendar, et java.text.SimpleDateFormat.

Le projet Joda-Time , désormais en mode maintenance , conseille la migration vers java.time.

Pour en savoir plus, consultez le didacticiel Oracle . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 310 .

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 de futurs ajouts possibles à java.time. Vous trouverez peut - être des classes utiles ici, comme Interval, YearWeek, YearQuarteret plus .

Basil Bourque
la source
0

Il n'est pas exactement défini comme zéro en soi, il est défini comme Calendar.January. C'est le problème de l'utilisation des entiers comme constantes au lieu des énumérations. Calendar.January == 0.

Pål GD
la source
1
Les valeurs sont identiques. Les API peuvent également renvoyer 0, c'est identique à la constante. Calendar.JANUARY aurait pu être défini comme 1 - c'est tout. Une énumération serait une bonne solution, mais de véritables énumérations n'ont pas été ajoutées au langage avant Java 5, et Date existe depuis le début. C'est malheureux, mais vous ne pouvez vraiment pas "réparer" une API aussi fondamentale une fois que le code tiers l'utilise. Le mieux qui puisse être fait est de fournir une nouvelle API et de déprécier l'ancienne pour encourager les gens à passer à autre chose. Merci, Java 7 ...
Quinn Taylor
0

Parce que l'écriture de la langue est plus difficile qu'il n'y paraît, et le temps de traitement en particulier est beaucoup plus difficile que la plupart des gens ne le pensent. Pour une petite partie du problème (en réalité, pas Java), voir la vidéo YouTube "Le problème du temps et des fuseaux horaires - Computerphile" sur https://www.youtube.com/watch?v=-5wpm-gesOY . Ne soyez pas surpris si votre tête tombe de rire de confusion.

Tihamer
la source
-1

En plus de la réponse de DannySmurf sur la paresse, j'ajouterai que c'est pour vous encourager à utiliser les constantes, telles que Calendar.JANUARY.

Powerlord
la source
5
C'est très bien quand vous écrivez explicitement le code pour un mois particulier, mais c'est pénible lorsque vous avez le mois sous une forme "normale" à partir d'une source différente.
Jon Skeet
1
C'est également un problème lorsque vous essayez d'imprimer la valeur du mois d'une manière particulière - vous y ajoutez toujours 1.
Brian Warshaw
-2

Parce que tout commence par 0. C'est un fait basique de la programmation en Java. Si une chose devait s'en écarter, cela conduirait à une confusion totale. Ne discutons pas de leur formation et ne codons pas avec eux.

Syrrus
la source
2
Non, la plupart des choses dans le monde réel commencent par 1. Les décalages commencent par 0, et le mois de l'année n'est pas un décalage, c'est l'un des douze, tout comme le jour du mois est l'un des 31 ou 30 ou 29 ou 28. Traiter le mois comme un décalage est tout simplement capricieux, surtout si en même temps nous ne traitons pas le jour du mois de la même manière. Quelle serait la raison de cette différence?
SantiBailors
dans le monde réel, commencez par 1, dans le monde Java, commencez par 0. MAIS ... je pense que c'est parce que: - pour calculer le jour de la semaine, il ne peut pas être compensé pour quelques calculs sans ajouter quelques étapes supplémentaires à il ... - en outre, il affiche les jours complets du mois si nécessaire (sans confusion ni besoin de vérifier février) - Pour le mois, il vous oblige à produire dans un format de date qui doit être utilisé de toute façon. De plus, étant donné que le nombre de mois dans une année est normal et que les jours dans un mois ne le sont pas, il est logique si vous devez déclarer des tableaux et utiliser un décalage pour mieux s'adapter au tableau.
Syrrus