Perles de Mongueurs

GNU/Linux Magazine n° 126 | avril 2010 | Philippe bruhat
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Depuis le numéro 59, les Mongueurs de Perl vous proposent tous les mois de découvrir les scripts jetables qu’ils ont pu coder ou découvrir dans leur utilisation quotidienne de Perl. Bref, des choses trop courtes pour en faire un article, mais suffisamment intéressantes pour mériter d’être publiées. Ce sont les perles de Mongueurs.

1. Recycler ses calendriers

Nous achetons chaque année de grands calendriers avec photos pour décorer notre appartement. Certaines photos sont si jolies qu'on regrette de ne pouvoir les contempler qu'un mois.

Si on ne tient pas compte de l'année, il est possible de réutiliser un calendrier, pourvu que les jours se correspondent. Par exemple, en 2009, nous pouvions utiliser un calendrier de 1998, comme nous le montre la commande cal(1) :

        janvier 1998                  janvier 2009    

    lu ma me je ve sa di          lu ma me je ve sa di

              1 2 3 4                    1 2 3 4

     5 6 7 8 9 10 11           5 6 7 8 9 10 11

    12 13 14 15 16 17 18          12 13 14 15 16 17 18

    19 20 21 22 23 24 25          19 20 21 22 23 24 25

    26 27 28 29 30 31             26 27 28 29 30 31   

Les critères de « recyclage » sont les suivants :

  • le 1er janvier doit commencer le même jour ;
  • les deux années doivent être toutes deux bissextiles ou non.

La relation d'équivalence que nous venons de définir entre deux années « échangeables » nous permet de définir des classes d'équivalences dans lesquelles nous allons pouvoir classer les années.

Il est facile de calculer qu'il y a 14 classes d'équivalence, en multipliant le nombre de jours de la semaine (7) par le nombre de possibilités d'être bissextile ou non (2).

Pour déterminer l'appartenance d'une année à une classe d'équivalence, il suffit de calculer une simple « somme de contrôle ». Deux années ayant la même somme de contrôle sont échangeables.

Si on prend un objet DateTime pour représenter le premier janvier de l'année en question, deux formules sont utilisables :

  • 7 * $dt->is_leap_year + $dt->day_of_week
  • 2 * $dt->day_of_week + $dt->is_leap_year

Même si les deux calculs ne donnent pas le même résultat pour une année donnée, ils définissent les mêmes classes d'équivalence (le résultat sera identique pour toutes les années équivalentes).

Pour savoir si l'année est bissextile, le calcul sera différent selon la formule choisie. Avec la première formule, la comparaison du résultat avec 7 ou 8 nous permet de savoir si l'année est bissextile : de 1 à 7, c'est une année normale, de 8 à 15, c'est une année bissextile.

Avec la seconde formule, la parité du résultat nous permet de savoir si la classe d'équivalence correspond à une année bissextile ou non.

    use strict;

    use warnings;

    use DateTime;

    my %classe;

# range chaque année dans sa classe d'équivalence

    for my $year ( 1970 .. 2038 ) {

        # calcule la somme de contrôle de l'année courante

        my $dt = DateTime->new( year => $year, month => 1, day => 1 );

        my $type = 2 * $dt->day_of_week + $dt->is_leap_year;

# ajoute l'année à sa classe d'équivalence

        push @{ $classe{$type} }, $year;

}

    # affiche les différentes classes d'équivalence

    # triées par la première année qu'elles contiennent

    # préfixe les années bissextiles d'une étoile

    print $_ % 2 ? '*' : ' ', " @{ $classe{$_} }\n"

        for sort { $classe{$a}[0] <=> $classe{$b}[0] } keys %classe;

Et voici le résultat pour une plage de dates « classique » :

      1970 1981 1987 1998 2009 2015 2026 2037

      1971 1982 1993 1999 2010 2021 2027 2038

    * 1972 2000 2028

      1973 1979 1990 2001 2007 2018 2029 2035

      1974 1985 1991 2002 2013 2019 2030

      1975 1986 1997 2003 2014 2025 2031

    * 1976 2004 2032

      1977 1983 1994 2005 2011 2022 2033

      1978 1989 1995 2006 2017 2023 2034

    * 1980 2008 2036

    * 1984 2012

    * 1988 2016

    * 1992 2020

    * 1996 2024

On peut facilement vérifier qu'on obtient le même résultat avec chacune des deux formules (sauf pour l'étoile qui marque les années bissextiles, bien sûr).

Évidemment, les années bissextiles ne se produisent pas très souvent. On peut donc essayer de tourner avec moins de calendriers en changeant de calendrier après le 29 février.

Les années bissextiles, le 29 février sera un jour imaginaire (au sens où il ne sera pas inscrit sur le calendrier) et on changera de calendrier le premier mars.

Nous aurons donc besoin de deux nouvelles relations d'équivalences, qui ne tiennent pas compte du caractère bissextile d'une année :

  • les années pour lesquelles le premier janvier est le même jour de la semaine ;
  • les années pour lesquelles le premier mars est le même jour de la semaine.

    use strict;

    use warnings;

    use DateTime;

    my %classe;

my (%janvier, %mars);

    # range chaque année dans sa classe d'équivalence

    for my $year ( 1970 .. 2038 ) {

        # calcule la somme de contrôle de l'année courante

        my $dt = DateTime->new( year => $year, month => 1, day => 1 );

        my $type = 7 * $dt->is_leap_year + $dt->day_of_week;

# ajoute l'année à sa classe d'équivalence

        push @{ $classe{$type} }, $year;

        # classe de janvier

        push @{ $janvier{ $dt->day_of_week } }, $year;

# classe de mars

        $dt->set_month(3);

        push @{ $mars{ $dt->day_of_week } }, $year;

}

    # affiche les différente classes d'équivalence

    # triées par la première année qu'elles contiennent

    for ( sort { $classe{$a}[0] <=> $classe{$b}[0] } keys %classe ) {

print "@{ $classe{$_} }\n";

        my $dt = DateTime->new( year => $classe{$_}[0], month => 1, day => 1 );

        

        if( $dt->is_leap_year) {

            print " Jan: @{ $janvier{ $dt->day_of_week } }\n";

            $dt->set_month(3);

            print " Mar: @{ $mars{ $dt->day_of_week } }\n";

}

    }

On obtient les mêmes résultats pour les années normales, avec des solutions de substitutions pour les années bissextiles :

    1970 1981 1987 1998 2009 2015 2026 2037

    1971 1982 1993 1999 2010 2021 2027 2038

    1972 2000 2028

      Jan: 1972 1977 1983 1994 2000 2005 2011 2022 2028 2033

      Mar: 1972 1978 1989 1995 2000 2006 2017 2023 2028 2034

    1973 1979 1990 2001 2007 2018 2029 2035

    1974 1985 1991 2002 2013 2019 2030

    1975 1986 1997 2003 2014 2025 2031

    1976 2004 2032

      Jan: 1970 1976 1981 1987 1998 2004 2009 2015 2026 2032 2037

      Mar: 1971 1976 1982 1993 1999 2004 2010 2021 2027 2032 2038

    1977 1983 1994 2005 2011 2022 2033

    1978 1989 1995 2006 2017 2023 2034

    1980 2008 2036

      Jan: 1974 1980 1985 1991 2002 2008 2013 2019 2030 2036

      Mar: 1975 1980 1986 1997 2003 2008 2014 2025 2031 2036

    1984 2012

      Jan: 1978 1984 1989 1995 2006 2012 2017 2023 2034

      Mar: 1973 1979 1984 1990 2001 2007 2012 2018 2029 2035

    1988 2016

      Jan: 1971 1982 1988 1993 1999 2010 2016 2021 2027 2038

      Mar: 1977 1983 1988 1994 2005 2011 2016 2022 2033

    1992 2020

      Jan: 1975 1986 1992 1997 2003 2014 2020 2025 2031

      Mar: 1970 1981 1987 1992 1998 2009 2015 2020 2026 2037

1996 2024

      Jan: 1973 1979 1990 1996 2001 2007 2018 2024 2029 2035

      Mar: 1974 1985 1991 1996 2002 2013 2019 2024 2030

2. Perl et le bug de 2038

Sous Unix, les dates sont stockées sous forme d'un nombre entier de secondes, comptées depuis l'epoch (date arbitrairement fixée à 1970-01-01 00:00:00 GMT). Sur les systèmes où cet entier est un entier signé de 32 bits, cela limite le nombre de secondes à 2 ** 31 - 1, soit 2 147 483 647 secondes. Autrement dit, le 19 janvier 2038 à 3 h 14 min 7 s temps universel, le système se croira le 13 décembre 1901.

Mais le problème ne se posera pas seulement dans un peu moins de trente ans. Beaucoup de monde utilise le système basé sur l'epoch pour représenter des dates dans le futur. Et il est des applications qui s'intéressent à des dates 30 ans dans le futur (au hasard, les prêts bancaires).

Les développeurs de Perl sont très attentifs à ce genre de problème de compatibilité. C'est pourquoi Michael Schwern a écrit le module Time::y2038, qui fournit des versions des fonctions internes de Perl (gmtime(), localtime(), timegm(), timelocal()) compatibles avec des dates post-2038.

Cependant, pour Time::y2038, les contraintes de réutilisation des dates sont un peu plus fortes, car le module doit tenir compte des fuseaux horaires, en particulier pour tenir compte des horaires d'été, très variables suivant les régions du monde.

Pour une année donnée après 2038, le module va sélectionner la dernière année correspondante dans le calendrier de 28 ans prédéfini. Pour que deux années se correspondent, il faut non seulement :

  • qu'elles commencent le même jour de la semaine ;
  • qu'elles aient toutes deux le même nombre de jour en février.

Mais il faut également :

  • que les années précédentes se correspondent, afin que lorsqu'on fait des calculs de date le premier janvier avec un fuseau horaire pré-UTC, le 31 décembre corresponde également ;
  • que l'année suivante commence le même jour de la semaine, quand on fait des calculs le 31 décembre avec un fuseau horaire post-UTC.

Dans le second cas, l'état bissextile ou non n'a pas d'importance, car on s'intéresse seulement au premier janvier. Ce mécanisme est suffisamment efficace pour permettre de travailler avec des dates allant jusqu'après 2400 (modulo les bases de locales qui ne seront pas à jour).

À noter que le code C derrière ce module Perl a été repris d'un code déjà existant, amélioré et retouché pour être rendu portable, permettant ainsi aux auteurs d'autres projets libres de le réutiliser librement.

Références

Page Wikipédia sur le bug de 2038 : http://fr.wikipedia.org/wiki/Bogue_de_l%27an_2038

Le module Time::y2038 : http://search.cpan.org/dist/Time-y2038/

Projet sur Google fournissant le code C : http://code.google.com/p/y2038/

Page collectant d'autres bugs similaires au bug de l'an 2000 et de 2038 : http://www.courtois.cc/humour/y2k.html

À vous !

Envoyez vos perles à perles@mongueurs.net, elles seront peut-être publiées dans un prochain numéro de Linux Magazine.