Contenu | Rechercher | Menus

Annonce

Si vous avez des soucis pour rester connecté, déconnectez-vous puis reconnectez-vous depuis ce lien en cochant la case
Me connecter automatiquement lors de mes prochaines visites.

À propos de l'équipe du forum.

#1 Le 31/01/2007, à 18:27

MistaB

[Astuce] shell : Difference dates, calcul sur date

Bonjour à tous,

J'ai galéré suffisamment longtemps pour trouver un moyen de faire une seule boucle permettant de retrouver toutes les dates d'une seule année
On utilise pour cela le jour de l'année (de 1 à 365 ou 366)
Le problème, c'est que le système de date avec le shell bash ne permet pas de retrouver une date à partir du jour de l'année (et de l'année en question), ni de faire des différences entre dates pour retrouver un jour donné (ex : combien y-a-t'il de jours entre le 1er mars 2007 et le 16 avril 2005?)

Plus basique encore
Pour retrouver des fichiers notés selon la date (20070101, pour le premier janvier 2007, par exemple) et les lire avec une boucle, il faut généralement faire :

for MM in 01 02 03 04 05 06 07 08 09 10 11 12
do
    mois=$MM
    for jj = 01 02 03 ... 31
    do
       ech $jj
    done
done

Et encore, il faut rajouter des test avec "case" ou "if", pour retrouver attribuer le juste nombre de jours dans le mois, et en plus, faire des tests pour voir si l'année est bisextile ou non. Bref, une batterie de test pour faire une simple boucle permettant de retrouver chaque jour de l'année et le transformer en format date.

J'ai donc pris mon courage à deux mains, et j'ai fouiller sur les forums divers et variés, souvent anglophones, pour trouver finalement qu'il fallait la plupart du temps éditer des fonctions complexes...ce que j'ai fait.
Et maintenant que je me suis cassé le derch, j'aimerais que tout le monde puisse en profiter, pour ne pas avoir à galérer comme je l'ai fait! Alors j'espère que cela vous aidera.

Tout d'abord, sur certains shells (pas celui que j'ai sur ma edgy), on peut avoir n'importe quelle date très facilement, pour une année donnée, à partir du seul jour de l'année, en feintant la fonction "date":

an=2000
jour_annee=12
date -d $an-1-$jour "+%d-%m-%Y"
12-01-2000
# ...normal, le 12ème jour de l'année, c'est le 12 janvier!

#...mais là...attention!
jour_annee=366
date -d $an-1-$jour "+%d-%m-%Y"
30-12-2000
#...eh oui! le 366ème jour du mois de janvier 2000 (année bisextile),
# c'est bien le 30 décembre 2000!

Donc, quand ce truc marche, on fait une boucle de 1 à 366 (for jj in `seq 1 366`), et on retrouve n'importe quelle date, avec le format désiré, à partir du seul jour de l'année et de l'année voulue.
Et pour les années bisextiles?
Et bien il suffit de faire un test genre :
Si l'année de la date correspondant au jour 366 est différente de l'année que l'on traite, alors l'année n'est pas bisextile! (bon je sais, c'est je ne suis pô très clair, mais je voudrais aller au bout de ce post...)

ZE PROBLEME, c'est que cette astuce ne marche pas avec la fonction date du shell que j'ai sur ma edgy (je pense que c'est la nouvelle version de la fonction date qui a dû exclure cette possibilité, mais comme je ne m'y connais pas du tout entre bash ksh et cie, je ne m'aventurerai pas à hypothèser des idioties...)

Donc...comment qu'on fait?
Et bien on écrit des fonctions permettant de retrouver le jour julien d'une date, de faire des différences entre les dates, etc...on programme, quoi!

Un petit apparté sur les jours juliens : il s'agit de numéro de jours, des nombres entiers, que l'on attribue à chaque date. Ce n'est pas la même chose que le jour de l'année.
Par exemple, pour le 1er janvier 2007, le jour julien est 2454102.
Par contre, le jour de l'année est 1. Cependant, une fois que l'on a ce jour julien, il suffit de faire la soustraction jour julien de la date voulue avec le jour julien du premier janvier de la même année, et d'ajouter 1, pour obtenir le jour de l'année...

Voici une série de fonctions que j'ai adapté et reprogrammé à partir de celles fournies par les url :
http://aa.usno.navy.mil/faq/docs/JD_Formula.html
http://www.samag.com/documents/s=8284/s … /0307b.htm
et qui permettent de manipuler les dates:

get_astro_JD() :
  Calcule et indique le jour julien en fonction du jour dans le mois, du mois et de l'année

get_astro_YEAR() :
  Calcule et indique l'année correspondant à un jour julien donné

get_astro_MONTH() :
  Calcule et indique le mois correspondant à un jour julien donné

get_astro_DAY() :
  Calcule et indique le jour dans le mois (1 à 31 ou 30 ou 28 ou 29 selon le mois) correspondant à un jour julien donné

get_astro_DATE() :
  Calcule et indique (au format YYYY-mm-dd , c'est-à-dire année-mois-jour) la date correspondant à un jour julien donné

diff_date() :
  Calcule et indique le nombre de jours de différence entre deux dates données (au format YYYY-mm-dd)

get_date_from_DOY_YEAR() :
  Calcule et indique une date (au format YYYY-mm-dd) à partir du jour de l'année (1 à 365 ou 366) et de l'année (format YYYY, à quatre chiffres) donnés...correspond à la première astuce ci-dessus

Les voici, donc (NB : les explications sont en anglais, par soucis d'universalité, des fois que des anglophones soient dirigés sur ce poste via un moteur de recherche)
Les fonctions diff_date() et get_date_from_DOY_YEAR() ont été totalement crées "maison"

# This function returns the Astro-Julian Date.  The Julian 
# date (JD) is a continuous count of days from 1 January 4713 BC. 
# The following algorithm is good from years 1801 to 2099.
# See URL: 
# http://aa.usno.navy.mil/faq/docs/JD_Formula.html 
# for more information
# arguments: $1 = day, $2 = month, $3 = year in format YYYY
get_astro_JD()
{
typeset -i JDD

JDD=$(($1-32075+1461*($3+4800+($2-14)/12)/4+367*($2-2-($2-14)/12*12)/12-3*(($3+4900+($2-14)/12)/100)/4))
echo $JDD
}


# This function gives returns the gregorian YEAR from a Julian date
# The following algorithm is good from years 1801 to 2099.
# See URL: 
# http://aa.usno.navy.mil/faq/docs/JD_Formula.html 
# for more information
# argument : $1 = julian date (e.g. from get_astro_JD() function)
get_astro_YEAR()
{
typeset -i MONTH
typeset -i YEAR
typeset -i DAY
typeset -i varL
typeset -i varN

      varL=$(($1+68569))
      varN=$((4*$varL/146097))
      varL=$(($varL-(146097*$varN+3)/4))
      YEAR=$((4000*($varL+1)/1461001))
      varL=$(($varL-1461*$YEAR/4+31))
      MONTH=$((80*$varL/2447))
      DAY=$(($varL-2447*$MONTH/80))
      varL=$(($MONTH/11))
      MONTH=$(($MONTH+2-12*$varL))
      YEAR=$((100*($varN-49)+$YEAR+$varL))
     
echo $YEAR
}



# This function gives returns the gregorian MONTH from a Julian date
# The following algorithm is good from years 1801 to 2099.
# See URL: 
# http://aa.usno.navy.mil/faq/docs/JD_Formula.html 
# for more information
# argument : $1 = julian date (e.g. from get_astro_JD() function)
get_astro_MONTH()
{
typeset -i MONTH
typeset -i YEAR
typeset -i DAY
typeset -i varL
typeset -i varN

      varL=$(($1+68569))
      varN=$((4*$varL/146097))
      varL=$(($varL-(146097*$varN+3)/4))
      YEAR=$((4000*($varL+1)/1461001))
      varL=$(($varL-1461*$YEAR/4+31))
      MONTH=$((80*$varL/2447))
      DAY=$(($varL-2447*$MONTH/80))
      varL=$(($MONTH/11))
      MONTH=$(($MONTH+2-12*$varL))
      YEAR=$((100*($varN-49)+$YEAR+$varL))
     
echo $MONTH
}

# This function gives returns the DAY OF THE MONTH of a gregorian calendar date from a Julian date
# The following algorithm is good from years 1801 to 2099.
# See URL: 
# http://aa.usno.navy.mil/faq/docs/JD_Formula.html 
# for more information
# argument : $1 = julian date (e.g. from get_astro_JD() function)
get_astro_DAY()
{
typeset -i MONTH
typeset -i YEAR
typeset -i DAY
typeset -i varL
typeset -i varN

      varL=$(($1+68569))
      varN=$((4*$varL/146097))
      varL=$(($varL-(146097*$varN+3)/4))
      YEAR=$((4000*($varL+1)/1461001))
      varL=$(($varL-1461*$YEAR/4+31))
      MONTH=$((80*$varL/2447))
      DAY=$(($varL-2447*$MONTH/80))
      varL=$(($MONTH/11))
      MONTH=$(($MONTH+2-12*$varL))
      YEAR=$((100*($varN-49)+$YEAR+$varL))
     
echo $DAY
}

# This function gives returns the gregorian DATE (format YYYY-mm-dd) from a Julian date
# The following algorithm is good from years 1801 to 2099.
# See URL: 
# http://aa.usno.navy.mil/faq/docs/JD_Formula.html 
# for more information
# argument : $1 = julian date (e.g. from get_astro_JD() function)
get_astro_DATE()
{
typeset -i MONTH
typeset -i YEAR
typeset -i DAY
typeset -i varL
typeset -i varN

      varL=$(($1+68569))
      varN=$((4*$varL/146097))
      varL=$(($varL-(146097*$varN+3)/4))
      YEAR=$((4000*($varL+1)/1461001))
      varL=$(($varL-1461*$YEAR/4+31))
      MONTH=$((80*$varL/2447))
      DAY=$(($varL-2447*$MONTH/80))
      varL=$(($MONTH/11))
      MONTH=$(($MONTH+2-12*$varL))
      YEAR=$((100*($varN-49)+$YEAR+$varL))
     
echo $YEAR'-'$MONTH'-'$DAY
}


# This function returns the difference in number of day between two dates
# Date format must be "%Y-%m-%d" (day and month with one or two digits, depending on the shell version used)
# arguments : $1 = the latest date, $2 = the earliest date
# example :
# echo $(diff_date 2002-31-12 2002-01-01)
# 365
# #bisextile year
# echo $(diff_date 2000-31-12 2000-01-01)
# 366
diff_date()
{
typeset -i JDD1
typeset -i JDD2
typeset -i JDIFF
typeset -i YEAR
typeset -i MONTH
typeset -i DAY

YEAR=`date -d $1 "+%Y"`
MONTH=`date -d $1 "+%m"`
DAY=`date -d $1 "+%d"`
JDD1=$(($DAY-32075+1461*($YEAR+4800+($MONTH-14)/12)/4+367*($MONTH-2-($MONTH-14)/12*12)/12-3*(($YEAR+4900+($MONTH-14)/12)/100)/4))

YEAR=`date -d $2 "+%Y"`
MONTH=`date -d $2 "+%m"`
DAY=`date -d $2 "+%d"`
JDD2=$(($DAY-32075+1461*($YEAR+4800+($MONTH-14)/12)/4+367*($MONTH-2-($MONTH-14)/12*12)/12-3*(($YEAR+4900+($MONTH-14)/12)/100)/4))

JDIFF=$(($JDD1-$JDD2+1))
echo $JDIFF
}

# This function returns the gregorian date from the day of the year (DOY, 1 to 366) 
# and the year number (format YYYY, i.e. with century)
# arguments : $1 = DOY, $2 = year
get_date_from_DOY_YEAR()
{
typeset -i JDD
typeset -i YEAR
typeset -i MONTH
typeset -i DAY
typeset -i varL
typeset -i varN

YEAR=$(($2))
MONTH=$((1))
DAY=$((1))
JDD=$(($DAY-32075+1461*($YEAR+4800+($MONTH-14)/12)/4+367*($MONTH-2-($MONTH-14)/12*12)/12-3*(($YEAR+4900+($MONTH-14)/12)/100)/4))

JDD=$(($JDD+$1-1))

varL=$(($JDD+68569))
varN=$((4*$varL/146097))
varL=$(($varL-(146097*$varN+3)/4))
YEAR=$((4000*($varL+1)/1461001))
varL=$(($varL-1461*$YEAR/4+31))
MONTH=$((80*$varL/2447))
DAY=$(($varL-2447*$MONTH/80))
varL=$(($MONTH/11))
MONTH=$(($MONTH+2-12*$varL))
YEAR=$((100*($varN-49)+$YEAR+$varL))

echo $YEAR'-'$MONTH'-'$DAY
}

Faites tourner!!!

J'espère que cela vous servira...
N'hésitez pas à simplifier les noms (certains sont un peu long)

N'hésitez pas à répondre à ce post si vous trouvez des bugs quelconque dans ces fonctions, ou si vous avez des suggestions.

Seul petit hic pou ma part...je ne m'y connais pas suffisament en linux pour lancer ces fonctions automatiquement au démarrage du shell (je les ai copié dans /etc/profile, mais rien n'y fait...).
Une idée?

Big Up...
...et merci pour l'entraide, merci à la communauté et aux développeurs (chapeau)
big_smile

MistaB

Dernière modification par MistaB (Le 01/02/2007, à 12:37)


-----------------------
euh....désolé!

Hors ligne

#2 Le 14/01/2011, à 03:34

nordinatueur

Re : [Astuce] shell : Difference dates, calcul sur date

Quasiment 4 ans après, je suis très heureux du résultat ! big_smile
Merci... Je vais ranger ma pelle !


Linux User #508094
Pour une meilleure coopération, utilisez des liens relatifs sur le forum !

Hors ligne

#3 Le 07/05/2013, à 18:06

ze-boulet

Re : [Astuce] shell : Difference dates, calcul sur date

Bonjour.

Voici une solution qui -a priori- ne fait appel qu'à des commandes ksh 'standard'.
Les dates considérées doivent être passées sous la forme aaaammjj.
Les limites du calendrier ne sont pas testées
(Il doit rester des variables qui font double emploi, elles ont été ajoutées pour faciliter la compréhension)
Si 1 seule date est renseignée, la date du jour sera considérée comme étant la seconde.
Le (ou les) paramètres peuvent être donné(s) en réponse à une question.
Il suffit de copier ce qui suit dans un fichier exécutable :

function chkd
{
set -A Mmx 0 31 28 31 30 31 30 31 31 30 31 30 31
(( D +=0 )) 2>/dev/null
[[ $? = 0 ]] || Do=n
[[ ${#D} = 8 ]] || Do=n
if   [[ $Do = y ]]
then
     typeset -L4 D8=$D
     (( D8 +=0 )) 2>/dev/null
     [[ ${#D8} = 4 ]] || Do=n
     if   [[ $Do = y ]]
     then
          typeset -R4 D4142=$D
          typeset -L2 D41=$D4142
          (( D41 +=0 )) 2>/dev/null
          [[ $? = 0 ]] || Do=n
          [[ $D41 -lt 1 || $D41 -ge ${#Mmx[*]} ]] && Do=n
          if   [[ $Do = y ]]
          then
               typeset -R2 D42=$D4142
               (( D42 +=0 )) 2>/dev/null
               [[ $? = 0 ]] || Do=n
               if   [[ $Do = y ]]
               then
                    D2=0
                    typeset -R2 yy=$D8
                    if   [[ $yy -eq "00" ]]
                    then
                         (( Modulo400=D8%400 ))
                         (( Modulo400 == 0 )) && D2=1
                    else
                         (( Modulo4=D8%4 ))
                         (( Modulo4 == 0 )) && D2=1
                    fi
                    D42l=${Mmx[$D41]}
                    (( D42l == 28 )) && (( D42l +=D2 ))
                    [[ $D42 -gt $D42l ]]  && Do=n
               fi
          fi
     fi
fi
R0=$D ; R1=$D8 ; R2=$D4142 ; R3=$D41 ; R4=$D42 ; R5=$D2
}
if   [[ $# -gt 2 ]]
then
     echo ""
     echo ""
     echo usage : $0 [yyyymmdd [yyyymmdd]]
     echo ""
     echo ""
     exit 1
fi

set -A Adj 0 0 31 59 90 120 151 181 212 243 273 304 334

if   [[ $# -gt 0 ]]
then
     dollar1=$1
else
     echo ""
     echo ""
     printf "please specify either one or two yyyymmdd date\(s\) :   "
     read dollar1 dollar2 Dust
fi

if   [[ $# = 2 ]]
then
     dollar2=$2
fi

[[ "$dollar2" = "" ]] && dollar2=$(date +%Y%m%d)

if   [[ $dollar1 -lt $dollar2 ]]
then
     Fm=$dollar1 ; To=$dollar2
elif [[ $dollar1 -gt $dollar2 ]]
then
     Fm=$dollar2 ; To=$dollar1
else
     echo no difference from $dollar1 to $dollar2
     exit
fi

Do=y
D=$dollar1
D=$Fm
chkd $D
if   [[ $Do = n ]]
then
     echo invalid from_date value \($Fm\)
     exit 1
fi
Fmy=$R1 ; Fmm=$R3 ; Fmd=$R4 ; Fm2=$D2
(( Fmy +=0 )) ; (( Fmm +=0 )) ; (( Fmd +=0 )) ; (( Fm2 +=0 ))

D=$dollar2
D=$To
chkd $D
if   [[ $Do = n ]]
then
     echo invalid from_date value \($To\)
     exit 1
fi
Toy=$R1 ; Tom=$R3 ; Tod=$R4 ; To2=$D2


Fmq=${Adj[$Fmm]}
(( Fmq += Fmd ))
[[ $Fmm -gt 2 ]] && (( Fmq += Fm2 ))

Toq=${Adj[$Tom]}
(( Toq += Tod ))
[[ $Tom -gt 2 ]] && (( Toq += To2 ))

if   [[ $Fmy = $Toy ]]
then
     FmTo=$(( Toq - Fmq ))
     echo ""
     echo the difference from $Fm to $To is $FmTo days
     echo ""
     exit
else
     Lqy=365
     Lqy=$(( Lqy += Fm2 ))
     FmTo=$(( Lqy -= Fmq ))
     (( Fmy += 1 ))
fi

while [[ $Fmy -lt $Toy ]]
do
     Fm2=0
     typeset -R2 yy=$Fmy
     if   [[ $yy -eq "00" ]]
     then
          typeset -L2 SS=$Fmy
          (( Modulo4=SS%4 ))
          (( Modulo4 == 0 )) && Fm2=1
     else
          (( Modulo4=Fmy%4 ))
          (( Modulo4 == 0 )) && Fm2=1
     fi
     Lqy=365
     FmTo=$(( FmTo += (Lqy + Fm2) ))
     (( Fmy += 1 ))
done

FmTo=$(( FmTo += Toq ))
echo ""
echo the difference from $Fm to $To is $FmTo days
echo ""

Hors ligne

#4 Le 19/03/2021, à 10:29

bef

Re : [Astuce] shell : Difference dates, calcul sur date

Merci MistaB, ton travail m'est très utile...

Hors ligne

#5 Le 19/03/2021, à 12:31

Nasman

Re : [Astuce] shell : Difference dates, calcul sur date

Tu as prévu les années divisibles par 4 et non bissextiles (comme 1700, 1800, 1900, 2100...) ?


PC fixe sous Bionic 64 bits et portable avec Focal 64 bits

Hors ligne