Jak przekonwertować z dnia roku na rok na datę RRRRMMDD?

11

Chcę przejść od dnia roku (1-366) i roku (np. 2011) do daty w formacie RRRRMMDD?

Umber Ferrule
źródło
1
Jaki dzień reprezentuje dzień 0? Zwykle jest to 1-366.
Mikel

Odpowiedzi:

17

Ta funkcja Bash działa dla mnie w systemie opartym na GNU:

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

Kilka przykładów:

$ 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

Ta funkcja uważa, że ​​dzień zero w Julii jest ostatnim dniem poprzedniego roku.

A oto funkcja bash dla systemów UNIX, takich jak macOS:

jul () { (( $2 >=0 )) && local pre=+; date -v$pre$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
Wstrzymano do odwołania.
źródło
2
To niesamowite rozwiązanie. GNU tylko data, ale o wiele krótsza.
Mikel
Nigdy nie wiedziałem, że data GNU była tak elastyczna. Fantastyczny.
njd
Cóż za miłe rozwiązanie wykorzystujące datę GNU. Jeśli chcesz zrobić to samo w systemie UNIX, takim jak macOS, możesz użyć:jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
mcantsin
1
@mcantsin: Dzięki za wersję MacOS!
Wstrzymano do odwołania.
1
@mcantsin: Zmodyfikowałem twoją wersję, aby działała z przesunięciami ujemnymi.
Wstrzymano do odwołania.
4

Nie można tego zrobić tylko w Bash, ale jeśli masz Perla:

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
źródło
1
Jestem właściwie pewien, że jego można zrobić tylko w Bash, choć nie w tak eleganckim (najgorszym przypadku, gdy formatuje to znacznik czasu). Ale nie jestem przed maszyną z Linuksem, aby to przetestować.
Bobby,
Bobby, możesz pomyśleć o datepoleceniu w coreutils. Sprawdziłem to i obsługuje tylko formatowanie bieżącej daty lub ustawienie jej na nową wartość, więc nie jest to opcja.
bandi
Używając datepolecenia można zrobić. Zobacz moją odpowiedź. Ale sposób Perla jest znacznie łatwiejszy i szybszy.
Mikel
2
@bandi: Chyba żedate -d
użytkownik1686
+1: Korzystałem z tego - dzięki, ale pojawiła się powyższa metoda date -d.
Umber Ferrule
4

Uruchom, info 'Date input formats'aby zobaczyć, jakie formaty są dozwolone.

Format daty RRRR-DDD nie wydaje się tam być i próbuje

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

pokazuje, że to nie działa, więc myślę, że njdjest poprawne, najlepszym sposobem jest użycie zewnętrznego narzędzia innego niż bashi date.

Jeśli naprawdę chcesz używać tylko bash i podstawowych narzędzi wiersza poleceń, możesz zrobić coś takiego:

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
}

Ale to dość powolny sposób.

Szybszy, ale bardziej złożony sposób próbuje zapętlać się w każdym miesiącu, znajduje ostatni dzień w tym miesiącu, a następnie sprawdza, czy dzień juliański mieści się w tym zakresie.

# 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
źródło
last_day_of_month_jdaymoże być również zaimplementowany przy użyciu np. date -d "$yyyymm01 -1 day"(tylko data GNU) lub $(($(date +"%s" -d "$yyyymm01") - 86400)).
Mikel
Bash ubytek może zrobić tak: ((day--)). Bash ma fortakie pętle for ((m=1; m<=12; m++))(nie ma takiej potrzeby seq). Można całkiem bezpiecznie założyć, że powłoki, które mają niektóre z innych używanych funkcji, mają local.
Wstrzymano do odwołania.
Lokalny IIRC nie jest określony przez POSIX, ale absolutnie, jeśli użycie ksh ma skład, zsh ma lokalne, a zsh deklaruje IIRC. Myślę, że skład działa we wszystkich 3, ale nie działa w popiołu / myślniku. Zwykle nie wykorzystuję stylu Ksh. Dziękuję za myśli.
Mikel
@Mikel: Dash ma, localpodobnie jak BusyBox ash.
Wstrzymano do odwołania.
Tak, ale nie skład IIRC. Wszystkie pozostałe mają skład. Gdyby kreska też miała skład, użyłbym tego w przykładzie.
Mikel
1

Jeśli dzień roku (1-366) to 149, a rok to 2014 ,

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

Pamiętaj o wpisaniu wartości dnia roku -1 .

justreturn
źródło
0

Moje rozwiązanie w 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
źródło
0

Na terminalu POSIX:

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

Więc zadzwoń jak

jul 2011 012
jul 2017 216
jul 2100 60
vpipkt
źródło
0

Zdaję sobie sprawę, że o to pytano wieki temu, ale żadna z odpowiedzi, które widzę, nie jest tym, co uważam za czystą PODSTAWĘ, ponieważ wszystkie używają GNU date. Pomyślałem, że odezwę się, odpowiadając ... Ale ostrzegam cię z wyprzedzeniem, odpowiedź nie jest elegancka, ani nie jest krótka na ponad 100 liniach. Można to ograniczyć, ale chciałem, aby inni mogli łatwo zobaczyć, co robi.

Najważniejsze „sztuczki” polegają na ustaleniu, czy rok jest rokiem przestępnym (czy nie), dzieląc MODULUS %roku na 4, a następnie dodając dni w każdym miesiącu oraz, w razie potrzeby, dodatkowy dzień za luty , przy użyciu prostej tabeli wartości.

Nie krępuj się, komentując i oferując sugestie, jak to zrobić lepiej, ponieważ przede wszystkim jestem tutaj, aby dowiedzieć się więcej, a ja uważam się za nowicjusza w BASH. Dałem z siebie wszystko, aby uczynić to tak przenośnym, jak wiem, a to oznacza, moim zdaniem, pewne kompromisy.

Do kodu ... Mam nadzieję, że jest to dość oczywiste.

#!/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
źródło
fyi, faktyczne obliczenie roku przestępnego jest nieco bardziej złożone niż „można podzielić przez 4”.
Erich
@erich - Masz rację, ale w przedziale lat dozwolonych przez ten skrypt (1901-2099) sytuacje, które nie działają, nie wystąpią. Dodanie testu „lub” do lat, które są równomiernie podzielne przez 100, ale nie równomiernie przez 400, nie jest strasznie trudne, aby objąć te przypadki, jeśli użytkownik chce to przedłużyć, ale nie czułem, aby było to naprawdę potrzebne ta sprawa. Może to był mój krótkowzroczność?
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

przedstawione matematycznie poniżej

[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)
użytkownik997434
źródło