Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I have to perform some date calculations and conversion in a shell script. For example computing the difference in day between a date formatted as Nov 28 20:27:19 2012 GMT and today.

There are several possibilities (GNU date, gawk, perl, etc.) but I would like to be able to run it on systems without GNU tools (e.g., BSDs, Solaris, etc.)

At the moment the most portable solution I have is perl but it could require the installation of additional modules for the date conversion.

What should I choose?

Edit: looking at the comments I realized that I need to clarify that the tool I am writing will be publicly distributed. I am not looking on a way to perform computations on my machine r on how to install GNU tools.

I want to use tools that are likely to be found on the majority of the machines.

share|improve this question
1  
Use what ever you are most comfortable with - i personally like the python datetime module though it is a little bit rough re. timezones - docs.python.org/library/datetime.html –  Ulrich Dangel Jul 5 '12 at 6:14
    
Python would not be a problem but I don't think I can just assume it is always installed. I'll check if it is present on standard Solaris and Mac OS X installations. –  Matteo Jul 5 '12 at 6:33
    
jftr perl is afaik not installed on minimal centos 6 installations - but if you want to go the perl route have a look at carton which is basically bundler for perl and will installed the specified modules in your local directory. –  Ulrich Dangel Jul 5 '12 at 6:38
    
Ok, so the standard way is to use a programming language like perl/ruby/python and just state the requirements. These languages have package manager as well as most systems have one too. –  Ulrich Dangel Jul 5 '12 at 9:28
1  
Non-embedded unices without perl are really rare. I'd go for perl with no additional module, it's good enough to parse a date that follows a well-known format. –  Gilles Jul 5 '12 at 22:37

4 Answers 4

up vote 2 down vote accepted

I had to do this in the past with brute force parsing and calculation in shell script.

Doing it manually in shell script is highly error-prone and slow. You need to account for days per month, leap years, time zones. It will fail with different locales and different languages.

I fixed up one of my old scripts, this would probably need to be modified for different shell environments, but there shouldn't be anything that can't be changed to work in any 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

I probably made a mistake somewhere and there may be a better way. Let me know if you find a bug, or a better way.

Once you have the epoch time, you can do some useful calculations... although converting that back into a date without the gnu utils... requires doing the above in reverse...

share|improve this answer

Most logical thing to me would be to use the shells date&time capabilities? e.g for bash : http://ss64.com/bash/date.html

share|improve this answer
2  
I didn't downvote you but the problem is that GNU date will behave differently than BSD date and they don't have, as far as i know, the same feature set. –  Ulrich Dangel Jul 5 '12 at 9:29
    
Yes the problem is that the needed "-d" is a GNU date option –  Matteo Jul 5 '12 at 14:36

My solution is short, and I have previously written versions in C, Lua, Python, Perl, Awk, Ksh, and Csh (the version below is ksh.) Some specific questions you can answer with ansi_dn, nd_isna, and dow: Given a date YYYY MM DD, what is the date 100 days before or after? Given a date, what is the day of the week? Given two dates, how many days are in between?

#==========================================================================
# 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)); }
share|improve this answer

You could use my dateutils. They're portable but it's rather safe to assume they're not installed anywhere. However, just ship the source code (plain old C) or binaries.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.