GNU ツールを使わない日付計算

GNU ツールを使わない日付計算

シェル スクリプトで日付の計算と変換を実行する必要があります。たとえば、 としてフォーマットされた日付Nov 28 20:27:19 2012 GMTと今日の日付の差を計算します。

いくつかの可能性があります (GNU 、、dateなど) が、GNU ツールのないシステム (BSD、Solaris など) でも実行できるようにしたいと思います。gawkperl

現時点では、最も移植性の高いソリューションは Perl ですが、日付変換用の追加モジュールのインストールが必要になる可能性があります。

何を選べばいいでしょうか?

編集: コメントを見て、私が作成しているツールは公開配布されることを明確にする必要があることに気付きました。私は自分のマシンで計算を実行する方法や GNU ツールをインストールする方法を探しているわけではありません。

大多数のマシンに搭載されている可能性のあるツールを使用したいと思います。

答え1

私のソリューションは短く、これまでに C、Lua、Python、Perl、Awk、Ksh、Csh のバージョンを書いたことがあります (以下のバージョンは ksh です)。ansi_dn、nd_isna、dow で回答できる具体的な質問: 日付 YYYY MM DD が与えられた場合、100 日前または後の日付はいつですか? 日付が与えられた場合、曜日は何ですか? 2 つの日付が与えられた場合、その間の日数はいくつですか?

#==========================================================================
# 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

以前は、シェル スクリプトでブルート フォース解析と計算を行ってこれを実行する必要がありました。

シェル スクリプトで手動で実行すると、エラーが発生しやすく、時間がかかります。月ごとの日数、うるう年、タイム ゾーンを考慮する必要があります。ロケールや言語が異なると失敗します。

古いスクリプトの 1 つを修正しました。これはおそらく、さまざまなシェル環境に合わせて変更する必要がありますが、どのシェルでも動作するように変更できないものはないはずです。

#!/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 ユーティリティを使用せずにそれを日付に変換するには、上記の手順を逆に実行する必要があります...

答え3

私のdateutils移植可能ですが、どこにもインストールされていないと想定するのが安全です。ただし、ソース コード (単純な古い C) またはバイナリを配布するだけです。

答え4

私にとって最も論理的なのは、シェルの日付と時刻の機能を使用することでしょうか? たとえば、bash の場合:http://ss64.com/bash/date.html

関連情報