Created
July 3, 2013 19:36
-
-
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…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} | |
?> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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