Hello dear monks, fair and foul
I have a monitoring alert gateway. It must invoke different behaviours in working hours and out of hours. Sometimes it may be asked to forward 1000/sec. Rather than run a test for in/out of hours on each event, I decided to have a flag for ooh, and a record of the epoch time when the next change will happen. This way, most of the time I just check the flag and its validity. Only a couple of times a day, do I need to work out if its in hours, weekend, bank holiday, etc.
This script builds a closure which knows the bank hols and working hours. When it is invoked with an epoch time, it will return a flag for out of hours, and the epoch time until which this is valid.
There is a minor inefficiency when Monday is a holiday. At the end of Friday it will return Mondays start time as the limit of validity for the OOH flag. This is not a big problem, as when it is called then to find the next validity point, it will see it is in a bank holiday and act accordingly
All suggestions/comments/improvements/missed corner cases welcome
Update
soonix++ for spotting a problem. There were timezone issues with the bank holiday code (see following thread). This version should have fixed them, the original version is left below. I have also made working days a parameter, I am aware we don't all work mon..fri
Update 2
I have also added correction for when the valid until time falls across a daylight savings time boundary.
#!/usr/bin/perl
use strict;
use warnings;
use POSIX qw(mktime);
use Data::Dumper;
use 5.014;
sub ooh {
my $start = shift; my $end = shift;
my %wkwk = map {$_ => 1} @_;
my @start = reverse split /:/, $start; # deliciously
my @end = reverse split /:/, $end; # naughty
my $i;
my %mns=map{$_=>$i++}qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct No
+v Dec);
# Bank holidays
# We assume they all begin at 00:00:00 and encompas the entire day
# POSIX::mktime(15, 28, 06, 27, 02, 206);'
my @bh;
my $today = join ":", reverse +(localtime time)[3 .. 5];
while (<DATA>) { # read bank holiday file
next unless /\d\d?\s+\w\w\w\s+\d{4}/;
chomp;
my ($day, $month, $year) = split;
my $bh_start = join ":", $year-1900, $mns{$month}, $day;
next if $bh_start lt $today; # history
push @bh, $bh_start;
}
@bh = sort @bh;
return sub {
my $epoch = shift;
my ($s, $m, $h, $date, $mnth, $yr, $day) = +(localtime $epoch)
+[0 .. 6];
# Check if we are out of hours, and when next flip is due
my ($valid, $ooh);
if ("$yr:$mnth:$date" eq $bh[0]) { # hooray! bank holiday
shift @bh; # rip page from calendar
$ooh = 1;
print "Bank Holiday ";
}
if (not $ooh and $wkwk{$day}) { # not a bank hol, is a working
+ day
my $time = sprintf "%02d:%02d:%02d", $h,$m,$s;
if ($time lt $start) { # not out of bed yet
print "Early doors ";
$valid = POSIX::mktime(@start, $date, $mnth, $yr);
$ooh = 1;
}
elsif ($time lt $end) { # came in, dreaming of home
print "Working hours ";
$valid = POSIX::mktime(@end, $date, $mnth, $yr);
$ooh = 0;
}
else {
print "G'night ";
}
}
else {
print "Weekend " unless $ooh;
}
unless ($valid) { # we did not establish our validity limit ye
+t
# we are at end of day, weekend or hols. Find next working
+ day
my $add = 1;
++$add until $wkwk{($day + $add)%7};
print "next working day is " .($day + $add)%7 . " ";
$valid = POSIX::mktime(@start, $date, $mnth, $yr);
$valid += $add * (24*60*60);
# Daylight savings adjustment
my $dst = $start[2] - +(localtime $valid)[2];
$valid += $dst * 60 * 60;
$ooh = 1
}
return $ooh, $valid;
}
}
# get an ooh tester, work week mon..fri
my $ooh_check = ooh('08:15:00', '17:45:00', 1..5);
# and run some tests
for (sort (time, 1382310123, 1359806999, 1360306452,
1381941000, 1360016452, 1382823512)) {
my ($ooh, $valid) = $ooh_check->($_);
say join " - ", scalar localtime $_, $ooh, scalar localtime $valid
+;
}
__DATA__
02 Feb 2013
18 Oct 2013
21 Oct 2013
23 Oct 2013
This is the original, broken version
use strict;
use warnings;
use POSIX qw(strftime mktime);
use Data::Dumper;
sub ooh {
my $start = shift;
my $end = shift;
my %wkwk = map {$_ => 1} (1..5); # Mon .. Fri
my $i;
my %mns = map{$_=>$i++}qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct
+Nov Dec);
# Bank holidays
# We assume they all begin at 00:00:00 and encompas the entire day
# POSIX::mktime(15, 28, 06, 27, 02, 206);'
my @bh;
while (<DATA>) { # read bank holiday file
next unless /\d\d?\s+\w\w\w\s+\d{4}/;
chomp;
my ($day, $month, $year) = split;
my $mnth = $mns{$month};
my $bh_start = POSIX::mktime(00, 00, 00, $day, $mnth, $year -
+1900);
next if $bh_start < time; # history
push @bh, $bh_start;
}
@bh = sort @bh;
return sub {
my $epoch = shift;
my ($s, $m, $h, $date, $mnth, $yr, $day) = +(localtime $epoch)
+[0 .. 6];
print $/;
print scalar localtime $epoch;
# Check if we are out of hours, and when next flip is due
my $valid;
my $ooh;
if ($epoch > $bh[0]) { # its a new bank holiday
shift @bh; # rip this page from the calendar
$ooh = 1;
print " Bank Holiday";
}
if (not $ooh and $wkwk{$day}) { # not a bank hol, is a working
+ day
my $time = sprintf "%02d:%02d:%02d", $h,$m,$s;
print " given: $time ";
if ($time lt $start) { # not out of bed yet
print "early doors ";
($h, $m, $s) = split /:/, $start;
$valid = POSIX::mktime($s, $m, $h, $date, $mnth, $yr);
return 1, $valid;
}
elsif ($time lt $end) { # came in, dreaming of home
print "within working hours ";
($h, $m, $s) = split /:/, $end;
$valid = POSIX::mktime($s, $m, $h, $date, $mnth, $yr);
return 0, $valid;
}
else {
print "g'night";
}
}
else {
print " weekend" unless $ooh;
}
# find next working day
my $add = 1;
++$add until $wkwk{($day + $add)%7};
print " Next working day is " .($day + $add)%7 . " ";
# end of day, weekend or hols
($h, $m, $s) = split /:/, $start;
$valid = POSIX::mktime($s, $m, $h, $date, $mnth, $yr);
$valid+= $add * (24*60*60);
return 1, $valid;
}
}
# get an ooh tester
my $ooh_check = ooh('08:15:00', '17:45:00');
# and run some tests
print join " -> ", 'OOH', $ooh_check->(1382310123);
print join " -> ", 'OOH', $ooh_check->(1359806999);
print join " -> ", 'OOH', $ooh_check->(1360306452);
print join " -> ", 'OOH', $ooh_check->(1381941000);
print join " -> ", 'OOH', $ooh_check->(1360016452);
print join " -> ", 'OOH', $ooh_check->(time);
__DATA__
01 Jan 2012
02 Feb 2013
03 Feb 2014
04 Aug 2013
21 Oct 2013
Cheers,
R.
Pereant, qui ante nos nostra dixerunt!
-
Are you posting in the right place? Check out Where do I post X? to know for sure.
-
Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
<code> <a> <b> <big>
<blockquote> <br /> <dd>
<dl> <dt> <em> <font>
<h1> <h2> <h3> <h4>
<h5> <h6> <hr /> <i>
<li> <nbsp> <ol> <p>
<small> <strike> <strong>
<sub> <sup> <table>
<td> <th> <tr> <tt>
<u> <ul>
-
Snippets of code should be wrapped in
<code> tags not
<pre> tags. In fact, <pre>
tags should generally be avoided. If they must
be used, extreme care should be
taken to ensure that their contents do not
have long lines (<70 chars), in order to prevent
horizontal scrolling (and possible janitor
intervention).
-
Want more info? How to link
or How to display code and escape characters
are good places to start.