Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

efficient determination of in/out of hours including Bank Holidays

by Random_Walk (Parson)
on Oct 17, 2013 at 10:10 UTC ( #1058615=CUFP: print w/ replies, xml ) Need Help??

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!

Comment on efficient determination of in/out of hours including Bank Holidays
Select or Download Code
Re: efficient determination of in/out of hours
by soonix (Priest) on Oct 17, 2013 at 13:52 UTC
    next if $bh_start < time; # history
    If the program ist started on a holiday (even 1 second past midnight), the routine will think it is a workday... I wanted to propose
    ... use integer; my $today = time / 86400 * 86400; # chop hours, minutes, seconds while (<DATA>) { # read bank holiday file ... next if $bh_start < $today; # history ... } ...
    but this doesn't work correctly, either, because time is in seconds since some 1970-01-01 UTC...

      Nice catch, thanks. Perhaps something like this will fix it

      my $now = time; my $today = $now - ($now % 86400); while (<DATA>) { # read bank holiday file ... next if $bh_start < $today; # history ... } ...

      I gotta run home now, will test it really works later

      Update

      Sadly it does not work. It looks lie POSIX mktime is based on GMT, localtime, of course is not. I added the above change, plus a bit of printing and a bank holiday for today (localtime now: 2116, GMT 2016) running the code it does skip todays bank hol (17th Oct 2013)

      start of day (epoch based):Thu Oct 17 01:00:00 2013 Bank Hols Mon Oct 21 00:00:00 2013 Mon Feb 3 00:00:00 2014

      Is there a better way to get timezone offset than to mktime for epoch + 1 day and comapare to localtime for the same? Can I trust the environment, or is there a magic Perl var?

      Cheers,
      R.

      Pereant, qui ante nos nostra dixerunt!
        That's mathematically the same. (If 86400 were a power of 2, the most efficient way 'd be >> and <<.) However time and mktime refer to different timezones. Unless you happen to be in Iceland, there are some hours when this works wrong...
        Update: I posted this while you updated your post :-)
        I'm not at my computer, but perhaps something like mktime(gmtime) for determining today's midnight could work...

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: CUFP [id://1058615]
Approved by moritz
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (6)
As of 2015-07-03 06:44 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (48 votes), past polls