package TripodDate;

#################
# TripodDate.pm #
##############################################################################
# TripodDate is a module for dealing with dates.  You can fetch the current  #
# date (or part of it), modify the format of a date, see which of two dates  #
# is more recent, and so on.                                                 #
#                                                                            #
# The functions you may want to use are:                                     #
#   * currentDate();                     * convertMonthNameToInt();          #
#   * currentDay();                      * convertIntToMonthName();          #
#   * currentMonth();                    * dateIsPast();                     #
#   * currentYear();                     * addNMonthsToDate();               #
#   * massageDate();                     * addNDaysToDate();                 #
#   * convertYearToYYYY();               * isValidDate();                    #
#                                                                            #
# The first four functions, currentDate(), currentDay(), currentMonth(), and #
# currentYear(), are very similar.  None of them take any arguments, and     #
# each returns a single string, like this:                                   #
#                                                                            #
#   $todays_date = $DATE->currentDate();                                     #
#                                                                            #
# Note that all of the above functions return their information in           #
# mm/dd/yyyy format - so if today is July 16th, 1999, currentDate() would    #
# return '08/16/1999', and currentMonth() would return '08'.                 #
#                                                                            #
# massageDate takes a date which may not be in mm/dd/yyyy format, and        #
# returns it in mm/dd/yyyy format.  So, you could use it like this:          #
#                                                                            #
#   $unformatted_date = '9/6/99';                                            #
#   $formatted_date = $DATE->massageDate($unformatted_date);                 #
#                                                                            #
# $formatted_date would then be equal to '09/06/1999'.  Note that            #
# massageDate() can handle dates using dashes or slashes, but the date needs #
# to be numerical, and it needs to be in the standard U.S. ordering of       #
# month/day/year.                                                            #
#                                                                            #
# convertYearToYYYY() is another date formatter.  It just takes a year and   #
# converts it into a four-digit year.  It should work well for years between #
# 1931 and 2030.  Just say this to convert $two_digit_year to a              #
# $four_digit_year:                                                          #
#                                                                            #
#   $two_digit_year = '78';                                                  #
#   $four_digit_year = $DATE->convertYearToYYYY();                           #
#                                                                            #
# This will make $four_digit_year equal '1978'.                              #
#                                                                            #
# convertMonthNameToInt() and convertIntToMonthName() are sister functions,  #
# used to convert back and forth between a month name (like 'January' or     #
# 'jan') and that month's number (in this case, '1').  Use them like this:   #
#                                                                            #
#   $month_name = 'February';                                                #
#   $month_number = $DATE->convertMonthNameToInt($month_name);               #
#   $month_name_again = $DATE->convertIntToMonthName($month_number);         #
#                                                                            #
# dateIsPast() checks to see if a given date has already occurred.  The date #
# should be in month/day/year format.  It returns 1 if the date is past, 0   #
# otherwise.  Use it like this:                                              #
#                                                                            #
#   $date_in_question = '1/1/2000';                                          #
#   if ($DATE->dateIsPast($date_in_question)) {                              #
#       print "Y2K has already occurred.\n";                                 #
#   } else {                                                                 #
#       print "Y2K hasn't happened yet - there's still time to prepare!\n";  #
#   }                                                                        #
#                                                                            #
# Our last functions are another pair:  addNMonthsToDate() and               #
# addNDaysToDate().  Both require a date and a number; that number is added  #
# to the date to produce a new date.  Note that the number can be negative,  #
# so you can subtract as well.  For example:                                 #
#                                                                            #
#   $moon_walk_date = '07/20/69';                                            #
#   $five_days_later = $DATE->addNMonthsToDate($moon_walk_date, '3');        #
#   $ten_months_earlier = $DATE->addNMonthsToDate($moon_walk_date, '-10');   #
##############################################################################

($CMONTH,$CDAY,$CYEAR) = _getCurrentDate();

%MONTH_LENGTH = ('1'  => 31,
		 '2'  => 28,
		 '3'  => 31,
		 '4'  => 30,
		 '5'  => 31,
		 '6'  => 30,
		 '7'  => 31,
		 '8'  => 31,
		 '9'  => 30,
		 '10' => 31,
		 '11' => 30,
		 '12' => 31);

sub new {
    my $class = shift;
    my $self  = {};
    bless $self, $class;
    return $self;
}

# Preferred over todaysDate()
sub currentDate {
    return "$CMONTH/$CDAY/$CYEAR";
}

sub todaysDate {
    return "$CMONTH/$CDAY/$CYEAR";
}

sub currentMonth {
    return $CMONTH;
}
sub currentDay {
    return $CDAY;
}
sub currentYear {
    return $CYEAR;
}

sub massageDate {
    my $self = shift;
    my $date = shift;
    my($ref,$month,$day,$year);

    $ref = ref($date);

    if ($ref eq 'ARRAY') {
	# We're passed a reference to an array.
	($month,$day,$year) = @$date;
    } else {
	if ($date =~ /\//) {
	    ($month,$day,$year) = split('/',$date);
	} else {
	    ($month,$day,$year) = split('-',$date);
	}
	$year = $CYEAR
	    if (! defined($year));
    }

    # Put single digits in 01 format.
    for ($month,$day) {
	s/^(\d)$/0$1/;
    }
    $year = convertYearToYYYY(undef,$year);

    if ($ref eq 'ARRAY') {
	return ($month,$day,$year);
    } else {
	return "$month/$day/$year";
    }
}

sub convertYearToYYYY {
    my $self = shift;
    my $year = shift;

    if (! defined($year)) {
	return 0;
    }

    if ($year > 100) {
	# Year already includes century.
	return $year;
    }
    if ($year > 30 && $year < 100) {
	$year += 1900;
    } else {
	$year += 2000;
    }

    return $year;
}

# Pass a valid month name and this method will return the corresponding
# value of 1-12. When passed an invalid month name, this method returns 0.
sub convertMonthNameToInt {
    my($self,$month_name) = @_;
    my(@month_names) = qw(jan feb mar apr may jun
			  jul aug sep oct nov dec);
    my($temp_month_name,$month_int);

    $month_int = 1;
    for $temp_month_name (@month_names) {
	if ($month_name =~ /^$temp_month_name/i) {
	    return $month_int;
	} else {
	    $month_int++;
	}
    }

    # Invalid month name passed!
    return 0;
}

# Pass a value of 1-12 and this method will return the corresponding
# three-letter month name. When passed an invalid value, this returns ''.
sub convertIntToMonthName {
    my($self,$int) = @_;
    my(@month_names) = qw(jan feb mar apr may jun
			  jul aug sep oct nov dec);

    if ( (1 <= $int) && ($int <= 12) ) { 
	return $month_names[$int-1];
    }
    
    # invalid integer passed!
    else {
	return '';
    }
}

sub dateIsPast {
    # Pre:  Pass well-formed test date and optional ref date (default current).
    # Post: If test date is before reference date, return true.
    my $self = shift;
    my($date,$ref_date) = @_;
    my($emonth,$eday,$eyear,$rmonth,$rday,$ryear);

    if (! defined($date)) {
	return 0;
    }

    if (defined($ref_date)) {
	($rmonth,$rday,$ryear) = split('/',$ref_date);
	unless ((length $ryear) == 4)
	{
	    $ryear  = $self->convertYearToYYYY($ryear);
	}
    } else {
	$rmonth = $CMONTH;
	$rday   = $CDAY;
	$ryear  = $CYEAR;
    }

    ($emonth,$eday,$eyear) = split('/',$date);
    unless((length $ryear) == 4)
    {
	$eyear = $self->convertYearToYYYY($eyear);
    }

    if (($eyear <  $ryear) ||
	($eyear == $ryear && $emonth <  $rmonth) ||
	($eyear == $ryear && $emonth == $rmonth && $eday <= $rday)) {
	return 1;
    } else {
	return 0;
    }
}

sub addNMonthsToDate {
    my $self = shift;
    my($start_date,$duration_in_months) = @_;
    my($smonth,$sday,$syear,$emonth,$eday,$eyear,@month_last_days);

    # If no valid duration given, cut out now.
    $duration_in_months =~ s/^(\d+).*/$1/;
    if ($duration_in_months eq '') {
	warn 'Invalid duration passed';
	return 0;
    }

    ($smonth,$sday,$syear) =  split('/',$start_date);

    $emonth = $smonth + $duration_in_months;
    $eday   = $sday;
    $eyear  = $self->convertYearToYYYY($syear);
    
    while ($emonth > 12) {
	$emonth -= 12;
	$eyear++;
    }

    @month_last_days = qw(0 31 28 31 30 31 30 31 31 30 31 30 31);
    if ($eday > $month_last_days[$emonth]) {
	$eday = $month_last_days[$emonth];
    }

    return $self->massageDate("$emonth/$eday/$eyear");
}

# takes: a positive or negative integer(a number of days), a wf date
# does: none
# returns: a new wf date
# NOTE: breaks when dealing with leap years (or rather ignores the
# fact that it's a leap year. This is good enough for now.
sub addNDaysToDate {
    my $self = shift;
    my ($date,$num_days) = @_;
    my ($mon,$day,$yr);

    if ($date =~ m|^(\d+)/(\d+)/(\d+)$|) {
	($mon,$day,$yr) = ($1,$2,$3);
    } else {
	$self->_error("malformed date for addNDaysToDate : date-$date");
	return 0;
    }

    if ($num_days < 0) {
	$day += $num_days;
	while ($day < 1) {
	    if ($mon == 1) {
		$mon = 12;
		$yr--;
	    } else {
		$mon--;
	    }
	    $day = $MONTH_LENGTH{$mon} + $day;
	}
    } elsif ($num_days > 0) {
	$day += $num_days;
	while ($day > $MONTH_LENGTH{$mon}) {
	    $day = $day - $MONTH_LENGTH{$mon};
	    if ($mon == 12) {
		$mon = 1;
		$yr++;
	    } else {
		$mon++;
	    }
	}
    } else {
	return $date;
    }
    
    $mon =~ s/^(\d)$/0$1/;
    $day =~ s/^(\d)$/0$1/;
    $yr =~ s/^(\d)$/0$1/;

    return "$mon/$day/$yr";
}


# Private routines

sub _getCurrentDate {
    my $self = shift;
    # Get current date. Don't forget to increment localtime()'s month.
    my ($month, $day, $year) = (localtime(time))[4,3,5];
    $month++;
    $year += 1900;
    my @date = ($month, $day, $year);
    return massageDate(undef, \@date);
}


# takes: a string
# does: prints it as an error message
# returns: none
sub _error {
    my $self = shift;
    my ($error_msg) = @_;

    warn "Problem in Util::Date.pm : $error_msg \n";
}

1;
