2010-05-31 3 views
14

Je viens de rencontrer un comportement étrange avec la classe GregorianCalendar, et je me demandais si je faisais vraiment quelque chose de mal. Ceci ne s'applique que lorsque le mois de la date d'initialisation est réellement supérieur au mois auquel je vais configurer le calendrier.Comportement étrange avec GregorianCalendar

Voici l'exemple de code:

// today is 2010/05/31 
    GregorianCalendar cal = new GregorianCalendar(); 

    cal.set(Calendar.YEAR, 2010); 
    cal.set(Calendar.MONTH, 1); // FEBRUARY 

    cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); 
    cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY)); 
    cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE)); 
    cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND)); 
    cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND)); 

    return cal.getTime(); // => 2010/03/03, wtf 

Je sais que le problème est causé par le fait que la date d'initialisation du calendrier est un mois de 31 jours (mai), qui mess avec le mois fixé à février (28 journées). Le correctif est facile (il suffit de mettre day_of_month à 1 avant de régler l'année et le mois), mais je me demandais si c'était vraiment le comportement désiré. Des pensées ?

Répondre

14

Il obtient les maximums réels de la date/heure actuelle. Le mois de mai a 31 jours, soit 3 de plus que le 28 février et il passera donc au 3 mars.

Vous devez appeler Calendar#clear() après l'obtention/sa création:

GregorianCalendar cal = new GregorianCalendar(); 
cal.clear(); 
// ... 

Il en résulte:

Sun Feb 28 23:59:59 GMT-04:00 2010 

(ce qui est correct selon mon fuseau horaire)

Comme indiqué dans un des réponses, les java.util.Calendar et Date sont des échecs épiques. Envisagez JodaTime lorsque vous effectuez des opérations de date/heure intensives.

+0

Cela fonctionne. Merci!! –

2

Oui, voici comment cela fonctionne. Si vous démarrez à partir d'un GregorianCalendar qui a une date précise et que vous le modifiez en le rendant incohérent, vous ne devriez pas faire confiance aux résultats obtenus.

Selon la documentation sur getActualMaximum(..) il est écrit:

Par exemple, si la date de cette instance est le 1er Février 2004, la valeur maximale réelle du champ DAY_OF_MONTH est 29 parce que 2004 est une année bissextile , et si la date de cette instance est le 1er février 2005, il est 28.

Il est donc supposé fonctionner mais vous devez le nourrir avec des valeurs cohérentes. 31 February 2010 n'est pas correct et l'application de choses qui s'appuient sur la valeur de date (comme getActualMaximum) ne peut pas fonctionner. Comment devrait-il le réparer par lui-même? En décidant que le mois est faux? ou que le jour est faux?

Par ailleurs, comme tout le monde dit toujours utiliser JodaTime .. :)

+1

Si vous définissez le mois sur Février, une API normale se rendrait compte que cela invalide le jour et qu'il doit être ajusté. Mais 'java.util.Calendar' est beaucoup de choses, mais pas une API normale. Voir le premier commentaire ici pour savoir comment JodaTime le fait: http://blog.smart-java.nl/blog/index.php/2010/01/20/java-util-calendar-getactualmaximum-returns-strange-results/ – Yishai

1

Peut-être setLenient(boolean lenient) trierai pour vous. Je reçois une exception lorsque je cours le code ci-dessous.

Sinon, Joda est une meilleure réponse.

import java.util.Calendar; 

public class CalTest 
{ 
    public static void main(String[] args) 
    { 
     // today is 2010/05/31 
     Calendar cal = Calendar.getInstance(); 
     cal.setLenient(false); 

     cal.set(Calendar.YEAR, 2010); 
     cal.set(Calendar.MONTH, 1); // FEBRUARY 

     cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); 
     cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY)); 
     cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE)); 
     cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND)); 
     cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND)); 

     System.out.println(cal.getTime()); 
    } 
} 
+0

Malheureusement, même un calendrier strict est d'une utilité limitée. Il lancerait une exception à 'getTime' mais rien d'autre. http://blog.smart-java.nl/blog/index.php/2010/01/20/java-util-calendar-getactualmaximum-returns-strange-results/ – Yishai

+0

D'accord - c'est ce que j'observe aussi. – duffymo

2

Je suis sûr que ce n'est pas un comportement souhaité. Je suis également sûr que personne n'a vraiment pensé à ce cas d'utilisation quand ils ont fait la classe. Le fait est que Calendar a un très gros problème avec l'état interne et comment il gère toutes les transitions potentielles dans toutes les méthodes de jeu.

Si vous ne pouvez pas utiliser JodaTime ou JSR-310 dans votre projet, effectuez un test intensif lors de l'utilisation de la classe Calendrier. Comme vous pouvez le voir dans ce cas, le code du calendrier se comporte différemment selon le jour du mois (ou l'heure de la journée) où vous exécutez le code.

0

La raison doit être que MONTH a une structure logique de type énumération. Vous pouvez facilement remplir et lire des tableaux/collections/listes. En raison de l'internationalisation, il doit être énumérable (accès indirect). DAY est juste un Integer accessible directement. C'est la différence.

0

Le calendrier commence par le jour actuel - 31 mai 2010 dans votre exemple. Lorsque vous définissez le mois sur février, la date change au 31 février 2010, ce qui correspond au 3 mars 2010, de sorte que cal.getActualMaximum (Calendar.DAY_OF_MONTH) renvoie 31 pour le mois de mars.

Calendar c = Calendar.getInstance(); 
c.set(Calendar.YEAR, 2010); 
c.set(Calendar.MONTH, Calendar.MAY); 
c.set(Calendar.DAY_OF_MONTH, 31); 
System.out.println(c.getTime()); 
c.set(Calendar.MONTH, Calendar.FEBRUARY); 
System.out.println(c.getTime()); 

sortie:

Mon May 31 20:20:25 GMT+03:00 2010 
Wed Mar 03 20:20:25 GMT+03:00 2010 

Pour fixer votre code, vous pouvez ajouter cal.clear(); ou définir le jour 1..28 avant de définir le mois