Date Javascript et piège du changement d’heure

Un bug est apparu récemment dans l’une de nos applications suite au passage à l’heure d’hiver.

Comportement standard lors du changement d’heure

Générons une plage de date au moment du changement d’heure :

var startDate = new Date(2013,09,27,0,0);
var endDate = new Date(2013,09,27,4,0);
var currentDate = startDate;

while(currentDate < endDate)
{
    // on ajoute une heure
    currentDate = new Date(currentDate.getTime() + (3600000));
    console.log(currentDate);
}

L’exécution produit la sortie suivante :

Sun Oct 27 2013 01:00:00 GMT+0200 (CEST)
Sun Oct 27 2013 02:00:00 GMT+0200 (CEST)
Sun Oct 27 2013 02:00:00 GMT+0100 (CET)
Sun Oct 27 2013 03:00:00 GMT+0100 (CET)
Sun Oct 27 2013 04:00:00 GMT+0100 (CET)

Notez que nous obtenons deux dates successives à 02:00 l’une à l’heure d’été, puis une à l’heure d’hiver. Jusqu’ici tout est normal puisque la journée du passage à l’heure d’hiver compte 25 heures.

 

Mise en lumière de notre bug

Reprenons exactement le même code en rajoutant une manipulation sur chaque date générée :

while(currentDate < endDate)
{
    // on ajoute une heure
    currentDate = new Date(currentDate.getTime() + (3600000));

    // manipulation de la date générée
    currendDate.setSeconds(0);

    console.log(currentDate);
}

L’exécution produit la sortie suivante :

Sun Oct 27 2013 01:00:00 GMT+0200 (CEST)
Sun Oct 27 2013 02:00:00 GMT+0100 (CET)
Sun Oct 27 2013 03:00:00 GMT+0100 (CET)
Sun Oct 27 2013 04:00:00 GMT+0100 (CET)

La date à 02:00 est considérée comme étant déjà à l’heure d’hiver, il nous manque une heure dans la journée !

 

Analyse et résolution

Nous avons résolu le problème en manipulant la Date Javascript au travers des méthodes ‘setUTC…’ au lieu des traditionnelles méthodes ‘set…’.

    // manipulation de la date générée
    currendDate.setUTCSeconds(0);

Si l’on regarde les différences de spécification (setSeconds et setUTCSeconds par exemple), il semblerait que les méthodes ‘set…’ cherchent à déterminer dynamiquement le décalage horaire le plus « approprié » sur l’instance de Date avant d’appliquer la modification alors que les méthodes ‘setUTC…’ utilisent directement cette instance sans traitement préalable.

Seul le moment du passage à l’heure d’hiver est impacté, le passage à l’heure d’été « sautant » une heure, il n’y a pas d’ambiguïté possible.

Tagué , ,

Arguments booléens et lisibilité du code

Que fait l’appel de fonction suivant ?

runSomeLogic(person, true, false);

Le traitement est déclenché pour une instance de « person » donnée, mais impossible de déterminer ce que font les arguments booléens.
Voici quelques alternatives qui devraient permettre d’améliorer la lisibilité du code source.

 

Solution 1 : utiliser les commentaires

runSomeLogic(
    person, 
    true /* enable caching */, 
    false /* disable security */
);

Inconvénient :

  • Ces commentaires sont laissés au bon vouloir du développeur.

 

Solution 2 : remplacer chaque booléen par une enum

enum Cache {
  ENABLE,
  DISABLE
}

enum Security {
  ENABLE,
  DISABLE
}

runSomeLogic(person, Cache.ENABLE, Security.DISABLE);

Inconvénients :

  • La création d’autant d’enum que de booléens est fastidieuse
  • Il faut gérer un « null » qui pourrait être passé en paramètre à la place de l’enum

 

Solution 3 : utiliser des masques binaires

On créé des constantes de type masques binaires en jouant sur les décalages de bits

private static final int NONE = 0;
private static final int CACHE = 1 << 1;
private static final int SECURITY = 1 << 2;

Ca permet d’activer simultanément plusieurs fonctionnalités à l’aide de l’opérateur ‘|’

// disable security, disable cache
runSomeLogic(person, NONE); 

// enable cache, disable security
runSomeLogic(person, CACHE); 

// enable cache and security
runSomeLogic(person, CACHE | SECURITY);

Inconvénients :

  • On pourrait passer n’importe quel int à la place des constantes, par exemple -13
  • Le nombre de constantes que l’on peut passer de cette manière est limité à la taille du type utilisé (32 pour un int)

 

Solution 4 : utiliser une seule enum et un argument de taille variable

C’est la version « moderne » de la solution 3

enum Options {
     CACHE,
     SECURITY
}

En considérant que la signature de la méthode utilise l’argument de taille variable (Java 5 et +)

void runSomeLogic(Person person, Options… options)

On peut alors activer simultanément plusieurs fonctionnalités

// disable security, disable cache
runSomeLogic(person); 

// enable cache, disable security
runSomeLogic(person, Options.CACHE); 

// enable cache and security
runSomeLogic(person, Options.CACHE, Options.SECURITY);

Inconvénients :

  • Il ne peut y avoir qu’un seul argument de taille variable par signature de méthode
  • L’argument de taille variable doit toujours être le dernier des arguments dans la signature de la méthode

 

Sources

Cet article est une synthèse de celui de Cédric Beust (développeur de TestNG et JCommander entre autres) : http://beust.com/weblog/2013/07/17/branching-logic/

Tagué ,