Cálculos de data sem ferramentas GNU

Cálculos de data sem ferramentas GNU

Tenho que realizar alguns cálculos de data e conversão em um script de shell. Por exemplo, calculando a diferença em dias entre uma data formatada como Nov 28 20:27:19 2012 GMTe hoje.

Existem várias possibilidades (GNU date, gawk, perl, etc.) mas eu gostaria de poder executá-lo em sistemas sem ferramentas GNU (por exemplo, BSDs, Solaris, etc.)

No momento a solução mais portátil que tenho é Perl mas pode exigir a instalação de módulos adicionais para a conversão de datas.

O que devo escolher?

Editar: olhando os comentários percebi que preciso esclarecer que a ferramenta que estou escrevendo será distribuída publicamente. Não estou procurando uma maneira de realizar cálculos em minha máquina ou de instalar ferramentas GNU.

Quero usar ferramentas que provavelmente serão encontradas na maioria das máquinas.

Responder1

Minha solução é curta e já escrevi versões em C, Lua, Python, Perl, Awk, Ksh e Csh (a versão abaixo é ksh). Algumas perguntas específicas que você pode responder com ansi_dn, nd_isna e dow: Dado um data AAAA MM DD, qual é a data 100 dias antes ou depois? Dada uma data, qual é o dia da semana? Dadas duas datas, quantos dias há entre elas?

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

Responder2

Eu tive que fazer isso no passado com análise e cálculo de força bruta em shell script.

Fazer isso manualmente em shell script é altamente sujeito a erros e lento. Você precisa contabilizar dias por mês, anos bissextos e fusos horários. Ele falhará com localidades e idiomas diferentes.

Eu consertei um dos meus scripts antigos, provavelmente precisaria ser modificado para diferentes ambientes de shell, mas não deveria haver nada que não pudesse ser alterado para funcionar em qualquer 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

Provavelmente cometi um erro em algum lugar e pode haver uma maneira melhor. Deixe-me saber se você encontrar um bug ou uma maneira melhor.

Depois de ter a época, você pode fazer alguns cálculos úteis... embora convertê-los de volta em uma data sem os utilitários gnu... exija fazer o procedimento acima ao contrário...

Responder3

Você poderia usar meudateutils. Eles são portáteis, mas é bastante seguro presumir que não estão instalados em lugar nenhum. No entanto, basta enviar o código-fonte (C simples e antigo) ou binários.

Responder4

A coisa mais lógica para mim seria usar os recursos de data e hora do shell? por exemplo, para bash:http://ss64.com/bash/date.html

informação relacionada