File Coverage

blib/lib/Time/Available.pm
Criterion Covered Total %
statement 171 197 86.8
branch 51 100 51.0
condition 22 66 33.3
subroutine 24 26 92.3
pod 0 6 0.0
total 268 395 67.8


line stmt bran cond sub pod time code
1             package Time::Available;
2              
3 1     1   25581 use 5.001;
  1         4  
  1         43  
4 1     1   6 use strict;
  1         1  
  1         37  
5 1     1   5 use warnings;
  1         4  
  1         37  
6 1     1   6 use Carp;
  1         3  
  1         105  
7 1     1   1071 use Time::Local;
  1         1899  
  1         186  
8              
9             require Exporter;
10              
11             our @ISA = qw(Exporter);
12              
13             our %EXPORT_TAGS = (
14             'days' => [ qw(
15             DAY_MONDAY
16             DAY_TUESDAY
17             DAY_WEDNESDAY
18             DAY_THURSDAY
19             DAY_FRIDAY
20             DAY_SATURDAY
21             DAY_SUNDAY
22             DAY_WEEKDAY
23             DAY_WEEKEND
24             DAY_EVERYDAY
25             ) ],
26             'fmt_interval' => [ qw(fmt_interval) ]
27             );
28              
29             our @EXPORT_OK = (
30             @{ $EXPORT_TAGS{'days'} },
31             @{ $EXPORT_TAGS{'fmt_interval'} }
32             );
33              
34             our @EXPORT; # don't export anything by default!
35              
36             our $VERSION = '0.05';
37              
38             # define some constants used later
39 1     1   7 use constant DAY_MONDAY => 0x01;
  1         2  
  1         133  
40 1     1   6 use constant DAY_TUESDAY => 0x02;
  1         2  
  1         41  
41 1     1   5 use constant DAY_WEDNESDAY => 0x04;
  1         2  
  1         40  
42 1     1   5 use constant DAY_THURSDAY => 0x08;
  1         2  
  1         43  
43 1     1   13 use constant DAY_FRIDAY => 0x10;
  1         2  
  1         49  
44 1     1   5 use constant DAY_SATURDAY => 0x20;
  1         1  
  1         40  
45 1     1   5 use constant DAY_SUNDAY => 0x40;
  1         2  
  1         43  
46 1     1   5 use constant DAY_WEEKDAY => 0x1F;
  1         2  
  1         44  
47 1     1   5 use constant DAY_WEEKEND => 0x60;
  1         2  
  1         41  
48 1     1   5 use constant DAY_EVERYDAY => 0x7F;
  1         1  
  1         42  
49              
50 1     1   5 use constant SEC_PER_DAY => 86400;
  1         1  
  1         2834  
51              
52             my $debug = 0;
53              
54             #
55             # make new instance
56             #
57             sub new {
58 5     5 0 715 my $class = shift;
59 5         10 my $self = {};
60 5         17 bless($self, $class);
61 5         32 $self->{ARGS} = {@_};
62 5         10 $debug = $self->{ARGS}->{DEBUG};
63              
64 5 50       18 croak("need start time") if (! defined($self->{ARGS}->{start}));
65              
66             # calc start and stop seconds
67 5         22 my ($hh,$mm,$ss) = split(/:/,$self->{ARGS}->{start},3);
68 5 50 0     13 print STDERR "new: start time ",$hh||0,":",$mm||0,":",$ss||0,"\n" if ($debug);
      0        
      0        
69 5 50       14 croak("need at least hour specified for start time") if (! defined($hh));
70 5         9 $mm |= 0;
71 5         7 $ss |= 0;
72 5         15 $self->{start_arr} = [$ss,$mm,$hh];
73              
74 5         8 my $start = $hh;
75 5         12 $start *= 60;
76 5         6 $start += $mm;
77 5         8 $start *= 60;
78 5         5 $start += $ss;
79              
80 5 50       17 croak("need end time") if (! defined($self->{ARGS}->{end}));
81              
82 5         19 ($hh,$mm,$ss) = split(/:/,$self->{ARGS}->{end},3);
83 5 50 0     14 print STDERR "new: end time ",$hh||0,":",$mm||0,":",$ss||0,"\n" if ($debug);
      0        
      0        
84 5 50       12 croak("need at least hour specified for end time") if (! defined($hh));
85 5         8 $mm |= 0;
86 5         6 $ss |= 0;
87 5         10 $self->{end_arr} = [$ss,$mm,$hh];
88              
89 5         11 my $end = $hh;
90 5         8 $end *= 60;
91 5         4 $end += $mm;
92 5         7 $end *= 60;
93 5         6 $end += $ss;
94              
95 5 50       15 croak("need dayMask specified") if (! defined($self->{ARGS}->{dayMask}));
96              
97 5         10 $self->{dayMask} = $self->{ARGS}->{dayMask};
98              
99             # over midnight?
100 5 100       15 if ($start > $end) {
101 1         4 $self->{sec_in_interval} = (86400 - $start + $end);
102             } else {
103 4         7 $self->{sec_in_interval} = ($end - $start);
104             }
105 5 50       20 $self ? return $self : return undef;
106             }
107              
108             #
109             # this sub (originally from Time::Avail) will return if day is applicable
110             #
111              
112             sub _dayOk($) {
113 14     14   28 my $self = shift;
114 14   100     39 my $day = shift || 0;
115              
116 14         21 my $dayMask = $self->{dayMask};
117              
118 14         16 my $dayOk = 0;
119              
120 14 100 100     137 if( ( $day == 0 ) && ( $dayMask & DAY_SUNDAY ) ) {
    100 66        
    50 33        
    100 66        
    100 66        
    50 33        
    50 33        
121 1         3 $dayOk = 1;
122             } elsif( ( $day == 1) && ( $dayMask & DAY_MONDAY ) ) {
123 2         4 $dayOk = 1;
124             } elsif( ($day == 2) && ( $dayMask & DAY_TUESDAY ) ) {
125 0         0 $dayOk = 1;
126             } elsif( ($day == 3) && ( $dayMask & DAY_WEDNESDAY ) ) {
127 1         2 $dayOk = 1;
128             } elsif( ( $day == 4) && ( $dayMask & DAY_THURSDAY ) ) {
129 9         10 $dayOk = 1;
130             } elsif( ( $day == 5 ) && ( $dayMask & DAY_FRIDAY ) ) {
131 0         0 $dayOk = 1;
132             } elsif( ( $day == 6 ) && ( $dayMask & DAY_SATURDAY ) ) {
133 0         0 $dayOk = 1;
134             }
135              
136 14 50       27 print STDERR "day: $day dayMask: ",unpack("B32", pack("N", $dayMask))," ok: $dayOk\n" if ($debug);
137              
138 14         42 return $dayOk;
139             }
140              
141             #
142             # calculate start and end of interval in given day
143             #
144              
145             sub _start {
146 13     13   14 my $self = shift;
147 13   33     28 my $t = shift || croak "_start needs timestap";
148              
149 13         218 my @lt = localtime($t);
150 13         33 $lt[0] = $self->{start_arr}[0];
151 13         16 $lt[1] = $self->{start_arr}[1];
152 13         25 $lt[2] = $self->{start_arr}[2];
153 13         44 return timelocal(@lt);
154             }
155              
156             sub _end {
157 11     11   15 my $self = shift;
158 11   33     23 my $t = shift || croak "_end needs timestap";
159              
160 11         176 my @lt = localtime($t);
161 11         23 $lt[0] = $self->{end_arr}[0];
162 11         17 $lt[1] = $self->{end_arr}[1];
163 11         18 $lt[2] = $self->{end_arr}[2];
164 11         27 return timelocal(@lt);
165             }
166              
167             #
168             # this will return number of seconds that service is available if passed
169             # uptime of service
170             #
171              
172             sub _t {
173 0   0 0   0 my $t = shift || die "no t?";
174 0         0 return "$t [" . localtime($t) . "]";
175             }
176              
177             sub uptime {
178 10     10 0 3290 my $self = shift;
179              
180 10   33     24 my $time = shift || croak "need uptime timestamp to calculate uptime";
181              
182             # calculate offset -- that is number of seconds since midnight
183 10         285 my @lt = localtime($time);
184              
185             # check if day falls into dayMask
186 10 100       29 return 0 if (! $self->_dayOk($lt[6]) );
187              
188 9         14 my $s=0;
189              
190 9         23 my $start = $self->_start($time);
191 9         485 my $end = $self->_end($time);
192              
193 9 50       390 print STDERR "uptime start: ",_t($start)," end: ",_t($end)," time: $time [$lt[2]:$lt[1]:$lt[0]]\n" if ($debug);
194              
195 9 100       24 if ( $end > $start ) {
    50          
196 5 100       13 if ($time < $start) {
    100          
197 1         2 $s = $end - $start;
198             } elsif ($time < $end) {
199 3         5 $s = $end - $time;
200             }
201             } elsif ( $start > $end ) { # over midnight
202 4 100       8 if ( $time < $end ) {
203 1 50       2 if ( $time < $start) {
204 1         2 $s = SEC_PER_DAY - $start + $end - $time;
205             } else {
206 0         0 $s = SEC_PER_DAY - $start + $end;
207             }
208             } else {
209 3 50       6 if ( $time < $start ) {
210 3         5 $s = SEC_PER_DAY - $start;
211             } else {
212 0         0 $s = SEC_PER_DAY - $time;
213             }
214             }
215             }
216            
217 9         36 return $s;
218             }
219              
220             #
221             # this will return number of seconds that service is available if passed
222             # downtime of service
223             #
224              
225             sub downtime {
226 2     2 0 3 my $self = shift;
227              
228 2   33     5 my $time = shift || croak "need downtime timestamp to calculate uptime";
229              
230             # calculate offset -- that is number of seconds since midnight
231 2         27 my @lt = localtime($time);
232              
233             # check if day falls into dayMask
234 2 50       8 return 0 if (! $self->_dayOk($lt[6]) );
235              
236 2         18 my $s=0;
237              
238 2         5 my $start = $self->_start($time);
239 2         70 my $end = $self->_end($time);
240              
241 2 50       69 print STDERR "downtime start: ",_t($start)," end: ",_t($end)," time: $time [$lt[2]:$lt[1]:$lt[0]]\n" if ($debug);
242              
243 2 50       5 if ( $end > $start ) {
    0          
244 2 50 33     8 if ($time > $start && $time <= $end) {
    50          
245 0         0 $s = $end - $time;
246             } elsif ($time < $start) {
247 2         3 $s = 0;
248             }
249             } elsif ( $start > $end ) { # over midnight
250 0 0       0 if ( $time < $end ) {
251 0 0       0 if ( $time < $start) {
252 0         0 $s = $time;
253             } else {
254 0         0 $s = 0;
255             }
256             } else {
257 0 0       0 if ( $time < $start ) {
258 0         0 $s = SEC_PER_DAY - $end;
259             } else {
260 0         0 $s = SEC_PER_DAY - $end + $start - $time;
261             }
262             }
263             }
264            
265 2         5 return $s;
266             }
267              
268             #
269             # this auxillary function will pretty-format interval in [days]d hh:mm:ss
270             #
271              
272             sub fmt_interval {
273 1   50 1 0 4581 my $int = shift || 0;
274 1         2 my $out = "";
275              
276 1         2 my $s=$int;
277 1         3 my $d = int($s/(24*60*60));
278 1         2 $s = $s % (24*60*60);
279 1         2 my $h = int($s/(60*60));
280 1         1 $s = $s % (60*60);
281 1         5 my $m = int($s/60);
282 1         2 $s = $s % 60;
283            
284 1 50       4 $out .= $d."d " if ($d > 0);
285              
286 1 50       3 if ($debug) {
287 0         0 $out .= sprintf("%02d:%02d:%02d [%d]",$h,$m,$s, $int);
288             } else {
289 1         4 $out .= sprintf("%02d:%02d:%02d",$h,$m,$s);
290             }
291              
292 1         4 return $out;
293             }
294              
295             #
296             # this function will calculate uptime for some interval
297             #
298              
299             sub interval {
300 2     2 0 11 my $self = shift;
301 2   33     6 my $from = shift || croak "need start time for interval";
302 2   33     5 my $to = shift || croak "need end time for interval";
303              
304 2 50       5 print STDERR "from:\t",_t($from),"\n" if ($debug);
305 2 50       5 print STDERR "to:\t",_t($to),"\n" if ($debug);
306              
307 2         2 my $total = 0;
308              
309             # calc first day availability
310 2 50       5 print STDERR "t:\t",_t($from),"\n" if ($debug);
311 2         8 $total += $self->uptime($from);
312              
313 2 50       6 print STDERR "total: ",fmt_interval($total)," (first)\n" if ($debug);
314              
315             # add all whole days
316              
317 2         4 my $sec_in_day = $self->{sec_in_interval};
318 2         2 my $day = 86400; # 24*60*60
319              
320 2         5 my $loop_start_time = int(${from}/${day})*$day + $day;
321 2         3 my $loop_end_time = int(${to}/${day})*$day;
322              
323 2 50       5 print STDERR "loop (start - end): $loop_start_time - $loop_end_time\n" if ($debug);
324              
325 2         6 for (my $t = $loop_start_time; $t < $loop_end_time; $t += $day) {
326 0 0       0 print STDERR "t:\t",_($t),"\n" if ($debug);
327 0 0       0 $total += $sec_in_day if ($self->day_in_interval($t));
328 0 0       0 print STDERR "total: ",fmt_interval($total)," (loop)\n" if ($debug);
329             }
330              
331             # add rest of last day
332 2 50       6 print STDERR "t:\t",_t($to),"\n" if ($debug);
333              
334 2 50       14 if ($to > $self->_start($to)) {
335 0 0 0     0 if ($to <= $self->_end($to)) {
    0          
336 0         0 $total += ( $to - $self->_start($to) );
337             } elsif($self->day_in_interval($to) && $loop_start_time < $loop_end_time) {
338 0         0 $total += $sec_in_day;
339             }
340             } else {
341 2         126 $total = abs($total - $self->downtime($to));
342             }
343 2 50       3 print STDERR "total: ",fmt_interval($total)," (final)\n" if ($debug);
344              
345 2         8 return $total;
346             }
347              
348             #
349             # this function will check if day falls into interval
350             #
351              
352             sub day_in_interval {
353 0     0 0   my $self = shift;
354              
355 0   0       my $time = shift || croak "need timestamp to check if day is in interval";
356              
357 0           my @lt = localtime($time);
358 0           return $self->_dayOk($lt[6]);
359             }
360              
361             #
362             # return seconds in defined interval
363             #
364              
365              
366             1;
367             __END__