Bagaimana cara mengonversi dari tahun ke tahun ke tanggal YYYYMMDD?

9

Saya ingin pergi dari hari tahun (1-366) dan tahun (mis. 2011) ke tanggal dalam format YYYYMMDD?

Umber Ferrule
sumber
1
Hari apa yang diwakili oleh hari 0? Biasanya 1-366.
Mikel

Jawaban:

16

Fungsi Bash ini berfungsi untuk saya di sistem berbasis GNU:

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

Beberapa contoh:

$ 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

Fungsi ini menganggap hari Julian nol sebagai hari terakhir tahun sebelumnya.

Dan di sini adalah fungsi bash untuk sistem berbasis UNIX, seperti macOS:

jul () { (( $2 >=0 )) && local pre=+; date -v$pre$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
Dennis Williamson
sumber
2
Itu solusi yang luar biasa. Hanya tanggal GNU, tetapi jauh lebih singkat.
Mikel
Saya tidak pernah tahu tanggal GNU sangat fleksibel. Fantastis.
njd
Solusi yang bagus menggunakan tanggal GNU. Jika Anda ingin melakukan hal yang sama pada sistem UNIX, seperti macOS, Anda dapat menggunakan:jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
mcantsin
1
@ mcantsin: Terima kasih untuk versi MacOS!
Dennis Williamson
1
@ mcantsin: Saya memodifikasi versi Anda sehingga berfungsi dengan offset negatif.
Dennis Williamson
4

Tidak bisa dilakukan hanya dalam Bash, tetapi jika Anda memiliki 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
sumber
1
Saya sebenarnya cukup yakin bahwa itu dapat dilakukan di Bash saja, meskipun tidak elegan (kasus terburuk menghitung timestamp memformatnya). Tapi saya tidak di depan mesin Linux untuk mengujinya.
Bobby
Bobby, Anda mungkin memikirkan dateperintah di coreutils. Saya memeriksanya dan hanya mendukung memformat tanggal saat ini atau mengaturnya ke nilai baru, jadi itu bukan pilihan.
bandi
Menggunakan dateperintah bisa dilakukan. Lihat jawaban saya. Namun cara Perl jauh lebih mudah dan lebih cepat.
Mikel
2
@bandi: Kecualidate -d
grawity
+1: Sedang menggunakan ini - terima kasih, tetapi metode tanggal -d di atas muncul.
Umber Ferrule
4

Jalankan info 'Date input formats'untuk melihat format apa yang diizinkan.

The YYYY-DDD format tanggal tampaknya tidak berada di sana, dan berusaha

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

menunjukkan itu tidak berfungsi, jadi saya pikir njditu benar, cara terbaik adalah menggunakan alat eksternal selain bashdan date.

Jika Anda benar-benar hanya ingin menggunakan bash dan alat-alat baris perintah dasar, Anda bisa melakukan sesuatu seperti ini:

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
}

Tapi itu cara yang sangat lambat untuk melakukannya.

Cara yang lebih cepat tetapi lebih kompleks mencoba untuk mengulang setiap bulan, menemukan hari terakhir di bulan itu, lalu melihat apakah hari Julian ada dalam kisaran itu.

# 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
sumber
last_day_of_month_jdayjuga dapat diimplementasikan menggunakan mis date -d "$yyyymm01 -1 day"(hanya tanggal GNU) atau $(($(date +"%s" -d "$yyyymm01") - 86400)).
Mikel
Bash bisa melakukan penurunan seperti ini: ((day--)). Bash memiliki forloop seperti ini for ((m=1; m<=12; m++))(tidak perlu untuk seq). Cukup aman untuk mengasumsikan bahwa cangkang yang memiliki beberapa fitur lain yang Anda gunakan miliki local.
Dennis Williamson
IIRC lokal tidak ditentukan oleh POSIX, tetapi tentu saja, jika menggunakan ksh memiliki typeset, zsh memiliki lokal, dan zsh telah mendeklarasikan IIRC. Saya pikir typeset bekerja di semua 3, tetapi tidak berfungsi di ash / dash. Saya cenderung kurang menggunakan gaya ksh untuk. Terima kasih atas pemikirannya.
Mikel
@Mikel: Dash memiliki localseperti halnya BusyBox ash.
Dennis Williamson
Ya, tetapi tidak mengeset IIRC. Semua yang lain memiliki setet. Jika dasbor memiliki typeset juga, saya akan menggunakannya dalam contoh.
Mikel
2

Jika hari dalam setahun (1-366) adalah 149 dan tahun adalah 2014 ,

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

Pastikan untuk memasukkan nilai hari tahun -1 .

justreturn
sumber
0

Solusi saya di 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
sumber
0

Di terminal POSIX:

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

Lalu panggil seperti

jul 2011 012
jul 2017 216
jul 2100 60
vpipkt
sumber
0

Saya menyadari ini telah ditanyakan berabad-abad yang lalu, tetapi tidak ada jawaban yang saya lihat yang saya anggap sebagai BASH murni karena mereka semua menggunakan GNU date. Saya pikir saya akan mencoba menjawab ... Tapi saya memperingatkan Anda sebelumnya, jawabannya tidak elegan, juga tidak pendek di lebih dari 100 baris. Itu bisa dikupas, tetapi saya ingin membiarkan orang lain dengan mudah melihat apa fungsinya.

"Trik" utama di sini adalah mencari tahu apakah satu tahun adalah tahun kabisat (atau tidak) dengan mendapatkan MODULUS %tahun tersebut dibagi dengan 4, dan kemudian menambahkan hari-hari di setiap bulan, ditambah hari ekstra untuk Februari jika diperlukan , menggunakan tabel nilai sederhana.

Jangan ragu untuk berkomentar dan menawarkan saran tentang cara untuk melakukan ini dengan lebih baik, karena saya terutama di sini untuk belajar lebih banyak sendiri, dan saya akan menganggap diri saya sebagai pemula BASH terbaik. Saya melakukan yang terbaik untuk menjadikan ini portabel seperti yang saya tahu caranya, dan itu berarti beberapa kompromi menurut pendapat saya.

Ke kode ... Saya harap ini cukup jelas.

#!/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
sumber
fyi, perhitungan tahun kabisat sebenarnya sedikit lebih kompleks daripada "habis dibagi 4".
Erich
@ erich - Anda benar, tetapi dalam rentang tahun yang diizinkan sah oleh skrip ini (1901-2099), situasi yang tidak berfungsi tidak akan terjadi. Tidak terlalu sulit untuk menambahkan tes "atau" untuk menangani tahun yang dapat dibagi rata dengan 100 tetapi tidak terbagi rata dengan 400 untuk menutup kasus-kasus tersebut jika pengguna perlu memperpanjang ini, tetapi saya tidak merasa itu benar-benar diperlukan dalam kasus ini. Mungkin itu pandangan saya yang pendek?
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

Secara matematis terwakili di bawah ini

[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)
pengguna997434
sumber