วิธีการแปลงจากวันของปีและปีถึงวันที่ YYYYMMDD?


คำตอบ:


17

ฟังก์ชัน Bash นี้ใช้งานได้กับฉันในระบบที่ใช้ GNU:

jul () { date -d "$1-01-01 +$2 days -1 day" "+%Y%m%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

ฟังก์ชั่นนี้พิจารณาว่าวันที่จูเลียนเป็นศูนย์เป็นวันสุดท้ายของปีที่แล้ว

และนี่คือฟังก์ชัน bash สำหรับระบบที่ใช้ UNIX เช่น macOS:

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

2
นั่นเป็นทางออกที่ยอดเยี่ยม วันที่ของ GNU เท่านั้น แต่สั้นกว่ามาก
Mikel

ฉันไม่เคยรู้ว่าวันที่ของ GNU นั้นมีความยืดหยุ่นขนาดนั้น น่าอัศจรรย์
njd

ช่างเป็นทางออกที่ดีโดยใช้วันที่ของ GNU ในกรณีที่คุณต้องการทำเช่นเดียวกันกับระบบ UNIX เช่น macOS คุณสามารถใช้:jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
mcantsin


1
@cantsin: ฉันแก้ไขเวอร์ชันของคุณเพื่อให้ทำงานได้ในเชิงลบ
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

4

ไม่สามารถทำได้ในเพียงแค่ Bash แต่ถ้าคุณมี 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
จริง ๆ แล้วฉันค่อนข้างแน่ใจว่าเขาสามารถทำได้ใน Bash เท่านั้นแม้ว่าจะไม่ใช่แบบที่สง่างาม (กรณีที่แย่ที่สุดในการคำนวณรูปแบบการประทับเวลา) แต่ฉันไม่ได้อยู่หน้าเครื่อง Linux เพื่อทดสอบ
Bobby

บ๊อบบี้คุณอาจคิดถึงdateคำสั่งใน coreutils ฉันตรวจสอบแล้วและรองรับเฉพาะการจัดรูปแบบวันที่ปัจจุบันหรือตั้งค่าเป็นค่าใหม่ดังนั้นจึงไม่ใช่ตัวเลือก
bandi

การใช้dateคำสั่งสามารถทำได้ ดูคำตอบของฉัน แต่วิธี Perl นั้นง่ายกว่าและเร็วกว่ามาก
Mikel

2
@bandi: เว้นแต่date -d
user1686

+1: ใช้สิ่งนี้ - ขอบคุณ แต่วิธีวันที่ -d ข้างต้นปรากฏขึ้น
Umber Ferrule

4

เรียกใช้info 'Date input formats'เพื่อดูรูปแบบที่อนุญาต

รูปแบบวันที่ YYYY-DDDดูเหมือนจะไม่ได้อยู่ที่นั่นและพยายาม

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

แสดงให้เห็นว่ามันไม่ทำงานดังนั้นฉันคิดว่าnjdถูกต้องวิธีที่ดีที่สุดคือการใช้เครื่องมือภายนอกอื่น ๆ กว่าและbashdate

หากคุณต้องการใช้เฉพาะเครื่องมือทุบตีและบรรทัดคำสั่งพื้นฐานคุณสามารถทำสิ่งนี้:

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
}

แต่นั่นเป็นวิธีที่ค่อนข้างช้าในการทำ

วิธีที่เร็วและซับซ้อนยิ่งขึ้นพยายามวนรอบแต่ละเดือนค้นหาวันสุดท้ายของเดือนนั้นแล้วดูว่าวันจูเลียนอยู่ในช่วงนั้นหรือไม่

# 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_jdayอาจจะมีการดำเนินการโดยใช้เช่นdate -d "$yyyymm01 -1 day"วันที่ (GNU เท่านั้น) $(($(date +"%s" -d "$yyyymm01") - 86400))หรือ
Mikel

((day--))ทุบตีสามารถทำลดลงเช่นนี้ Bash มีforลูปแบบนี้for ((m=1; m<=12; m++))(ไม่จำเป็นseq) มันค่อนข้างปลอดภัยที่จะสมมติว่าเชลล์ที่มีคุณสมบัติอื่น ๆ ที่คุณใช้localอยู่
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

IIRC ภายในไม่ได้ระบุโดย POSIX แต่แน่นอนถ้าใช้ ksh มีประเภทเรียงพิมพ์ zsh มีโลคัลและ zsh ประกาศ IIRC ฉันคิดว่าเรียงพิมพ์ใช้งานได้ทั้ง 3 แบบ แต่ไม่ได้ทำงานในเถ้า / เส้นประ ฉันมักจะใช้ ksh-style ต่ำเกินไป ขอบคุณสำหรับความคิด
Mikel


ใช่ แต่ไม่ใช่ประเภท IIRC คนอื่น ๆ ทั้งหมดมีเรียงพิมพ์ หากเส้นประมีชุดตัวพิมพ์ด้วยฉันจะใช้มันในตัวอย่าง
Mikel

1

ถ้าวันของปี (1-366) เป็น149และปี2014 ,

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

ให้แน่ใจว่าได้ป้อนข้อมูลวันของปี -1ค่า


0

วิธีการแก้ปัญหาของฉันในทุบตี

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

บนเทอร์มินัล POSIX:

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

จากนั้นโทรเช่น

jul 2011 012
jul 2017 216
jul 2100 60

0

ฉันรู้นี้ถูกถามทุกเพศทุกวัยที่ผ่านมา แต่ไม่มีคำตอบที่ฉันเห็นเป็นสิ่งที่ผมพิจารณาทุบตีบริสุทธิ์ตั้งแต่พวกเขาทั้งหมดใช้ dateGNU ฉันคิดว่าฉันจะรับแทงที่ตอบ ... แต่ฉันเตือนคุณก่อนเวลาคำตอบนั้นไม่สวยและไม่สั้นเกิน 100 บรรทัด มันอาจถูกตัดลง แต่ฉันต้องการให้คนอื่นเห็นว่ามันทำอะไรได้ง่าย

"เคล็ดลับ" หลักที่นี่คือการหาว่าปีหนึ่งเป็นปีอธิกสุรทิน (หรือไม่) โดยรับโมดูลัส%ของปีหารด้วย 4 แล้วเพิ่มวันในแต่ละเดือนบวกวันพิเศษสำหรับเดือนกุมภาพันธ์หากจำเป็น ใช้ตารางค่าง่าย ๆ

โปรดแสดงความคิดเห็นและให้คำแนะนำเกี่ยวกับวิธีการที่จะทำสิ่งนี้ให้ดีขึ้นเนื่องจากฉันมาที่นี่เพื่อเรียนรู้เพิ่มเติมเกี่ยวกับตัวฉันเอง ฉันพยายามอย่างเต็มที่ที่จะทำให้มันเป็นแบบพกพาอย่างที่ฉันรู้และนั่นหมายความว่าฉันมีความเห็นที่ประนีประนอม

ไปที่รหัส ... ฉันหวังว่ามันจะอธิบายได้ด้วยตนเอง

#!/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 การคำนวณปีอธิกลาจริงนั้นซับซ้อนกว่า "หารด้วย 4" เพียงเล็กน้อย
ริช

@erich - คุณถูกต้อง แต่ภายในช่วงปีที่อนุญาตให้ใช้ได้โดยสคริปต์นี้ (1901-2099) สถานการณ์ที่ไม่ทำงานจะไม่เกิดขึ้น ไม่ใช่เรื่องยากที่จะเพิ่มการทดสอบ "หรือ" เพื่อจัดการกับปีที่หารด้วย 100 เท่า ๆ กัน แต่ไม่หารหารด้วย 400 เพื่อครอบคลุมกรณีเหล่านั้นหากผู้ใช้ต้องการขยายเวลานี้ แต่ฉันไม่รู้สึกว่าจำเป็นจริงๆใน กรณีนี้. อาจจะเป็นสายตาสั้นของฉัน?
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

แสดงทางคณิตศาสตร์ด้านล่าง

[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.