Ich muss einige Datumsberechnungen und -konvertierungen in einem Shell-Skript durchführen. Beispielsweise die Berechnung der Tagesdifferenz zwischen einem als „Datum“ formatierten Datum Nov 28 20:27:19 2012 GMT
und dem heutigen Tag.
Es gibt mehrere Möglichkeiten (GNU date
, gawk
, perl
, usw.), aber ich möchte es auf Systemen ohne GNU-Tools ausführen können (z. B. BSDs, Solaris usw.).
Im Moment ist Perl die portabelste Lösung, die ich habe, aber für die Datumskonvertierung könnte die Installation zusätzlicher Module erforderlich sein.
Was soll ich wählen?
Bearbeiten: Beim Durchsehen der Kommentare ist mir klar geworden, dass ich klarstellen muss, dass das Tool, das ich schreibe, öffentlich verteilt wird. Ich suche nicht nach einer Möglichkeit, Berechnungen auf meiner Maschine durchzuführen oder GNU-Tools zu installieren.
Ich möchte Werkzeuge verwenden, die vermutlich auf den meisten Maschinen vorhanden sind.
Antwort1
Meine Lösung ist kurz und ich habe zuvor Versionen in C, Lua, Python, Perl, Awk, Ksh und Csh geschrieben (die folgende Version ist ksh). Einige spezifische Fragen, die Sie mit ansi_dn, nd_isna und dow beantworten können: Gegeben sei ein Datum TT.MM.JJJJ. Welches Datum liegt 100 Tage davor oder danach? Gegeben sei ein Datum. Welcher Wochentag ist heute? Gegeben seien zwei Daten. Wie viele Tage liegen dazwischen?
#==========================================================================
# 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)); }
Antwort2
Früher musste ich dies mithilfe von Brute-Force-Parsing und Berechnungen in Shell-Skripten erledigen.
Die manuelle Ausführung in einem Shell-Skript ist sehr fehleranfällig und langsam. Sie müssen Tage pro Monat, Schaltjahre und Zeitzonen berücksichtigen. Bei unterschiedlichen Gebietsschemas und unterschiedlichen Sprachen schlägt dies fehl.
Ich habe eines meiner alten Skripte repariert. Dies müsste wahrscheinlich für verschiedene Shell-Umgebungen geändert werden, aber es sollte nichts geben, was nicht geändert werden kann, um in jeder Shell zu funktionieren.
#!/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
Wahrscheinlich habe ich irgendwo einen Fehler gemacht und es gibt vielleicht einen besseren Weg. Sagen Sie mir Bescheid, wenn Sie einen Fehler oder einen besseren Weg finden.
Sobald Sie die Epochenzeit haben, können Sie einige nützliche Berechnungen durchführen. Um diese jedoch ohne die GNU-Dienstprogramme wieder in ein Datum umzuwandeln, müssen Sie die obigen Schritte in umgekehrter Reihenfolge ausführen.
Antwort3
Du könntest meinedateutils
. Sie sind portabel, aber man kann davon ausgehen, dass sie nirgendwo installiert sind. Liefern Sie jedoch nur den Quellcode (einfaches altes C) oder Binärdateien aus.
Antwort4
Am logischsten wäre es für mich, die Datums- und Uhrzeitfunktionen der Shell zu verwenden? Beispiel für Bash:http://ss64.com/bash/date.html