--- Available.pm 2003/10/03 16:40:00 1.3 +++ Available.pm 2003/12/02 12:41:39 1.12 @@ -3,6 +3,8 @@ use 5.001; use strict; use warnings; +use Carp; +use Time::Local; require Exporter; @@ -31,7 +33,7 @@ our @EXPORT; # don't export anything by default! -our $VERSION = '0.01'; +our $VERSION = '0.03'; # define some constants used later use constant DAY_MONDAY => 0x01; @@ -59,38 +61,57 @@ $self->{ARGS} = {@_}; $debug = $self->{ARGS}->{DEBUG}; - die("need start time") if (! $self->{ARGS}->{start}); + croak("need start time") if (! defined($self->{ARGS}->{start})); # calc start and stop seconds my ($hh,$mm,$ss) = split(/:/,$self->{ARGS}->{start},3); - my $s = $hh * 3600 || die("need at least hour specified for start time"); - $s += $mm * 60 if ($mm); - $s += $ss if ($ss); - $self->{start} = $s; + print STDERR "new: start time ",$hh||0,":",$mm||0,":",$ss||0,"\n" if ($debug); + croak("need at least hour specified for start time") if (! defined($hh)); + $mm |= 0; + $ss |= 0; + $self->{start_arr} = [$ss,$mm,$hh]; + + my $start = $hh; + $start *= 60; + $start += $mm; + $start *= 60; + $start += $ss; - die("need end time") if (! $self->{ARGS}->{end}); + croak("need end time") if (! defined($self->{ARGS}->{end})); ($hh,$mm,$ss) = split(/:/,$self->{ARGS}->{end},3); - $s = $hh * 3600 || die("need at least hour specified for end time"); - $s += $mm * 60 if ($mm); - $self->{end} = $s; + print STDERR "new: end time ",$hh||0,":",$mm||0,":",$ss||0,"\n" if ($debug); + croak("need at least hour specified for end time") if (! defined($hh)); + $mm |= 0; + $ss |= 0; + $self->{end_arr} = [$ss,$mm,$hh]; + + my $end = $hh; + $end *= 60; + $end += $mm; + $end *= 60; + $end += $ss; - die("need dayMask specified") if (! $self->{ARGS}->{dayMask}); + croak("need dayMask specified") if (! defined($self->{ARGS}->{dayMask})); $self->{dayMask} = $self->{ARGS}->{dayMask}; + # over midnight? + if ($start > $end) { + $self->{sec_in_interval} = (86400 - $start + $end); + } else { + $self->{sec_in_interval} = ($end - $start); + } $self ? return $self : return undef; } - - # # this sub (originally from Time::Avail) will return if day is applicable # sub _dayOk($) { my $self = shift; - my $day = shift || return; + my $day = shift || 0; my $dayMask = $self->{dayMask}; @@ -117,6 +138,31 @@ return $dayOk; } +# +# calculate start and end of interval in given day +# + +sub _start { + my $self = shift; + my $t = shift || croak "_start needs timestap"; + + my @lt = localtime($t); + $lt[0] = $self->{start_arr}[0]; + $lt[1] = $self->{start_arr}[1]; + $lt[2] = $self->{start_arr}[2]; + return timelocal(@lt); +} + +sub _end { + my $self = shift; + my $t = shift || croak "_end needs timestap"; + + my @lt = localtime($t); + $lt[0] = $self->{end_arr}[0]; + $lt[1] = $self->{end_arr}[1]; + $lt[2] = $self->{end_arr}[2]; + return timelocal(@lt); +} # # this will return number of seconds that service is available if passed @@ -126,44 +172,87 @@ sub uptime { my $self = shift; - my $time = shift || die "need uptime timestamp to calcualte uptime"; + my $time = shift || croak "need uptime timestamp to calculate uptime"; # calculate offset -- that is number of seconds since midnight my @lt = localtime($time); - my $offset = $lt[2]; # hour - $offset *= 60; # convert to minutes - $offset += $lt[1]; # minutes - $offset *= 60; # convert to seconds - $offset += $lt[0]; # check if day falls into dayMask return 0 if (! $self->_dayOk($lt[6]) ); my $s=0; - my $start = $self->{start}; - my $end = $self->{end}; + my $start = $self->_start($time); + my $end = $self->_end($time); - print STDERR "start: $start end: $end time: $offset\n" if ($debug); + print STDERR "start: $start end: $end time: $time [$lt[2]:$lt[1]:$lt[0]]\n" if ($debug); if ( $end > $start ) { - if ($offset < $start) { + if ($time < $start) { $s = $end - $start; - } elsif ($offset < $end) { - $s = $end - $offset; + } elsif ($time < $end) { + $s = $end - $time; } } elsif ( $start > $end ) { # over midnight - if ( $offset < $end ) { - if ( $offset < $start) { - $s = SEC_PER_DAY - $start + $end - $offset; + if ( $time < $end ) { + if ( $time < $start) { + $s = SEC_PER_DAY - $start + $end - $time; } else { $s = SEC_PER_DAY - $start + $end; } } else { - if ( $offset < $start ) { + if ( $time < $start ) { $s = SEC_PER_DAY - $start; } else { - $s = SEC_PER_DAY - $offset; + $s = SEC_PER_DAY - $time; + } + } + } + + return $s; +} + +# +# this will return number of seconds that service is available if passed +# downtime of service +# + +sub downtime { + my $self = shift; + + my $time = shift || croak "need downtime timestamp to calculate uptime"; + + # calculate offset -- that is number of seconds since midnight + my @lt = localtime($time); + + # check if day falls into dayMask + return 0 if (! $self->_dayOk($lt[6]) ); + + my $s=0; + + my $start = $self->_start($time); + my $end = $self->_end($time); + + print STDERR "start: $start end: $end time: $time [$lt[2]:$lt[1]:$lt[0]]\n" if ($debug); + + if ( $end > $start ) { + if ($time > $start && $time <= $end) { + $s = $end - $time; + } elsif ($time < $start) { + $s = $end - $start; + } + } elsif ( $start > $end ) { # over midnight + if ( $time < $end ) { + if ( $time < $start) { + $s = $time; + } else { + $s = 0; + } + } else { + if ( $time < $start ) { + $s = SEC_PER_DAY - $end; + } else { + $s = SEC_PER_DAY - $end + $start - $time; } } } @@ -176,9 +265,10 @@ # sub fmt_interval { - my $s = shift || 0; + my $int = shift || 0; my $out = ""; + my $s=$int; my $d = int($s/(24*60*60)); $s = $s % (24*60*60); my $h = int($s/(60*60)); @@ -188,11 +278,86 @@ $out .= $d."d " if ($d > 0); - $out .= sprintf("%02d:%02d:%02d",$h,$m,$s); + if ($debug) { + $out .= sprintf("%02d:%02d:%02d [%d]",$h,$m,$s, $int); + } else { + $out .= sprintf("%02d:%02d:%02d",$h,$m,$s); + } return $out; } +# +# this function will calculate uptime for some interval +# + +sub interval { + my $self = shift; + my $from = shift || croak "need start time for interval"; + my $to = shift || croak "need end time for interval"; + + print STDERR "from:\t$from\t",scalar localtime($from),"\n" if ($debug); + print STDERR "to:\t$to\t",scalar localtime($to),"\n" if ($debug); + + my $total = 0; + + # calc first day availability + print STDERR "t:\t$from\t",scalar localtime($from),"\n" if ($debug); + $total += $self->uptime($from); + + print STDERR "total: ",fmt_interval($total)," (first)\n" if ($debug); + + # add all whole days + + my $sec_in_day = $self->{sec_in_interval}; + my $day = 86400; # 24*60*60 + + my $loop_start_time = int($from/$day)*$day + $day; + my $loop_end_time = int($to/$day)*$day; + + print STDERR "loop (start - end): $loop_start_time - $loop_end_time\n" if ($debug); + + for (my $t = $loop_start_time; $t < $loop_end_time; $t += $day) { + print STDERR "t:\t$t\t",scalar localtime($t),"\n" if ($debug); + $total += $sec_in_day if ($self->day_in_interval($t)); + print STDERR "total: ",fmt_interval($total)," (loop)\n" if ($debug); + } + + # add rest of last day + print STDERR "t:\t$to\t",scalar localtime($to),"\n" if ($debug); + + if ($to > $self->_start($to)) { + if ($to <= $self->_end($to)) { + $total = abs($total - $self->downtime($to)); + } elsif($self->day_in_interval($to) && $loop_start_time < $loop_end_time) { + $total += $sec_in_day; + } + } else { + $total = abs($total - $self->downtime($to)); + } + print STDERR "total: ",fmt_interval($total)," (final)\n" if ($debug); + + return $total; +} + +# +# this function will check if day falls into interval +# + +sub day_in_interval { + my $self = shift; + + my $time = shift || croak "need timestamp to check if day is in interval"; + + my @lt = localtime($time); + return $self->_dayOk($lt[6]); +} + +# +# return seconds in defined interval +# + + 1; __END__ @@ -213,9 +378,12 @@ my $interval = new( start=>'07:00', stop=>'17:00', dayMask=> DAY_WEEKDAY ); - # calculate current availability in seconds + # calculate current uptime availability from now in seconds print $interval->uptime(localtime); + # calculate maximum downtime in seconds from current moment + print $interval->downtime(localtime); + # calculate availablity in seconds from interval of uptime print $interval->interval($utime1,$utime2); @@ -226,12 +394,17 @@ =head1 DESCRIPTION Time::Available is used to calculate availability of some resource if start -end end time of availability is available. That availability is calculated +and end time of availability is supplied. Availability is calculated relative to some interval which is defined when new instance of module is created. Start and end dates must be specified in 24-hour format. You can specify -just hour, hour:minute or hour:minute:seconds format. +just hour, hour:minute or hour:minute:seconds format. Start and end time is +specified in your B. Timestamp, are specified in unix +utime, and module will take care of recalculating (using C and +C when needed). There is one small canvat here: module is assuing +that time you are specifing is in same time zone in which your module is +running (that is from local system). The B parameter is constructed by OR'ing together one or more of the following dayMask constants: @@ -239,38 +412,38 @@ =over 4 =item * -Time::Avail::DAY_MONDAY +Time::Available::DAY_MONDAY =item * -Time::Avail::DAY_TUESDAY +Time::Available::DAY_TUESDAY =item * -Time::Avail::DAY_WEDNESDAY +Time::Available::DAY_WEDNESDAY =item * -Time::Avail::DAY_THURSDAY +Time::Available::DAY_THURSDAY =item * -Time::Avail::DAY_FRIDAY +Time::Available::DAY_FRIDAY =item * -Time::Avail::DAY_SATURDAY +Time::Available::DAY_SATURDAY =item * -Time::Avail::DAY_SUNDAY +Time::Available::DAY_SUNDAY =item * -Time::Avail::DAY_WEEKDAY +Time::Available::DAY_WEEKDAY =item * -Time::Avail::DAY_WEEKEND +Time::Available::DAY_WEEKEND =item * -Time::Avail::DAY_EVERYDAY +Time::Available::DAY_EVERYDAY =back -FIXME +They should be self-explainatory. =head2 EXPORT @@ -299,18 +472,16 @@ =over 8 =item * -Use croak and not die in module for better error handling - -=item * Allow arbitary (array?) of holidays to be included. =back =head1 SEE ALSO -Time::Avail is CPAN module that started it all. However, it lacked +L is CPAN module that started it all. However, it lacked calculating of availability of some interval and precision in seconds, so -this module was born. +this module was born. It also had some bugs in dayMask which where reported +to author, but his e-mail address bounced. More information about this module might be found on http://www.rot13.org/~dpavlin/projects.html#cpan