Come convertire da giorno dell'anno e anno a una data AAAAMMGG?


11

Sto cercando di passare dal giorno dell'anno (1-366) e l'anno (es. 2011) a una data nel formato AAAAMMGG?


1
In che giorno rappresenta il giorno 0? Normalmente è 1-366.
Mikel,

Risposte:


17

Questa funzione Bash funziona per me su un sistema basato su GNU:

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

Qualche esempio:

$ 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

Questa funzione considera il giorno zero di Julian l'ultimo giorno dell'anno precedente.

Ed ecco una funzione bash per sistemi basati su UNIX, come macOS:

jul () { (( $2 >=0 )) && local pre=+; date -v$pre$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }

2
Questa è una soluzione fantastica. Solo data GNU, ma molto più breve.
Mikel,

Non ho mai saputo che la data di GNU fosse così flessibile. Fantastico.
njd,

Che bella soluzione usando la data GNU. Se vuoi fare lo stesso su un sistema UNIX, come macOS, puoi usare:jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
mcantsin

1
@mcantsin: grazie per la versione per MacOS!
In pausa fino a nuovo avviso.

1
@mcantsin: ho modificato la tua versione in modo che funzioni con offset negativi.
In pausa fino a nuovo avviso.

4

Non si può fare solo in Bash, ma se hai 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;

1
In realtà sono abbastanza sicuro che il suo può essere fatto solo in Bash, anche se non così elegante (nel peggiore dei casi calcolare il timestamp formattandolo). Ma non sono di fronte a una macchina Linux per testarlo.
Bobby,

Bobby, potresti pensare al datecomando nei coreutils. L'ho controllato e supporta solo la formattazione della data corrente o l'impostazione su un nuovo valore, quindi non è un'opzione.
bandi

L'utilizzo del datecomando può essere eseguito. Vedi la mia risposta Ma il modo Perl è molto più semplice e veloce.
Mikel,

2
@bandi: A meno chedate -d
user1686

+1: Stavo usando questo - grazie, ma è apparso il metodo date -d sopra.
Umber Ferrule,

4

Esegui info 'Date input formats'per vedere quali formati sono consentiti.

Il formato data AAAA-GGD non sembra essere lì e ci prova

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

mostra che non funziona, quindi penso che njdsia corretto, il modo migliore è usare uno strumento esterno diverso da bashe date.

Se vuoi davvero usare solo gli strumenti bash e di base della riga di comando, puoi fare qualcosa del genere:

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
}

Ma è un modo piuttosto lento di farlo.

Un modo più veloce ma più complesso cerca di scorrere ogni mese, trova l'ultimo giorno in quel mese, quindi vede se il giorno giuliano è compreso in quell'intervallo.

# 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
}

last_day_of_month_jdaypotrebbe anche essere implementato usando ad esempio date -d "$yyyymm01 -1 day"(solo data GNU) o $(($(date +"%s" -d "$yyyymm01") - 86400)).
Mikel,

Bash può fare decremento in questo modo: ((day--)). Bash ha forloop come questo for ((m=1; m<=12; m++))(non è necessario seq). È abbastanza sicuro supporre che le shell che hanno alcune delle altre funzionalità che stai usando abbiano local.
In pausa fino a nuovo avviso.

IIRC local non è specificato da POSIX, ma assolutamente, se l'utilizzo di ksh ha la composizione, zsh ha local e zsh ha dichiarato IIRC. Penso che la composizione funzioni in tutti e 3, ma non funziona in ash / dash. Tendo a sottoutilizzare lo stile ksh per. Grazie per i pensieri
Mikel,

@Mikel: Dash ha localcome BusyBox ash.
In pausa fino a nuovo avviso.

Sì, ma non composto IIRC. Tutti gli altri hanno composto. Se anche dash fosse stato composto, lo avrei usato nell'esempio.
Mikel,

1

Se il giorno dell'anno (1-366) è 149 e l'anno è 2014 ,

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

Assicurati di inserire il valore del giorno dell'anno -1 .


0

La mia soluzione in 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

0

Su un terminale POSIX:

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

Quindi chiama come

jul 2011 012
jul 2017 216
jul 2100 60

0

Mi rendo conto che questo è stato chiesto anni fa, ma nessuna delle risposte che vedo sono ciò che considero BASH puro poiché usano tutti GNU date. Pensavo che mi sarei preso una cazzata nel rispondere ... Ma ti avverto in anticipo, la risposta non è elegante, né è breve a oltre 100 righe. Potrebbe essere ridotto, ma volevo che gli altri vedessero facilmente cosa fa.

I "trucchi" principali qui sono capire se un anno è un anno bisestile (o meno) facendo dividere il MODULUS %dell'anno per 4, e quindi sommando i giorni di ogni mese, più un giorno in più per febbraio se necessario , usando una semplice tabella di valori.

Non esitate a commentare e offrire suggerimenti su come farlo meglio, dal momento che sono principalmente qui per imparare di più me stesso, e mi considero al massimo un principiante BASH. Ho fatto del mio meglio per renderlo portatile come so, e questo significa alcuni compromessi secondo me.

Sul codice ... spero che sia abbastanza autoesplicativo.

#!/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"

a proposito, l'attuale calcolo dell'anno bisestile è un po 'più complesso di "è divisibile per 4".
Erich,

@erich - Hai ragione, ma nel giro di anni consentito come valido da questo script (1901-2099), non si verificheranno situazioni che non funzionano. Non è terribilmente difficile aggiungere un "o" test per affrontare anni che sono uniformemente divisibili per 100 ma non uniformemente divisibili per 400 per coprire quei casi se l'utente ha bisogno di estenderlo, ma non pensavo fosse davvero necessario in questo caso. Forse era miope?
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

matematicamente rappresentato di seguito

[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.