Cálculos de data sem ferramentas GNU

7

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

Existem várias possibilidades (GNU date , gawk , perl , etc.) mas 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 data.

O que devo escolher?

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

Eu quero usar ferramentas que possam ser encontradas na maioria das máquinas.

    
por Matteo 05.07.2012 / 08:07

4 respostas

3

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

Fazê-lo manualmente no shell script é altamente propenso a erros e lento. Você precisa contabilizar dias por mês, anos bissextos e fusos horários. Ele falhará com diferentes idiomas e idiomas diferentes.

Eu consertei um dos meus scripts antigos, isso provavelmente precisaria ser modificado para ambientes de shell diferentes, mas não deve haver nada que não possa 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

Eu 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.

Uma vez que você tenha o tempo de época, você pode fazer alguns cálculos úteis ... apesar de converter isso de volta em uma data sem os utilitários do gnu ... requer fazer o acima ao contrário ...

    
por 05.07.2012 / 19:11
4

Minha solução é curta e já escrevi versões anteriores 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 uma data AAAA MM DD, qual é a data de 100 dias antes ou depois? Dada uma data, qual é o dia da semana? Dadas duas datas, quantos dias estão entre?

#==========================================================================
# 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)); }
    
por 05.07.2012 / 21:56
2

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

    
por 11.07.2012 / 15:52
-3

A coisa mais lógica para mim seria usar os recursos de data e hora do shells?  por exemplo, para o bash: link

    
por 05.07.2012 / 10:59

Tags