/ Perl6

CalcDates

Here I describe yet another Perl 6 web application I developed to address a simple issue I have at work.

We currently utilize a MS Excel spread sheet to calculate total days a person has been on a trip. This is done by putting the Entry date and the Departure date and then calculating the total days for that trip. Then at the bottom, all of the trips are summed up. Simple enough right? The problem with this is this spread sheet is used used by multiple people, over a network share. Now I know that is not really an issue, but when you can not access the file because somebody else has it open or the name of the file is changed or the data for the previous user is left in and you have to zero it out before use, or even worse an formula gets accidentally deleted It can be frustrating.

I figured it would be a great exercise to develop a Perl6 web application that can do something similar and be more readily available and for multiple users.

I first started off with the fundamental data I wanted to work with and came up with a Perl6 class to model the information I wanted to work with.

Behold the tripObj class:
screenshot of tripObj class

This basic class has only 3 attributes; $startDate & $endDate which are a Date objects, and $totalDays which is an Interger.
The only method this class has is the BUILD submethod. This method is special in the fact that it helps you construct your class at the time that the object is made. In this case we take the two Date objects and calculate the days in between them then set the $!totalDays attribute. If you notice on line 12 the $total.abs will return the absolute value, as we dont want any negative numbers for our purposes.

Another thing to note about this class, the $.startDate and $.endDate are both required and both need to be a Date Object.

To make use of this class, first you need an array of tripObj objects.

my @objects;
@objects.push( tripObj.new( startDate => Date.new( 2017, 1, 1 ), endDate => Date.new( 2017, 4, 6) ) );
@objects.push( tripObj.new( startDate => Date.new( 2017, 5, 1 ), endDate => Date.new( 2017, 6, 12) ) );

Now some code to access and process the data in the @objects array:

my $total = 0;
for @objects {
  $total += .totalDays;
}
say $total;

Now the the basic data structure is complete and functional, I put the class into a basic Bailador web app. I have one page / displaying the form, and one page /addDates to view the results. The form page has some Javascript that will allow the user to add a data set to the form.

screen shot of trip form

By clicking on the "Add Another Trip" button, another set of fields will be added to the form. When all of the dates are inputted into the form, clicking on the "Submit" button will send the data to the /addDates address where the data is parsed and converted into tripObj objects.

for request.params {
 my $data = ~$_; ## Convert the request.params hash into a string.
 if $data ~~ / 'set-' \d \s+ (\d**4)\-(\d**2)\-(\d**2) \s+ (\d**4)\-(\d**2)\-(\d**2)  / {
   @data.push( tripObj.new( startDate => Date.new( $0, $1, $2 ),   endDate => Date.new( $3, $4, $5 ) ) );
 }
}

I have intentions on improving upon this by allowing you to upload a plane text file with includes a data set on each line and process it instead on passing the data set by using the form. You can find the application at this address:
calc.independentcomputing.biz or view the code yourself below.

Full code:

use v6;
#use lib 'lib';
use Bailador;

class tripObj {
  has Date $.startDate is required;
  has Date $.endDate is required;
  has Int $.totalDays;
  submethod BUILD( :$!startDate, :$!endDate ) {
      my $total = $!endDate - $!startDate;
      $!totalDays = $total.abs;
  }
}

my $htmlHeader = q:to/END/;
    <head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
    th, td { padding: 6px; text-align: center; }
    tr:nth-child(even) { background-color: #f2f2f2 }
    tr:hover {background-color: #f5f5f5}  	
    </style>  
    </head>
    <body>
END

my $htmlFooter = q:to/END/;
    </body>
    <footer>
    </footer>
END

get '/' => sub {
my $formText = q:to/END/;
    <head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script language="Javascript" type="text/javascript">
    var counter = 1;
    var limit = 99999;
    function addInput(divName){
    if (counter == limit)  {
    alert("You have reached the limit of adding " + counter + " inputs");
    } else {
    var newdiv = document.createElement('div');
    newdiv.innerHTML = "<hr>Trip " + (counter + 1) + ': <br>Start Date<input type="date" name="set-' + (counter + 1) + '"> <br><br>End Date <input type="date" name="set-' + (counter + 1) + '">';
    document.getElementById(divName).appendChild(newdiv);
    counter++;
    }
    }
    </script>
    </head>
    <body>
    <h3>Trip days caculator.</h3>
    <p>This web application is a simple days caculator.  It is designed to return the total amount of days in mulpital non-concurrent trips. 
    <form method="POST" action="/addDates">
    <div id="dynamicInput">
    Trip 1: <br>Start Date <input type="date" name="set-1"> <br> <br>End Date <input type="date" name="set-1">
    </div>
    <br>
    <input type="button" value="Add Another Trip "  onClick="addInput('dynamicInput');">
    <input type="Submit">
    </form>
    </body>
END
return $formText;
}

post '/addDates' => sub {
  my @data; my $total = 0; my $index = 0;
  for request.params {
    my $data = ~$_;
    if $data ~~ / 'set-' \d \s+ (\d**4)\-(\d**2)\-(\d**2) \s+ (\d**4)\-(\d**2)\-(\d**2)  / {
      @data.push( tripObj.new( startDate => Date.new( $0, $1, $2 ), endDate => Date.new( $3, $4, $5 )));
    } else { say "Got bad data. Did not parse!!"; }
  }
  my $htmlBody; my $htmlBody_tableData; my @tripData;
	$htmlBody_tableData ~= 'Individual Trip Info:';
  $htmlBody_tableData ~= '<table text-align="center"><th>Trip</th><th>Start Date</th><th>End Date</th><th>Total Trip Days</th>';
  for @data {
    $htmlBody_tableData ~= "<tr><td>Trip { ++$index }</td><td>{ .startDate}</td><td>{ .endDate}</td><td><strong>{ .totalDays }<strong></td></tr>";
    $total += .totalDays;
  }
  $htmlBody_tableData ~= "</table></div>";
  $htmlBody ~= "<h3>Total days: { $total }</h3>";
  $htmlBody ~= $htmlBody_tableData;	
  $htmlBody ~= '<p><a href="http://calc.independentcomputing.biz">Reset</a>';
return $htmlHeader ~ $htmlBody ~ $htmlFooter;
}

baile( );