package WebGUI::DateTime;

=head1 LEGAL

 -------------------------------------------------------------------
  WebGUI is Copyright 2001-2004 Plain Black LLC.
 -------------------------------------------------------------------
  Please read the legal notices (docs/legal.txt) and the license
  (docs/license.txt) that came with this distribution before using
  this software.
 -------------------------------------------------------------------
  http://www.plainblack.com                     info@plainblack.com
 -------------------------------------------------------------------

=cut

use Date::Manip;
use Exporter;
use strict;
use WebGUI::International;
use WebGUI::Session;
use WebGUI::Utility;

our @ISA = qw(Exporter);
our @EXPORT = qw(&localtime &time &addToTime &addToDate &epochToHuman &epochToSet &humanToEpoch &setToEpoch &monthStartEnd);



=head1 NAME

Package WebGUI::DateTime

=head1 DESCRIPTION

This package provides easy to use date math functions, which are normally a complete pain.

=head1 SYNOPSIS

 use WebGUI::DateTime;
 $epoch = WebGUI::DateTime::addToDate($epoch, $years, $months, $days);
 $epoch = WebGUI::DateTime::addToTime($epoch, $hours, $minutes, $seconds);
 $epoch = WebGUI::DateTime::arrayToEpoch(@date);
 ($startEpoch, $endEpoch) = WebGUI::DateTime::dayStartEnd($epoch);
 @date = WebGUI::DateTime::epochToArray($epoch);
 $dateString = WebGUI::DateTime::epochToHuman($epoch, $formatString);
 $setString = WebGUI::DateTime::epochToSet($epoch);
 $day = WebGUI::DateTime::getDayName($dayInteger);
 $integer = WebGUI::DateTime::getDaysInMonth($epoch);
 $integer = WebGUI::DateTime::getDaysInInterval($start, $end);
 $integer = WebGUI::DateTime::getFirstDayInMonthPosition($epoch);
 $month = WebGUI::DateTime::getMonthName($monthInteger);
 $seconds = WebGUI::DateTime::getSecondsFromEpoch($seconds);
 $epoch = WebGUI::DateTime::humanToEpoch($dateString);
 $seconds = WebGUI::DateTime::intervalToSeconds($interval, $units);
 @date = WebGUI::DateTime::localtime($epoch);
 ($startEpoch, $endEpoch) = WebGUI::DateTime::monthStartEnd($epoch);
 ($interval, $units) = WebGUI::DateTime::secondsToInterval($seconds);
 $timeString = WebGUI::DateTime::secondsToTime($seconds);
 $epoch = WebGUI::DateTime::setToEpoch($setString);
 $epoch = WebGUI::DateTime::time();
 $seconds = WebGUI::DateTime::timeToSeconds($timeString);

=head1 METHODS

These functions are available from this package:

=cut

sub epochToDate {
	my $secs	= shift;
	return &ParseDateString("epoch $secs");
}

sub dateToEpoch {
	return &UnixDate(shift,"%s");
}



#-------------------------------------------------------------------

=head2 addToDate ( epoch [ , years, months, days ] )

Returns an epoch date with the amount of time added.

=over

=item epoch

The number of seconds since January 1, 1970.

=item years

The number of years to add to the epoch.

=item months

The number of months to add to the epoch.

=item days

The number of days to add to the epoch. 

=back

=cut

sub addToDate {
	my ($date,$years,$months,$days,$newDate);
	$date		= &epochToDate(shift);
	$years 		= shift || 0;
	$months 	= shift || 0;
	$days	 	= shift || 0;
	$newDate 	= DateCalc($date,"$years:$months:0:$days:0:0:0");
	return &dateToEpoch($newDate);
}

#-------------------------------------------------------------------

=head2 addToTime ( epoch [ , hours, minutes, seconds ] )

Returns an epoch date with the amount of time added.

=over

=item epoch

The number of seconds since January 1, 1970.

=item hours

The number of hours to add to the epoch.

=item minutes

The number of minutes to add to the epoch.

=item seconds

The number of seconds to add to the epoch.

=back

=cut

sub addToTime {
	my ($date,$hours,$mins,$secs,$newDate);
	$date		= &epochToDate(shift);
	$hours 		= shift || 0;
	$mins	 	= shift || 0;
	$secs	 	= shift || 0;
	$newDate 	= DateCalc($date,"0:0:0:0:$hours:$mins:$secs");
	return &dateToEpoch($newDate);
}

#-------------------------------------------------------------------

=head2 arrayToEpoch ( date )

Returns an epoch date.

=over

=item date

An array of the format year, month, day, hour, min, sec.

=back

=cut

sub arrayToEpoch {
	my ($year,$month,$day, $hour,$min,$sec) = @_;
	$min = "0$min" if (length($min) == 1);
	$sec = "0$sec" if (length($sec) == 1);
	return &dateToEpoch(&ParseDate("$year-$month-$day $hour:$min:$sec"));
}


#-------------------------------------------------------------------

=head2 dayStartEnd ( epoch )

Returns the epoch dates for the start and end of the day.

=over

=item epoch

The number of seconds since January 1, 1970.

=back

=cut

sub dayStartEnd {
        my ($year,$month,$day, $hour,$min,$sec, $start, $end);
        ($year,$month,$day, $hour,$min,$sec) = epochToArray($_[0]);
        $start = &arrayToEpoch($year,$month,$day,0,0,0);
        $end = &arrayToEpoch($year,$month,$day,23,59,59);
        return ($start, $end);
}

#-------------------------------------------------------------------

=head2 epochToArray ( epoch ) 

Returns a date array in the form of year, month, day, hour, min, sec.

=over

=item epoch

An epoch date.

=back

=cut

sub epochToArray {
	my $epoch = shift;
	return &UnixDate(epochToDate($epoch),'%Y','%m','%d','%H','%M','%S');
}


#-------------------------------------------------------------------

=head2 epochToHuman ( [ epoch, format ] )

Returns a formated date string.

=over

=item epoch

The number of seconds since January 1, 1970. Defaults to NOW!

=item format 

A string representing the output format for the date. Defaults to '%z %Z'. You can use the following to format your date string:

 %% = % (percent) symbol.
 %c = The calendar month name.
 %C = The calendar month name abbreviated to 3 characters and represented in English.
 %d = A two digit day.
 %D = A variable digit day.
 %h = A two digit hour (on a 12 hour clock).
 %H = A variable digit hour (on a 12 hour clock).
 %j = A two digit hour (on a 24 hour clock).
 %J = A variable digit hour (on a 24 hour clock).
 %m = A two digit month.
 %M = A variable digit month.
 %n = A two digit minute.
 %o = Offset from GMT represented as an integer.
 %O = Offset from GMT represented in four digit form with a sign. Example: -0600
 %p = A lower-case am/pm.
 %P = An upper-case AM/PM.
 %s = A two digit second.
 %w = Day of the week. 
 %W = Day of the week abbreviated to 3 characters and represented in English. 
 %y = A four digit year.
 %Y = A two digit year. 
 %z = The current user's date format preference.
 %Z = The current user's time format preference.

=back

=cut

sub epochToHuman {
	my ($offset, $temp, $hour12, $value, $output);
	$offset = $session{user}{timeOffset} || 0;
	$offset = $offset*3600;
	$temp = int($_[0]) || WebGUI::DateTime::time();
	$temp = $temp+$offset;
	my $dt = epochToDate($temp);
	my ($year,$month,$day,$hour,$min,$sec) = epochToArray($temp);
	$output = $_[1] || "%z %Z";
  #---GMT Offsets
	$temp = $session{user}{timeOffset}*100;
	$temp = sprintf('%+05d',$temp);
	$output =~ s/\%O/$temp/g;
	$temp = $session{user}{timeOffset}+0;
	$output =~ s/\%o/$temp/g;
  #---dealing with percent symbol
	$output =~ s/\%\%/\%/g;
  #---date format preference
	$temp = $session{user}{dateFormat} || '%M/%D/%y';
	$output =~ s/\%z/$temp/g;
  #---time format preference
	$temp = $session{user}{timeFormat} || '%H:%n %p';
	$output =~ s/\%Z/$temp/g;
  #---year stuff
	$output =~ s/\%y/$year/g;
	$value = substr($year,2,2);
	$output =~ s/\%Y/$value/g;
  #---month stuff
	$value = sprintf("%02d",$month);
	$output =~ s/\%m/$value/g;
	$output =~ s/\%M/$month/g;
	if ($output =~ /\%c/) {
		$temp = getMonthName($month);
		$output =~ s/\%c/$temp/g;
	}
	if ($output =~ /\%C/) {
		$temp = &UnixDate($dt,'%b');
		$output =~ s/\%C/$temp/g;
	}
  #---day stuff
	$value = sprintf("%02d",$day);
	$output =~ s/\%d/$value/g;
	$output =~ s/\%D/$day/g;
	if ($output =~ /\%w/) {
		$temp = getDayName(&UnixDate($dt,'%w'));
		$output =~ s/\%w/$temp/g;
	}
	if ($output =~ /%W/) {
		$temp = &UnixDate($dt,'%a');
		$output =~ s/\%W/$temp/g;
	}
  #---hour stuff
	$hour12 = $hour;
	if ($hour12 > 12) {
		$hour12 = $hour12 - 12;
	}	
	if ($hour12 == 0) {
		$hour12 = 12;
	}
	$value = sprintf("%02d",$hour12);
	$output =~ s/\%h/$value/g;
	$output =~ s/\%H/$hour12/g;
	$value = sprintf("%02d",$hour);
	$output =~ s/\%j/$value/g;
	$output =~ s/\%J/$hour/g;
	if ($hour > 11) {
		$output =~ s/\%p/pm/g;
		$output =~ s/\%P/PM/g;
	} else {
		$output =~ s/\%p/am/g;
		$output =~ s/\%P/AM/g;
	}
  #---minute stuff
	$value = sprintf("%02d",$min);
	$output =~ s/\%n/$value/g;
  #---second stuff
	$value = sprintf("%02d",$sec);
	$output =~ s/\%s/$value/g;
	return $output;
}

=head2 epochToSet ( epoch )

Returns a set date (used by WebGUI::HTMLForm->date) in the format of MM/DD/YYYY. 

=over

=item epoch

The number of seconds since January 1, 1970.

=back

=cut

sub epochToSet {
	return epochToHuman($_[0],"%m/%d/%y");
}

#-------------------------------------------------------------------

=head2 getMonthName ( month )

Returns a string containing the calendar month name in the language of the current user.

=over

=item month

An integer ranging from 1-12 representing the month.

=back

=cut

sub getMonthName {
        if ($_[0] == 1) {
                return WebGUI::International::get(15);
        } elsif ($_[0] == 2) {
                return WebGUI::International::get(16);
        } elsif ($_[0] == 3) {
                return WebGUI::International::get(17);
        } elsif ($_[0] == 4) {
                return WebGUI::International::get(18);
        } elsif ($_[0] == 5) {
                return WebGUI::International::get(19);
        } elsif ($_[0] == 6) {
                return WebGUI::International::get(20);
        } elsif ($_[0] == 7) {
                return WebGUI::International::get(21);
        } elsif ($_[0] == 8) {
                return WebGUI::International::get(22);
        } elsif ($_[0] == 9) {
                return WebGUI::International::get(23);
        } elsif ($_[0] == 10) {
                return WebGUI::International::get(24);
        } elsif ($_[0] == 11) {
                return WebGUI::International::get(25);
        } elsif ($_[0] == 12) {
                return WebGUI::International::get(26);
        }
}

#-------------------------------------------------------------------

=head2 getDayName ( day )

Returns a string containing the weekday name in the language of the current user. 

=over

=item day

An integer ranging from 1-7 representing the day of the week (Sunday is 1 and Saturday is 7). 

=back

=cut

sub getDayName {
	my $day = $_[0];
        if ($day == 7) {
                return WebGUI::International::get(27);
        } elsif ($day == 1) {
                return WebGUI::International::get(28);
        } elsif ($day == 2) {
                return WebGUI::International::get(29);
        } elsif ($day == 3) {
                return WebGUI::International::get(30);
        } elsif ($day == 4) {
                return WebGUI::International::get(31);
        } elsif ($day == 5) {
                return WebGUI::International::get(32);
        } elsif ($day == 6) {
                return WebGUI::International::get(33);
        }
}

#-------------------------------------------------------------------

=head2 getDaysInMonth ( epoch )

Returns the total number of days in the month.

=over

=item epoch

An epoch date.

=back

=cut

sub getDaysInMonth {
	my $epoch = shift;
	my @date = WebGUI::DateTime::epochToArray($epoch);
	return &Date_DaysInMonth($date[1], $date[0]);
}


#-------------------------------------------------------------------

=head2 getDaysInInterval ( start, end )

Returns the number of days between two epoch dates.

=over

=item start

An epoch date.

=item end

An epoch date.

=back

=cut

sub getDaysInInterval {
	my $start = &epochToDate(shift);
	my $end = &epochToDate(shift);
	my $err;
	my $delta = &DateCalc($start,$end,\$err);
	return &Delta_Format($delta,0,'%dh');
}



#-------------------------------------------------------------------

=head2 getFirstDayInMonthPosition ( epoch) {

Returns the position (1 - 7) of the first day in the month.

=over

=item epoch

An epoch date.

=back

=cut

sub getFirstDayInMonthPosition {
	my $epoch = shift;
	my @date = WebGUI::DateTime::epochToArray($epoch);
	my $firstDayInFirstWeek = &UnixDate("$date[0]-$date[1]-01",'%w');
	unless ($session{user}{firstDayOfWeek}) { #american format
        	$firstDayInFirstWeek++;
        	if ($firstDayInFirstWeek > 7) {
                	$firstDayInFirstWeek = 1;
        	}
	}
	return $firstDayInFirstWeek;
}


#-------------------------------------------------------------------

=head2 getSecondsFromEpoch ( epoch )

Calculates the number of seconds into the day of an epoch date the epoch datestamp is.

=over

=item epoch

The number of seconds since January 1, 1970 00:00:00.

=back

=cut

sub getSecondsFromEpoch {
	return timeToSeconds(epochToHuman($_[0],"%j:%n:%s"));
}



#-------------------------------------------------------------------

=head2 humanToEpoch ( date )

Returns an epoch date derived from the human date.

=over

=item date

The human date string. YYYY-MM-DD HH:MM:SS 

=back

=cut

sub humanToEpoch {
	my (@temp, $dateString, $timeString, $output, @date);
	($dateString,$timeString) = split(/ /,$_[0]);
	@temp = split(/-/,$dateString);
	$date[0] = int($temp[0]);
	$date[1] = int($temp[1]);
	$date[2] = int($temp[2]);
	@temp = split(/:/,$timeString);
	$date[3] = int($temp[0]);
	$date[4] = int($temp[1]);
	$date[5] = int($temp[2]);
	$output = arrayToEpoch(@date);
	return $output;
} 

#-------------------------------------------------------------------

=head2 intervalToSeconds ( interval, units )

Returns the number of seconds derived from the interval.

=over

=item interval

An integer which represents the amount of time for the interval.

=item units

A string which represents the units of the interval. The string must be 'years', 'months', 'weeks', 'days', 'hours', 'minutes', or 'seconds'. 

=back

=cut

sub intervalToSeconds {
	if ($_[1] eq "years") {
		return ($_[0]*31536000);
	} elsif ($_[1] eq "months") {
		return ($_[0]*2592000);
        } elsif ($_[1] eq "weeks") {
                return ($_[0]*604800);
        } elsif ($_[1] eq "days") {
                return ($_[0]*86400);
        } elsif ($_[1] eq "hours") {
                return ($_[0]*3600);
        } elsif ($_[1] eq "minutes") {
                return ($_[0]*60);
        } else {
                return $_[0];
	} 
}

#-------------------------------------------------------------------

=head2 localtime ( epoch )

Returns an array of time elements. The elements are: years, months, days, hours, minutes, seconds, day of year, day of week, daylight savings.

=over

=item epoch

The number of seconds since January 1, 1970. Defaults to now.

=back

=cut

sub localtime {
	my $epoch = shift || &dateToEpoch(&ParseDate("today"));
	my $date  = &epochToDate($epoch);
	my ($year, $month, $day, $hour, $min, $sec) = epochToArray($epoch);
	if ($epoch) {
		($year, $month, $day, $hour, $min, $sec) = epochToArray($epoch);
	}
	my $doy = &UnixDate($date,'%j');
	my $dow = &UnixDate($date,'%w');
	my @temp = localtime($epoch);
	return ($year, $month, $day, $hour, $min, $sec, $doy, $dow, $temp[8]);
}

#-------------------------------------------------------------------
=head2 monthCount ( startEpoch, endEpoch )

Returns the number of months between the start and end dates (inclusive).

=over

=item startEpoch

An epoch datestamp corresponding to the first month.

=item endEpoch

An epoch datestamp corresponding to the last month.

=back

=cut

sub monthCount {
	my $start = &epochToDate(shift);
	my $end = &epochToDate(shift);
	my $err;
	my $delta = &DateCalc($start,$end,\$err,1);
	return $delta;
	return &Delta_Format($delta,0,'%Mh');
}


#-------------------------------------------------------------------

=head2 monthStartEnd ( epoch )

Returns the epoch dates for the start and end of the month.

=over

=item epoch

The number of seconds since January 1, 1970.

=back

=cut

sub monthStartEnd {
	#my ($year,$month,$day, $hour,$min,$sec, $start, $end);
	#($year,$month,$day, $hour,$min,$sec) = epochToArray($_[0]);
	#$start = arrayToEpoch($year,$month,1,0,0,0);
	#($year,$month,$day, $hour,$min,$sec) = epochToArray(addToDate($_[0],0,1,0));
	#$end = arrayToEpoch($year,$month,1,0,0,0)-1;
	#return ($start, $end);
    my ($year,$month,$day, $hour,$min,$sec, $start, $end);
    ($year,$month,$day, $hour,$min,$sec) = epochToArray($_[0]);
    $start = &arrayToEpoch($year,$month,1,0,0,0) + 0;
    $end = &UnixDate(&DateCalc(&epochToDate($start), "+1 month"),'%s')-1;
    return ($start, $end);
}

#-------------------------------------------------------------------

=head2 secondsToInterval ( seconds )

Returns an interval and units derived the number of seconds.

=over

=item seconds

The number of seconds in the interval. 

=back

=cut

sub secondsToInterval {
	my ($interval, $units);
	if ($_[0] >= 31536000) {
		$interval = round($_[0]/31536000);
		$units = "years";
	} elsif ($_[0] >= 2592000) {
                $interval = round($_[0]/2592000);
                $units = "months";
	} elsif ($_[0] >= 604800) {
                $interval = round($_[0]/604800);
                $units = "weeks";
	} elsif ($_[0] >= 86400) {
                $interval = round($_[0]/86400);
                $units = "days";
        } elsif ($_[0] >= 3600) {
                $interval = round($_[0]/3600);
                $units = "hours";
        } elsif ($_[0] >= 60) {
                $interval = round($_[0]/60);
                $units = "minutes";
        } else {
                $interval = $_[0];
                $units = "seconds";
	}
	return ($interval, $units);
}

#-------------------------------------------------------------------

=head2 secondsToTime ( seconds )

Returns a time string of the format HH::MM::SS on a 24 hour clock. See also timeToSeconds().

=over

=item seconds

A number of seconds. 

=back

=cut

sub secondsToTime {
	my $seconds = $_[0];
	my $timeString = sprintf("%02d",int($seconds / 3600)).":";
	$seconds = $seconds % 3600;	
	$timeString .= sprintf("%02d",int($seconds / 60)).":";
	$seconds = $seconds % 60;
	$timeString .= sprintf("%02d",$seconds);
	return $timeString;
}


#-------------------------------------------------------------------

=head2 setToEpoch ( set )

Returns an epoch date.

=over

=item set

A string in the format of YYYY-MM-DD or YYYY-MM-DD HH:MM:SS.

=back

=cut

sub setToEpoch {
	my @now = epochToArray(WebGUI::DateTime::time());
 	my ($month, $day, $year) = split(/\//,$_[0]);
	if (int($year) < 2038 && int($year) > 1969) {
		$year = int($year);
	} else {
		$year = $now[0];
	}
        if (int($month) < 13 && int($month) > 0) {
                $month = int($month);
        } else {
                $month = $now[1]++;
        }
        if (int($day) < 32 && int($day) > 0) {
                $day = int($day);
        } else {
                $day = $now[2];
        }
	return arrayToEpoch($year,$month,$day,0,0,0);
}

#-------------------------------------------------------------------

=head2 time ( )

Returns an epoch date for now.

=cut

sub time {
	return dateToEpoch(&ParseDate("now"));
}

#-------------------------------------------------------------------

=head2 timeToSeconds ( timeString )

Returns the seconds since 00:00:00 on a 24 hour clock.

=over

=item timeString

A string that looks similar to this: 15:05:32

=back

=cut

sub timeToSeconds {
	my ($hour,$min,$sec) = split(/:/,$_[0]);
	return ($hour*3600+$min*60+$sec);
}


1;
