The weekly challenge 259 - Task 1: Banking Day Offset

 1 #!/usr/bin/env perl
 2 # https://theweeklychallenge.org/blog/perl-weekly-challenge-259/#TASK1
 3 #
 4 # Task 1: Banking Day Offset
 5 # ==========================
 6 #
 7 # You are given a start date and offset counter. Optionally you also get bank
 8 # holiday date list.
 9 #
10 # Given a number (of days) and a start date, return the number (of days)
11 # adjusted to take into account non-banking days. In other words: convert a
12 # banking day offset to a calendar day offset.
13 #
14 # Non-banking days are:
15 #
16 # a) Weekends
17 # b) Bank holidays
18 #
19 ## Example 1
20 ##
21 ## Input: $start_date = '2018-06-28', $offset = 3, $bank_holidays = ['2018-07-03']
22 ## Output: '2018-07-04'
23 ##
24 ## Thursday bumped to Wednesday (3 day offset, with Monday a bank holiday)
25 #
26 ## Example 2
27 ##
28 ## Input: $start_date = '2018-06-28', $offset = 3
29 ## Output: '2018-07-03'
30 #
31 ############################################################
32 ##
33 ## discussion
34 ##
35 ############################################################
36 #
37 # As long as offset is > 0, we add one to the current date. If the
38 # new date is neither a bank holiday nor on a weekend, we decrease
39 # the remaining offset by one.
40 # I use Date::Calc to take care of the date calculation part.
41 
42 use strict;
43 use warnings;
44 use Date::Calc qw(Day_of_Week Add_Delta_Days);
45 
46 banking_day_offset('2018-06-28', 3, ['2018-07-03']);
47 banking_day_offset('2018-06-28', 3);
48 
49 sub banking_day_offset {
50    my ($start_date, $offset, $bank_holidays) = @_;
51    $bank_holidays //= [];
52    print "Input: start_date = $start_date, offset = $offset, bank_holidays = [" . join(", ", @$bank_holidays) . "]\n";
53    while($offset > 0) {
54       my $next_date = next_day($start_date);
55       if( is_bankholiday_or_weekend($next_date, $bank_holidays) ) {
56          $start_date = $next_date;
57          next;
58       }
59       $offset--;
60       $start_date = $next_date;
61    }
62    print "Output: $start_date\n";
63 }
64 
65 sub is_bankholiday_or_weekend {
66    my ($date, $bank_holidays) = @_;
67    return 1 if Day_of_Week(split(/-/, $date)) > 5;
68    foreach my $d (@$bank_holidays) {
69       return 1 if $d eq $date;
70    }
71    return 0;
72 }
73 
74 sub next_day {
75    my $date = shift;
76    my ($year, $month, $day) = split /-/, $date;
77    my @new_date = Add_Delta_Days($year, $month, $day, 1);
78    my $d = sprintf("%04d-%02d-%02d", @new_date);
79    return $d;
80 }