Comment convertir le jour de l'année et l'année en une date AAAAMMJJ?

11

Je souhaite passer du jour de l'année (1-366) et de l'année (par exemple 2011) à une date au format AAAAMMJJ?

Virole d'ombre
la source
1
Quel jour le jour 0 représente-t-il? Normalement, c'est 1-366.
Mikel

Réponses:

17

Cette fonction Bash fonctionne pour moi sur un système basé sur GNU:

jul () { date -d "$1-01-01 +$2 days -1 day" "+%Y%m%d"; }

Quelques exemples:

$ y=2011; od=0; for d in {-4..4} 59 60 {364..366} 425 426; do (( d > od + 1)) && echo; printf "%3s " $d; jul $y $d; od=$d; done
 -4 20101227
 -3 20101228
 -2 20101229
 -1 20101230
  0 20101231
  1 20110101
  2 20110102
  3 20110103
  4 20110104

 59 20110228
 60 20110301

364 20111230
365 20111231
366 20120101

425 20120229
426 20120301

Cette fonction considère le jour julien zéro comme le dernier jour de l'année précédente.

Et voici une fonction bash pour les systèmes basés sur UNIX, tels que macOS:

jul () { (( $2 >=0 )) && local pre=+; date -v$pre$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
En pause jusqu'à nouvel ordre.
la source
2
C'est une solution géniale. Date GNU uniquement, mais beaucoup plus courte.
Mikel
Je n'ai jamais su que la date GNU était aussi flexible. Fantastique.
2011
Quelle belle solution en utilisant la date GNU. Dans le cas où vous souhaitez faire de même sur un système UNIX, tel que macOS, vous pouvez utiliser:jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
mcantsin
1
@mcantsin: Merci pour la version MacOS!
pause jusqu'à nouvel ordre.
1
@mcantsin: J'ai modifié votre version pour qu'elle fonctionne avec des décalages négatifs.
pause jusqu'à nouvel ordre.
4

Ne peut pas être fait en Bash, mais si vous avez Perl:

use POSIX;

my ($jday, $year) = (100, 2011);

# Unix time in seconds since Jan 1st 1970
my $time = mktime(0,0,0, $jday, 0, $year-1900);

# same thing as a list that we can use for date/time formatting
my @tm = localtime $time;

my $yyyymmdd = strftime "%Y%m%d", @tm;
njd
la source
1
Je suis en fait assez sûr que cela peut être fait dans Bash uniquement, mais pas si élégant (pire cas de calcul de l'horodatage en le formatant). Mais je ne suis pas devant une machine Linux pour le tester.
Bobby
Bobby, vous pourriez penser à la datecommande dans les coreutils. Je l'ai vérifié et il ne prend en charge que le formatage de la date actuelle ou la définition d'une nouvelle valeur, ce n'est donc pas une option.
bandi
L'utilisation de la datecommande peut être effectuée. Voir ma réponse. Mais la méthode Perl est beaucoup plus facile et plus rapide.
Mikel
2
@bandi: Saufdate -d
user1686
+1: J'utilisais ceci - merci, mais la méthode date -d ci-dessus est apparue.
Umber Ferrule
4

Exécutez info 'Date input formats'pour voir quels formats sont autorisés.

Le format de date AAAA-JJ ne semble pas être là, et essayer

$ date -d '2011-011'
date: invalid date `2011-011'

montre que cela ne fonctionne pas, donc je pense que njdc'est correct, la meilleure façon est d'utiliser un outil externe autre que bashet date.

Si vous voulez vraiment utiliser uniquement les outils bash et de ligne de commande de base, vous pouvez faire quelque chose comme ceci:

julian_date_to_yyyymmdd()
{
    date=$1    # assume all dates are in YYYYMMM format
    year=${date%???}
    jday=${date#$year}
    for m in `seq 1 12`; do
        for d in `seq 1 31`; do
            yyyymmdd=$(printf "%d%02d%02d" $year $m $d)
            j=$(date +"%j" -d "$yyyymmdd" 2>/dev/null)
            if test "$jday" = "$j"; then
                echo "$yyyymmdd"
                return 0
            fi
        done
    done
    echo "Invalid date" >&2
    return 1
}

Mais c'est une façon assez lente de le faire.

Une manière plus rapide mais plus complexe essaie de parcourir chaque mois, trouve le dernier jour de ce mois, puis voit si le jour julien est dans cette plage.

# year_month_day_to_jday <year> <month> <day> => <jday>
# returns 0 if date is valid, non-zero otherwise
# year_month_day_to_jday 2011 2 1 => 32
# year_month_day_to_jday 2011 1 32 => error
year_month_day_to_jday()
{
    # XXX use local or typeset if your shell supports it
    _s=$(printf "%d%02d%02d" "$1" "$2" "$3")
    date +"%j" -d "$_s"
}

# last_day_of_month_jday <year> <month>
# last_day_of_month_jday 2011 2 => 59
last_day_of_month_jday()
{
    # XXX use local or typeset if you have it
    _year=$1
    _month=$2
    _day=31

    # GNU date exits with 0 if day is valid, non-0 if invalid
    # try counting down from 31 until we find the first valid date
    while test $_day -gt 0; do
        if _jday=$(year_month_day_to_jday $_year $_month $_day 2>/dev/null); then
            echo "$_jday"
            return 0
        fi
        _day=$((_day - 1))
    done
    echo "Invalid date" >&2
    return 1
}

# first_day_of_month_jday <year> <month>
# first_day_of_month_jday 2011 2 => 32
first_day_of_month_jday()
{
    # XXX use local or typeset if you have it
    _year=$1
    _month=$2
    _day=1

    if _jday=$(year_month_day_to_jday $_year $_month 1); then
        echo "$_jday"
        return 0
    else
        echo "Invalid date" >&2
        return 1
    fi
}

# julian_date_to_yyyymmdd <julian day> <4-digit year>
# e.g. julian_date_to_yyyymmdd 32 2011 => 20110201
julian_date_to_yyyymmdd()
{
    jday=$1
    year=$2

    for m in $(seq 1 12); do
        endjday=$(last_day_of_month_jday $year $m)
        if test $jday -le $endjday; then
            startjday=$(first_day_of_month_jday $year $m)
            d=$((jday - startjday + 1))
            printf "%d%02d%02d\n" $year $m $d
            return 0
        fi
    done
    echo "Invalid date" >&2
    return 1
}
Mikel
la source
last_day_of_month_jdaypourrait également être implémenté en utilisant par exemple date -d "$yyyymm01 -1 day"(date GNU uniquement) ou $(($(date +"%s" -d "$yyyymm01") - 86400)).
Mikel
Bash peut faire décrément comme ceci: ((day--)). Bash a des forboucles comme ça for ((m=1; m<=12; m++))(pas besoin seq). Il est assez sûr de supposer que les shells qui ont certaines des autres fonctionnalités que vous utilisez ont local.
pause jusqu'à nouvel ordre.
IIRC local n'est pas spécifié par POSIX, mais absolument, si vous utilisez ksh a typeset, zsh a local et zsh a déclaré IIRC. Je pense que la composition fonctionne dans les 3, mais ne fonctionne pas dans ash / dash. J'ai tendance à sous-utiliser le style ksh. Merci pour les pensées.
Mikel
@Mikel: Dash a localcomme BusyBox ash.
pause jusqu'à nouvel ordre.
Oui, mais pas composé IIRC. Tous les autres ont une composition. Si dash avait également été composé, je l'aurais utilisé dans l'exemple.
Mikel
1

Si le jour de l'année (1-366) est 149 et l'année est 2014 ,

$ date -d "148 days 2014-01-01" +"%Y%m%d"
20140529

Assurez-vous de saisir la valeur du jour de l'année -1 .

justreturn
la source
0

Ma solution en bash

from_year=2013
from_day=362

to_year=2014
to_day=5

now=`date +"%Y/%m/%d" -d "$from_year/01/01 + $from_day days - 2 day"`
end=`date +"%Y/%m/%d" -d "$to_year/01/01 + $to_day days - 1 day"`


while [ "$now" != "$end" ] ; 
do
    now=`date +"%Y/%m/%d" -d "$now + 1 day"`;
    echo "$now";
    calc_day=`date -d "$now" +%G'.'%j`
    echo $calc_day
done
Seb
la source
0

Sur un terminal POSIX:

jul () { date -v$1y -v1m -v1d -v+$2d -v-1d "+%Y%m%d"; }

Ensuite, appelez comme

jul 2011 012
jul 2017 216
jul 2100 60
vpipkt
la source
0

Je me rends compte que cela a été demandé il y a longtemps, mais aucune des réponses que je vois n'est ce que je considère comme du BASH pur car ils utilisent tous GNU date. Je pensais que je tenterais de répondre ... Mais je vous préviens à l'avance, la réponse n'est pas élégante, ni courte à plus de 100 lignes. Il pourrait être réduit, mais je voulais que les autres voient facilement ce qu'il fait.

Les principaux "trucs" ici consistent à déterminer si une année est une année bissextile (ou non) en %divisant le MODULE de l'année par 4, puis en additionnant simplement les jours de chaque mois, plus un jour supplémentaire pour février si nécessaire , à l'aide d'un simple tableau de valeurs.

N'hésitez pas à commenter et à proposer des moyens de mieux faire, car je suis principalement ici pour en savoir plus moi-même, et je me considérerais au mieux comme un novice BASH. J'ai fait de mon mieux pour que ce soit aussi portable que je sais, et cela signifie à mon avis certains compromis.

Passons au code ... J'espère qu'il est assez explicite.

#!/bin/sh

# Given a julian day of the year and a year as ddd yyyy, return
# the values converted to yyyymmdd format using ONLY bash:

ddd=$1
yyyy=$2
if [ "$ddd" = "" ] || [ "$yyyy" = "" ]
then
  echo ""
  echo "   Usage: <command> 123 2016"
  echo ""
  echo " A valid julian day from 1 to 366 is required as the FIRST"
  echo " parameter after the command, and a valid 4-digit year from"
  echo " 1901 to 2099 is required as the SECOND. The command and each"
  echo " of the parameters must be separated from each other with a space."
  echo " Please try again."
  exit
fi
leap_yr=$(( yyyy % 4 ))
if [ $leap_yr -ne 0 ]
then
  leap_yr=0
else
  leap_yr=1
fi
last_doy=$(( leap_yr + 365 ))
while [ "$valid" != "TRUE" ]
do
  if [ 0 -lt "$ddd" ] && [ "$last_doy" -ge "$ddd" ]
  then
    valid="TRUE"
  else
    echo "   $ddd is an invalid julian day for the year given."
    echo "   Please try again with a number from 1 to $last_doy."
    exit    
  fi
done
valid=
while [ "$valid" != "TRUE" ]
do
  if [ 1901 -le "$yyyy" ] && [ 2099 -ge "$yyyy" ]
  then
    valid="TRUE"
  else
    echo "   $yyyy is an invalid year for this script."
    echo "   Please try again with a number from 1901 to 2099."
    exit    
  fi
done
if [ "$leap_yr" -eq 1 ]
then
  jan=31  feb=60  mar=91  apr=121 may=152 jun=182
  jul=213 aug=244 sep=274 oct=305 nov=335
else
  jan=31  feb=59  mar=90  apr=120 may=151 jun=181
  jul=212 aug=243 sep=273 oct=304 nov=334
fi
if [ "$ddd" -gt $nov ]
then
  mm="12"
  dd=$(( ddd - nov ))
elif [ "$ddd" -gt $oct ]
then
  mm="11"
  dd=$(( ddd - oct ))
elif [ "$ddd" -gt $sep ]
then
  mm="10"
  dd=$(( ddd - sep ))
elif [ "$ddd" -gt $aug ]
then
  mm="09"
  dd=$(( ddd - aug ))
elif [ "$ddd" -gt $jul ]
then
  mm="08"
  dd=$(( ddd - jul ))
elif [ "$ddd" -gt $jun ]
then
  mm="07"
  dd=$(( ddd - jun ))
elif [ "$ddd" -gt $may ]
then
  mm="06"
  dd=$(( ddd - may ))
elif [ "$ddd" -gt $apr ]
then
  mm="05"
  dd=$(( ddd - apr ))
elif [ "$ddd" -gt $mar ]
then
  mm="04"
  dd=$(( ddd - mar ))
elif [ "$ddd" -gt $feb ]
then
  mm="03"
  dd=$(( ddd - feb ))
elif [ "$ddd" -gt $jan ]
then
  mm="02"
  dd=$(( ddd - jan ))
else
  mm="01"
  dd="$ddd"
fi
if [ ${#dd} -eq 1 ]
then
  dd="0$dd"
fi
if [ ${#yyyy} -lt 4 ]
then
  until [ ${#yyyy} -eq 4 ]
  do
    yyyy="0$yyyy"
  done
fi
printf '\n   %s%s%s\n\n' "$yyyy" "$mm" "$dd"
Dave Lydick
la source
fyi, le calcul réel de l'année bissextile est un peu plus complexe que "est divisible par 4".
Erich
@erich - Vous avez raison, mais dans la plage d'années autorisée comme valide par ce script (1901-2099), les situations qui ne fonctionnent pas ne se produiront pas. Il n'est pas très difficile d'ajouter un test "ou" pour traiter les années qui sont divisibles par 100 mais pas divisibles par 400 pour couvrir ces cas si l'utilisateur a besoin de prolonger cela, mais je ne pensais pas que c'était vraiment nécessaire dans ce cas. Peut-être que c'était une myopie?
Dave Lydick
0
OLD_JULIAN_VAR=$(date -u -d 1840-12-31 +%s)

TODAY_DATE=`date --date="$odate" +"%Y-%m-%d"`
TODAY_DATE_VAR=`date -u -d "$TODAY_DATE" +"%s"`
export JULIAN_DATE=$((((TODAY_DATE_VAR - OLD_JULIAN_VAR))/86400))
echo $JULIAN_DATE

représenté mathématiquement ci-dessous

[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)
user997434
la source