Cálculos de fechas sin herramientas GNU

Cálculos de fechas sin herramientas GNU

Tengo que realizar algunos cálculos de fechas y conversiones en un script de Shell. Por ejemplo, calcular la diferencia en días entre una fecha con formato hoy Nov 28 20:27:19 2012 GMTy hoy.

Hay varias posibilidades (GNU date, gawk, perletc.) pero me gustaría poder ejecutarlo en sistemas sin herramientas GNU (por ejemplo, BSD, Solaris, etc.)

Por el momento, la solución más portátil que tengo es Perl, pero podría requerir la instalación de módulos adicionales para la conversión de fechas.

¿Qué debo elegir?

Editar: mirando los comentarios me di cuenta de que necesito aclarar que la herramienta que estoy escribiendo se distribuirá públicamente. No estoy buscando una manera de realizar cálculos en mi máquina o cómo instalar herramientas GNU.

Quiero utilizar herramientas que probablemente se encuentren en la mayoría de las máquinas.

Respuesta1

Mi solución es breve y anteriormente escribí versiones en C, Lua, Python, Perl, Awk, Ksh y Csh (la versión siguiente es ksh). Algunas preguntas específicas que puedes responder con ansi_dn, nd_isna y dow: Dado un fecha AAAA MM DD, ¿cuál es la fecha 100 días antes o después? Dada una fecha, ¿cuál es el día de la semana? Dadas dos fechas, ¿cuántos días hay entre ellas?

#==========================================================================
# 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)); }

Respuesta2

Tuve que hacer esto en el pasado con análisis y cálculo de fuerza bruta en un script de shell.

Hacerlo manualmente en un script de shell es muy lento y propenso a errores. Debe tener en cuenta los días del mes, los años bisiestos y las zonas horarias. Fallará con diferentes configuraciones regionales y diferentes idiomas.

Arreglé uno de mis scripts antiguos, probablemente sería necesario modificarlo para diferentes entornos de shell, pero no debería haber nada que no se pueda cambiar para que funcione en cualquier 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

Probablemente cometí un error en alguna parte y puede que haya una manera mejor. Avíseme si encuentra un error o una forma mejor.

Una vez que tenga la época, puede hacer algunos cálculos útiles... aunque convertir eso nuevamente en una fecha sin las utilidades gnu... requiere hacer lo anterior a la inversa...

Respuesta3

Podrías usar midateutils. Son portátiles, pero es bastante seguro asumir que no están instalados en ningún lugar. Sin embargo, simplemente envíe el código fuente (C simple y antiguo) o los archivos binarios.

Respuesta4

Lo más lógico para mí sería utilizar las capacidades de fecha y hora del shell. por ejemplo, para bash:http://ss64.com/bash/date.html

información relacionada