Làm cách nào để chuyển đổi từ ngày trong năm và năm sang ngày YYYYMMDD?


9

Tôi đang tìm cách đi từ ngày trong năm (1-366) và năm (ví dụ 2011) đến một ngày ở định dạng YYYYMMDD?


1
Ngày 0 đại diện cho ngày nào? Thông thường là 1-366.
Mikel

Câu trả lời:


16

Hàm Bash này hoạt động với tôi trên một hệ thống dựa trên GNU:

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

Vài ví dụ:

$ 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

Hàm này coi Julian day zero là ngày cuối cùng của năm trước.

Và đây là một hàm bash cho các hệ thống dựa trên UNIX, chẳng hạn như macOS:

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

2
Đó là một giải pháp tuyệt vời. Ngày GNU chỉ, nhưng ngắn hơn nhiều.
Mikel

Tôi không bao giờ biết ngày GNU là linh hoạt. Tuyệt vời.
njd

Thật là một giải pháp tốt khi sử dụng ngày GNU. Trong trường hợp bạn muốn làm tương tự trên một hệ thống UNIX, chẳng hạn như macOS, bạn có thể sử dụng:jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
mcantsin

1
@mcantsin: Cảm ơn phiên bản MacOS!
Dennis Williamson

1
@mcantsin: Tôi đã sửa đổi phiên bản của bạn để nó hoạt động với độ lệch âm.
Dennis Williamson

4

Không thể được thực hiện chỉ trong Bash, nhưng nếu bạn có 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
Tôi thực sự khá chắc chắn rằng anh ta chỉ có thể được thực hiện trong Bash, mặc dù không thanh lịch (trường hợp xấu nhất là tính toán định dạng dấu thời gian). Nhưng tôi không ở trước máy Linux để kiểm tra.
Bobby

Bobby, bạn có thể nghĩ về datelệnh trong coreutils. Tôi đã kiểm tra nó và nó chỉ hỗ trợ định dạng ngày hiện tại hoặc đặt nó thành một giá trị mới, vì vậy nó không phải là một tùy chọn.
bandi

Sử dụng datelệnh có thể được thực hiện. Xem câu trả lời của tôi. Nhưng cách Perl dễ dàng hơn và nhanh hơn nhiều.
Mikel

2
@bandi: Trừ khidate -d
grawity

+1: Đã sử dụng cái này - cảm ơn, nhưng phương thức date -d ở trên đã xuất hiện.
Umber Ferrule

4

Chạy info 'Date input formats'để xem định dạng nào được cho phép.

Các định dạng ngày YYYY-DDD dường như không có mặt ở đó, và cố gắng

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

cho thấy nó không hoạt động, vì vậy tôi nghĩ njdlà chính xác, cách tốt nhất là sử dụng một công cụ bên ngoài khác bashdate.

Nếu bạn thực sự muốn chỉ sử dụng các công cụ dòng lệnh bash và cơ bản, bạn có thể làm một cái gì đó như thế này:

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
}

Nhưng đó là một cách khá chậm để làm điều đó.

Một cách nhanh hơn nhưng phức tạp hơn cố gắng lặp đi lặp lại mỗi tháng, tìm ngày cuối cùng trong tháng đó, sau đó xem ngày Julian có nằm trong phạm vi đó không.

# 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_jdaycũng có thể được thực hiện bằng cách sử dụng ví dụ date -d "$yyyymm01 -1 day"(chỉ ngày GNU) hoặc $(($(date +"%s" -d "$yyyymm01") - 86400)).
Mikel

Bash có thể làm giảm như thế này : ((day--)). Bash có forcác vòng lặp như thế này for ((m=1; m<=12; m++))(không cần seq). Sẽ khá an toàn khi cho rằng các vỏ có một số tính năng khác mà bạn đang sử dụng có local.
Dennis Williamson

Địa phương IIRC không được chỉ định bởi POSIX, nhưng tuyệt đối, nếu sử dụng ksh có kiểu sắp chữ, zsh có cục bộ và zsh đã khai báo IIRC. Tôi nghĩ rằng kiểu chữ hoạt động trong cả 3, nhưng không hoạt động trong tro / dash. Tôi có xu hướng sử dụng kiểu ksh cho. Cảm ơn những suy nghĩ.
Mikel

@Mikel: Dash cũng localnhư BusyBox ash.
Dennis Williamson

Có, nhưng không phải loại IIRC. Tất cả những người khác có sắp chữ. Nếu dấu gạch ngang cũng có kiểu chữ, tôi sẽ sử dụng nó trong ví dụ.
Mikel

2

Nếu ngày trong năm (1-366) là 149 và năm là 2014 ,

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

Hãy chắc chắn nhập giá trị ngày của năm -1 .


0

Giải pháp của tôi trong 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

Trên thiết bị đầu cuối POSIX:

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

Sau đó gọi như

jul 2011 012
jul 2017 216
jul 2100 60

0

Tôi nhận ra điều này đã được hỏi từ lâu, nhưng không có câu trả lời nào tôi thấy là những gì tôi cho là BASH thuần vì tất cả đều sử dụng GNU date. Tôi nghĩ rằng tôi sẽ cố gắng trả lời ... Nhưng tôi cảnh báo bạn trước thời hạn, câu trả lời không thanh lịch, cũng không quá ngắn với hơn 100 dòng. Nó có thể được giảm xuống, nhưng tôi muốn cho người khác dễ dàng thấy những gì nó làm.

"Thủ thuật" chính ở đây là tìm hiểu xem một năm có phải là năm nhuận (hay không) bằng cách lấy MODULUS %của năm chia cho 4, và sau đó chỉ cần cộng các ngày trong mỗi tháng, cộng thêm một ngày cho tháng 2 nếu cần , sử dụng một bảng giá trị đơn giản.

Xin vui lòng bình luận và đưa ra đề xuất về các cách để làm điều này tốt hơn, vì tôi chủ yếu ở đây để tìm hiểu thêm về bản thân mình và tôi sẽ coi mình là người mới BASH tốt nhất. Tôi đã làm hết sức mình để biến nó thành hàng xách tay như tôi biết, và điều đó có nghĩa là một số thỏa hiệp theo ý kiến ​​của tôi.

Về mã ... Tôi hy vọng nó khá tự giải thích.

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

fyi, cách tính năm nhuận thực tế phức tạp hơn một chút so với "chia hết cho 4".
Erich

@erich - Bạn đã đúng, nhưng trong phạm vi năm được phép theo kịch bản này (1901-2099), các tình huống không hoạt động sẽ không xảy ra. Không quá khó để thêm một thử nghiệm "hoặc" để đối phó với số năm chia hết cho 100 nhưng không chia hết cho 400 để bao gồm các trường hợp đó nếu người dùng cần gia hạn điều này, nhưng tôi không cảm thấy thực sự cần thiết trong trường hợp này. Có lẽ đó là thiển cận của tôi?
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

đại diện toán học dưới đây

[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.