Skip to content

Instantly share code, notes, and snippets.

@zimzat
Created July 3, 2013 19:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zimzat/5922014 to your computer and use it in GitHub Desktop.
Save zimzat/5922014 to your computer and use it in GitHub Desktop.
Part of a code challenge for a job application in 2007 was to create a pay date calculator. Presented here is the exact code created for it over 6 years ago. Needless to say some of my coding practices and styles have further developed since that time, such as placing the trailing parenthesis on a separate line, using lowerCamelCase for variable…
<?php
class Paydate_Calculator {
/**
* Indicate if we're going forward (1) or backward (-1) when we make our adjustments.
* @var int
*/
private $due_date_adjustment = 1;
/**
* Keep track of how many pay days we've skipped ahead
* @var int
*/
private $pay_day_adjustment = 0;
/**
* Keep track of the fund day given to us.
* @var unix_timestamp
*/
private $fund_day;
/**
* Array of unix timestamp holidays
* @var array
*/
private $holiday_array;
/**
* String specifying interval between pay periods
* @var string
*/
private $pay_span;
/**
* Keep track of the pay day given to us.
* @var unix_timestamp
*/
private $pay_day;
/**
* Boolean value specifying if they have direct deposit
* @var boolean
*/
private $direct_deposit;
/**
* This function determines the first available due date following the funding of a loan.
* The paydate will be at least 10 days in the future from the $fund_day. The due_date will
* fall on a day that is a paydate based on their paydate model specified by '$pay_span'
* unless the date must be adjusted forward to miss a weekend or backward to miss a holiday.
* Holiday adjustment takes precedence over Weekend.
*
* @param unix_timestamp $fund_day The day the loan was funded.
* @param array $holiday_array An array of unix timestamp's containing holidays.
* @param string $pay_span A string representing the frequency at which the customer is paid.
* (weekly,bi-weekly,monthly)
* @param unix_timestamp $pay_day A timestamp containing one of the customers paydays
* @param bool $direct_deposit A boolean determining whether or not the customer receives
* their paycheck via direct deposit.
* @return unix_timestamp A unix timestamp representing the determined due date.
*/
public function Calculate_Due_Date($fund_day, $holiday_array, $pay_span, $pay_day,
$direct_deposit) {
/* Make sure $pay_span is an expected frequency */
if (!in_array($pay_span, array('weekly', 'bi-weekly', 'monthly'))) {
trigger_error('Unexpected pay span [' . htmlspecialchars($pay_span) . '] specified',
E_USER_ERROR);
return 0;
}
/* Set/Refresh all of our initial values. Also standardize dates for easier comparison */
$this->fund_day = $this->getDayUnixTimestamp($fund_day);
$this->holiday_array = array_map(array(&$this, 'getDayUnixTimestamp'), $holiday_array);
$this->pay_span = $pay_span;
$this->pay_day = $this->getDayUnixTimestamp($pay_day);
$this->direct_deposit = (bool)$direct_deposit;
$this->pay_day_adjustment = 0;
$this->due_date_adjustment = 1;
/*
* Get first pay date after fund date. This will be our first due date.
* We could optimize this by getting the first pay date 10 days after fund day, but
* in one very specific circumstance that could lead to the due date being a whole pay date
* after the 10 days past the fund date, even though, due to the weekend plus, it would have
* been right after the 10 day fund date.
*/
$next_pay_day = $this->pay_day;
while ($next_pay_day <= $this->fund_day) {
$next_pay_day = $this->getNextPayDay();
}
$due_date = $next_pay_day;
$minimum_due_date = $this->getDayAdjustment($this->fund_day, 10);
do {
/* Add a day if this isn't a direct deposit */
if (!$this->direct_deposit) {
$due_date = $this->getDayAdjustment($due_date, 1);
}
/* Check for weekends and holidays and adjust as necessary */
$this->due_date_adjustment = 1;
while (!$this->isAcceptableDate($due_date)) {
$due_date = $this->getDayAdjustment($due_date, $this->due_date_adjustment);
}
/* If we're less than fund day + 10 days, also get the next pay day and then loop */
} while ($due_date < $minimum_due_date && $due_date = $this->getNextPayDay());
return $due_date;
}
/**
* Take a given unix timestamp and return one that is at the start of the day.
* This allows us to standize all timestamps for easier comparison, since we only care about
* the specific year/day and not the hour/seconds.
*
* @param unix_timestamp $unix_timestamp Timestamp to standardize.
* @return unix_timestamp Timestamp of the beginning of the input timestamp day.
*/
private function getDayUnixTimestamp($unix_timestamp) {
return mktime(0, 0, 0,
date('m', $unix_timestamp),
date('d', $unix_timestamp),
date('Y', $unix_timestamp),
false);
}
/**
* Check if a timestamp given is during the weekend or holiday.
* If it's during a holiday then reverse the due date adjustment.
*
* @param unix_timestamp $due_date Unix timestamp of day to check against.
* @return boolean True if it is acceptable, false otherwise.
*/
private function isAcceptableDate($due_date) {
if ($this->isTimestampInWeekend($due_date)) {
return false;
} else if ($this->isTimestampInHoliday($due_date)) {
$this->due_date_adjustment = -1;
return false;
}
return true;
}
/**
* Check to see if the timestamp is a weekend day.
*
* @param unix_timestamp $unix_timestamp The timestamp to check the day of.
* @return boolean True if a weekend day, false otherwise.
*/
private function isTimestampInWeekend($unix_timestamp) {
/* Sunday = 0, Saturday = 6 */
$weekend = array(0, 6);
$timestamp_day = date('w', $unix_timestamp);
return in_array($timestamp_day, $weekend);
}
/**
* Check if a given unix timestamp is in the holiday array.
*
* @param unix_timestamp $unix_timestamp The timestamp to check against holidays.
* @return boolean True if a holiday, false otherwise.
*/
private function isTimestampInHoliday($unix_timestamp) {
return in_array($unix_timestamp, $this->holiday_array);
}
/**
* Function returns the unix timestamp of the next day based on unix timestamp input.
*
* @param unix_timestamp $unix_timestamp A timestamp to adjust the days of
* @param integer $adjustment How many days to adjust the timestamp by
* @return unix_timestamp A unix timestamp of the adjusted time
*/
private function getDayAdjustment($unix_timestamp, $adjustment) {
return mktime(0, 0, 0,
date('m', $unix_timestamp),
date('d', $unix_timestamp) + $adjustment,
date('Y', $unix_timestamp),
false);
}
/**
* Calculate the next pay day based on the pay span.
* Special care must be taken for monthly pay spans because not all months will have
* the same day (e.g. February doesn't have a 30th). When this occurs we set the pay day as
* the last day of the month instead.
*
* @return unix_timestamp The next pay day.
*/
private function getNextPayDay() {
$this->pay_day_adjustment++;
if ($this->pay_span == 'monthly') {
$pay_day = mktime(0, 0, 0,
date('m', $this->pay_day) + $this->pay_day_adjustment,
date('d', $this->pay_day),
date('Y', $this->pay_day),
false);
/* If the month actually jumps two months ahead go to the last day of the month. */
if ((date('m', $this->pay_day) + $this->pay_day_adjustment) - date('m', $pay_day) < 0) {
$pay_day = mktime(0, 0, 0,
date('m', $this->pay_day) + $this->pay_day_adjustment + 1,
0,
date('Y', $this->pay_day),
false);
}
} else {
$adjustment_rate = ($this->pay_span == 'weekly') ? 7 : 14;
$adjustment = $this->pay_day_adjustment * $adjustment_rate;
$pay_day = $this->getDayAdjustment($this->pay_day, $adjustment);
}
return $pay_day;
}
}
?>
<?php
require_once('paydate_calculator.class');
/* Federal Holiday List for 2007 copied from: http://www.opm.gov/fedhol/2007.asp */
$holiday_array = array(
mktime(0, 0, 0, 1, 1, 2007, false), /* New Year's Day */
mktime(0, 0, 0, 1, 15, 2007, false), /* Birthday of Martin Luther King, Jr */
mktime(0, 0, 0, 2, 19, 2007, false), /* Washington's Birthday */
mktime(0, 0, 0, 5, 28, 2007, false), /* Memorial Day */
mktime(0, 0, 0, 7, 4, 2007, false), /* Independence Day */
mktime(0, 0, 0, 9, 3, 2007, false), /* Labor Day */
mktime(0, 0, 0, 10, 8, 2007, false), /* Columbus Day */
mktime(0, 0, 0, 11, 12, 2007, false), /* Veterans Day */
mktime(0, 0, 0, 11, 22, 2007, false), /* Thanksgiving Day */
mktime(0, 0, 0, 12, 25, 2007, false)); /* Christmas Day */
$paydate_calculator = new Paydate_Calculator;
/*
* Scenario 1:
* Monthly pay span makes next pay day fall on a non-existant day of the next month.
* Expected result: next due date is the last day of the that month.
*/
$fund_day = mktime(0, 0, 0, 2, 5, 2007, false);
$pay_day = mktime(0, 0, 0, 1, 30, 2007, false);
$pay_span = 'monthly';
$direct_deposit = true;
$due_date = $paydate_calculator->Calculate_Due_Date($fund_day, $holiday_array, $pay_span, $pay_day,
$direct_deposit);
if ($due_date == mktime(0, 0, 0, 2, 28, 2007, false)) {
echo 'Scenario 1: Passed.' . "\n";
} else {
echo 'Scenario 1: Failed!' . "\n";
}
/*
* Scenario 2:
* bi-weekly pay span makes the next pay date fall 9 days after due date but on Saturday.
* Expected result: next due date will be that Monday, 11 days after due date.
*/
$fund_day = mktime(0, 0, 0, 2, 15, 2007, false);
$pay_day = mktime(0, 0, 0, 2, 10, 2007, false);
$pay_span = 'bi-weekly';
$direct_deposit = true;
$due_date = $paydate_calculator->Calculate_Due_Date($fund_day, $holiday_array, $pay_span, $pay_day,
$direct_deposit);
if ($due_date == mktime(0, 0, 0, 2, 26, 2007, false)) {
echo 'Scenario 2: Passed.' . "\n";
} else {
echo 'Scenario 2: Failed!' . "\n";
}
/*
* Scenario 3:
* The due date falls on a weekend, but the following Monday is a holiday.
* Expected result: The next due date will be the friday before the weekend.
*/
$fund_day = mktime(0, 0, 0, 2, 5, 2007, false);
$pay_day = mktime(0, 0, 0, 2, 3, 2007, false);
$pay_span = 'bi-weekly';
$direct_deposit = true;
$due_date = $paydate_calculator->Calculate_Due_Date($fund_day, $holiday_array, $pay_span, $pay_day,
$direct_deposit);
if ($due_date == mktime(0, 0, 0, 2, 16, 2007, false)) {
echo 'Scenario 3: Passed.' . "\n";
} else {
echo 'Scenario 3: Failed!' . "\n";
}
/*
* Scenario 4:
* The due date falls on a holiday, but the previous day is less than 10 days after the fund date.
* Expected result: The due date will jump to the next pay date, a week later.
*/
$fund_day = mktime(0, 0, 0, 6, 24, 2007, false);
$pay_day = mktime(0, 0, 0, 6, 27, 2007, false);
$pay_span = 'weekly';
$direct_deposit = true;
$due_date = $paydate_calculator->Calculate_Due_Date($fund_day, $holiday_array, $pay_span, $pay_day,
$direct_deposit);
if ($due_date == mktime(0, 0, 0, 7, 11, 2007, false)) {
echo 'Scenario 4: Passed.' . "\n";
} else {
echo 'Scenario 4: Failed!' . "\n";
}
/*
* Scenario 5:
* Direct deposit is off.
* Expected result: The due date will be the day after the next pay date.
*/
$fund_day = mktime(0, 0, 0, 3, 1, 2007, false);
$pay_day = mktime(0, 0, 0, 3, 1, 2007, false);
$pay_span = 'weekly';
$direct_deposit = false;
$due_date = $paydate_calculator->Calculate_Due_Date($fund_day, $holiday_array, $pay_span, $pay_day,
$direct_deposit);
if ($due_date == mktime(0, 0, 0, 3, 16, 2007, false)) {
echo 'Scenario 5: Passed.' . "\n";
} else {
echo 'Scenario 5: Failed!' . "\n";
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment