我必須在 shell 腳本中執行一些日期計算和轉換。例如,計算格式為的日期Nov 28 20:27:19 2012 GMT
與今天之間的天數差異。
有幾種可能性(GNU 、、、date
等),但我希望能夠在沒有 GNU 工具的系統(例如 BSD、Solaris 等)上運行它。gawk
perl
目前,我擁有的最便攜的解決方案是 Perl,但它可能需要安裝額外的模組來進行日期轉換。
我該選擇什麼?
編輯:查看評論,我意識到我需要澄清我正在編寫的工具將公開分發。我並不是在尋找一種在我的機器上執行計算的方法,也不是在尋找如何安裝 GNU 工具。
我想使用大多數機器上都可以找到的工具。
答案1
我的解決方案很簡短,我之前已經用 C、Lua、Python、Perl、Awk、Ksh 和 Csh 編寫過版本(下面的版本是 ksh。)您可以使用 ansi_dn、nd_isna 和 dow 回答一些具體問題:日期YYYY MM DD,前後100 天的日期是哪一天?給定一個日期,星期幾?給定兩個日期,中間相隔幾天?
#==========================================================================
# Calendar operations: Valid for dates in [1601-01-01, 3112-12-30].
# Adapted from a paper by B.E. Rodden, Mathematics Magazine, January, 1985.
#==========================================================================
# ANSI day number, given y=$1, m=$2, d=$3, starting from day number 1 ==
# (1601,1,1). This is similar to COBOL's INTEGER-OF-DATE function.
function ansi_dn {
integer y=$1 ys=$(($1-1601)) m=$2 d=$3 s dn1 isleap h
((isleap = y%4==0 && (y%100!=0 || y%400==0) ))
((dn1 = 365*ys + ys/4 - ys/100 + ys/400)) # first day number of given year.
((s = (214*(m-1) + 3)/7 + d - 1)) # standardized day number within year.
((h = -2*(!isleap && s>58) - (isleap && s>59) )) # correction to actual dn.
print -- $((1 + dn1 + s + h))
}
# Inverse of ansi_dn, for adn=$1 in [1, 552245], with returned dates in
# [1601-01-01, 3112-12-30]. Similar to COBOL's DATE-OF-INTEGER function.
function nd_isna {
integer y a s ys d0=$(($1-1)) dn1 isleap h
typeset -Z2 m d
((ys = d0/365))
((365*ys + ys/4 - ys/100 + ys/400 >= $1)) && ((ys -= 1))
((dn1 = 365*ys + ys/4 - ys/100 + ys/400))
((y = 1601 + ys))
((a = d0 - dn1))
((isleap = y%4==0 && (y%100!=0 || y%400==0) ))
((h = -2*(!isleap && a>58) - (isleap && a>59) ))
((s = a - h))
((m = (7*s + 3)/214 + 1))
((d = s - (214*(m-1) + 3)/7 + 1))
print -- $y $m $d
}
# Day of the week, given $1=ansi_dn. 0==Sunday, 6==Saturday.
function dow { print -- $(($1%7)); }
答案2
過去我必須在 shell 腳本中透過暴力解析和計算來完成此操作。
在 shell 腳本中手動執行此操作非常容易出錯且速度緩慢。您需要考慮每個月的天數、閏年、時區。對於不同的區域設定和不同的語言,它會失敗。
我修復了我的一個舊腳本,這可能需要針對不同的 shell 環境進行修改,但不應該有任何東西不能更改以在任何 shell 中工作。
#!/bin/sh
datestring="Nov 28 20:27:19 2012 GMT"
today=`date`
function date2epoch {
month=$1
day=$2
time=$3
year=$4
zone=$5
# assume 365 day years
epochtime=$(( (year-1970) * 365 * 24 * 60 * 60 ))
# adjust for leap days
i=1970
while [[ $i -lt $4 ]]
do
if [[ 0 -eq $(( i % 400 )) ]]
then
#echo $i is a leap year
# divisible by 400 is a leap year
epochtime=$((epochtime+24*60*60))
elif [[ 0 -eq $(( i % 100 )) ]]
then
#echo $i is not a leap year
epochtime=$epochtime
elif [[ 0 -eq $(( i % 4 )) ]]
then
#echo $i is a leap year
# divisible by 4 is a leap year
epochtime=$((epochtime+24*60*60))
# epoch=`expr $epoch + 24 * 60 * 60`
fi
i=$((i+1))
done
dayofyear=0
for imonth in Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
do
if [[ $month == $imonth ]]
then
break
fi
case $imonth in
'Feb')
if [[ 0 -eq $(( year % 400 )) ]]
then
days=29
elif [[ 0 -eq $(( year % 100 )) ]]
then
days=28
elif [[ 0 -eq $(( year % 4 )) ]]
then
days=29
fi
;;
Jan|Mar|May|Jul|Aug|Oct|Dec) days=31 ;;
*) days=30 ;;
esac
#echo $imonth has $days days
dayofyear=$((dayofyear + days))
done
## add the day of the month
dayofyear=$((dayofyear+day))
#echo $dayofyear
########## Add the day fo year (-1) to the epochtime
#(-1, since eg. Jan 1 is not 24 hours into Jan1 )
epochtime=$((epochtime + (dayofyear -1) * 24*60*60))
#echo $epochtime
################## hours, minutes, seconds
OFS=$IFS
IFS=":"
set -- $time
hours=$1
minutes=$2
seconds=$3
epochtime=$((epochtime + (hours * 60 * 60) + (minutes * 60) + seconds))
IFS=$OFS
################## Time zone
case $zone in
'GMT') zonenumber=0
break;;
'EST') zonenumber=-5
break;;
'EDT') zonenumber=-4
break;;
esac
epochtime=$((epochtime + zonenumber * 60 * 60 ))
echo $epochtime
}
result=`date2epoch $datestring`
echo $result
我可能在某個地方犯了錯誤,也許有更好的方法。如果您發現錯誤或更好的方法,請告訴我。
一旦你有了紀元時間,你就可以做一些有用的計算...儘管在沒有 gnu utils 的情況下將其轉換回日期...需要反向執行上述操作...
答案3
你可以用我的dateutils
。它們是便攜式的,但可以安全地假設它們沒有安裝在任何地方。但是,只需提供原始碼(純舊 C)或二進位。
答案4
對我來說最合乎邏輯的事情是使用 shell 的日期和時間功能?例如對於 bash :http://ss64.com/bash/date.html