;# ctime.pl is a simple Perl emulation for the well known ctime(3C) function.
;#
;# Waldemar Kebsch, Federal Republic of Germany, November 1988
;# kebsch.pad@nixpbe.UUCP
;# Modified March 1990 to better handle timezones
;#  $Id: lib-unctime.perl,v 1.1.1.1 1997/03/11 17:35:03 foley Exp $
;#   Marion Hakanson (hakanson@cse.ogi.edu)
;#   Oregon Graduate Institute of Science and Technology
;#
;# usage:
;#
;#     #include <ctime.pl>          # see the -P and -I option in perl.man
;#     $Date = do ctime(time);
;#     $Timestamp = do unctime($Date);
;#     $Period = do time_period(time);
;#
;# HISTORY
;# 12/18/90 SMR		Hacked on ctime - removed timezone info and made 
;#			8:xx:xx come out as 08:xx:xx for compatibility with 
;#			dump.
;# 12/18/90 SMR		Added unctime - takes ctime format date string, converts
;#			back into timestamp integer.  Algorithm taken from unctime.c 
;#			from BSD dump.
;#

@Months = ("jan", "feb", "mar", "apr", "may", "jun", "jul", 
	   "aug", "sep", "oct", "nov", "dec");

@DoW = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
@MoY = ('Jan','Feb','Mar','Apr','May','Jun',
        'Jul','Aug','Sep','Oct','Nov','Dec');

#
# ctime - convert timestamp into an ascii string in localtime.
#
# ctime(timestamp), where timestamp is an integer as returned by time().
# returns a string as in "Sat Dec 21 22:14:37 1990\n".
#

sub ctime {
    local($time) = @_;
    local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);

    ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
      localtime($time);
    $year += ($year < 70) ? 2000 : 1900;
    sprintf("%s %s %2d %02d:%02d:%02d %4d\n",
      $DoW[$wday], $MoY[$mon], $mday, $hour, $min, $sec, $year);
}

#
# Simple lookup routine - look for $what in @array, return index number or 
# -1 if not found.
#

sub lookup {
    local($what, @array) = @_;
    local($i);

    for ($i = 0; $i <= $#array; $i++) {
	if ($array[$i] eq $what) {
	    return($i);
	}
    }

    return(-1);
}

# 
# emitl (ltime backward) - takes year, month, month-day, hour, minute and 
# second and converts it into a timestamp such as time() produces.
#
# How?  heh, heh.  Binary search.  Start with a timestamp of 0 ($result).  
# Try setting/unsetting bits starting from the high end until 
# &localtime($result) matches @a (the input time).
#
sub emitl {
    local(@a) = @_;
    local(@b);
    local($l_sec,$l_min,$l_hour,$l_mday,$l_mon,$l_year,$l_wday,$l_yday,$l_isdst);
    local($result, $bit, $i, $j);

    $result = 0;

    #
    # For each bit in the value, starting with the highest bit...
    #
bit_loop:
    for ($i = 30; $i >= 0; $i--) {
	$bit = 1 << $i;

	$result |= $bit;	# Try setting it...

	($l_sec,$l_min,$l_hour,$l_mday,$l_mon,$l_year,$l_wday,$l_yday,$l_isdst) =
	  localtime($result);	# Get the time info for that time...

	@b = ($l_year, $l_mon, $l_mday, $l_hour, $l_min, $l_sec);
				# Convert into a useful array...

	#
	# For each field (year, month, day of month, hour, minute and second)
	# ...if they are equal, go to next field.  If the new one is greater, 
	# unset this bit and go to the next bit.  If the new one is less, go 
	# to the next bit.
	#
	for ($j = 0; $j <= $#a; $j++) {
	    if ($b[$j] > $a[$j]) {
		$result &= ~ $bit;
		next bit_loop;
	    } 

	    if ($b[$j] < $a[$j]) {
		next bit_loop;
	    }
	}
    }

    return($result);
}

#
# unctime - undo a ctime style string back into a timestamp.
#
# unctime($string) - $string is the string to be undone.
# returns a time() style integer.
#
# Pretty simple - break $string into its constituent parts, use &emitl to convert 
# that into a timestamp.
#

sub unctime {
    local($string) = @_;
    local(@pieces, @tm, $i);
    local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
    local($s_time);
    if ((($s_wday, $s_mon, $s_mday, $s_time, $s_year) = split(/[ \t\n]+/, $string)) != 5) {
	return(-1);
    }

    if ((($s_hour, $s_min, $s_sec) = split(/:/, $s_time)) != 3) {
	return(-1);
    }

    # deal with day of week...
    $wday = &lookup($s_wday, @DoW);
    if ($wday == -1) {
	return(-1);
    }

    # deal with month...
    $mon = &lookup($s_mon, @MoY);
    if ($mon == -1) {
	return(-1);
    }

    # deal with day of month...
    $mday = $s_mday + 0;
    if ($mday <= 0 || $mday >= 32) {
	return(-1);
    }

    $hour = $s_hour + 0;
    if ($hour < 0 || $hour >= 24) {
	return(-1);
    }

    $min = $s_min + 0;
    if ($min < 0 || $min >= 60) {
	return(-1);
    }

    $sec = $s_sec + 0;
    if ($sec < 0 || $sec >= 60) {
	return(-1);
    }

    $year = $s_year + 0;
    if ($year >= 2000) {
	$year -= 2000;
    } else {
	$year -= 1900;
    }

    return(&emitl($year, $mon, $mday, $hour, $min, $sec));
}

#
# time_period($timestamp) - convert $timestamp (# seconds) into a string of 
# the form [N+]hh:mm:ss.  For example, "12+3:15:22" (12 days, 3 hours, 15 
# minutes and 22 seconds).
#

sub time_period {
    local($period) = @_;
    local($result);
    local($days, $hours, $minutes, $seconds) = (0, 0, 0, 0);
    local($spd, $sph, $spm) = (86400, 3600, 60);

    if ($period >= $spd) {
	$days = int($period / $spd);
	$period -= $days * $spd;
    }

    if ($period >= $sph) {
	$hours = int($period / $sph);
	$period -= $hours * $sph;
    }

    if ($period >= $spm) {
	$minutes = int($period / $spm);
	$period -= $minutes * $spm;
    }

    $seconds = $period;

    $result = sprintf("%s%d:%02d:%02d",
		      ($days > 0) ? sprintf("%d+", $days) : "",
		      $hours,
		      $minutes,
		      $seconds);

    return($result);
}


#
# spiffy(?) routine to parse a string containing date and time and turn it 
# into a timestamp.
#

sub parse_date_into_timestamp {
    local($date_string) = @_;
    local($sec, $min, $hour, $mday, $mon, $year, $tmp);
    local($tstamp);

    $date_string =~ tr/A-Z/a-z/;

    ($sec,$min,$hour,$mday,$mon,$year,$tmp,$tmp,$tmp) = 
      localtime(time);
    $tstamp = -1;

    # "now" or "today"
    if ($date_string =~ /^now$/i || $date_string =~ /^today$/i) {
	$tstamp = time;
	return($tstamp);
    } 

    # "6175388", a UNIX timestamp
    if ($date_string =~ /^\d+$/) {
	$tstamp = $date_string + 0;
	return($tstamp);
    }

    # must be some combination of date and time
    # Try ripping out the timestamp first

    if ($date_string =~ s/(\d+):(\d\d)(:(\d\d))?//) {
	$hour = $1 + 0;
	$min = $2 + 0;
	$sec = $4 + 0;
    }

    # ok, parse the rest as a date...

    # 3/27/61
    if ($date_string =~ m,^\s*(\d+)/(\d+)/(\d\d)\s*$,) {
	$mon = $1 - 1;
	$mday = $2 + 0;
	$year = $3 + 0;
    }

    # mar 27 1961
    elsif ($date_string =~ m,^\s*(\S\S\S)\s+(\d+)\s+(\d\d\d\d)\s*$,) {
	$mon = &lookup($1, @Months);
	$mday = $2 + 0; 
	$3 += 0;
	$year = ($3 >= 2000) ? $3 - 2000 : $3 - 1900;
    }

    # no date - use today
    elsif ($date_string =~ m,^\s*$,) {
    }

    # couldn't parse
    else {
	warn "warning: that isn't an acceptable time: try something like 'now' or '3/27/61 15:30' or\n'3/27/61' or 'mar 27 1961 15:30' or 'mar 27 1961'.\n";
	return(-1);
    }

    if ($hour >= 24) {
	warn "warning: hours must be < 24\n";
	return(-1);
    }
    if ($min >= 60) {
	warn "warning: minutes must be < 60\n";
	return(-1);
    }
    if ($sec >= 60) {
	warn "warning: seconds must be < 60\n";
	return(-1);
    }
    if ($mon == -1) {
	warn "warning: month must be (capitalized) Jan, Feb, Mar, etc...\n";
	return(-1);
    }
    if ($mon > 11) {
	warn "warning: month must be between 1 and 12 (inclusive).\n";
	return(-1);
    }

    $tstamp = &emitl($year, $mon, $mday, $hour, $min, $sec);
    return($tstamp);
}

1;
