Datumsberechnungen ohne GNU-Tools

Datumsberechnungen ohne GNU-Tools

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 GMTund 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

verwandte Informationen