Monthly Commission Calculation PHP - php

I'm running into a coder's block with PHP dates. Let me first paint the picture of what I want to happen. ex:
$user_join_date = new DateTime('2015-01-31');
$today_date = new DateTime('2015-04-30');
Every day a cron will be run and for this example, on every 31st (or 30th - 28th depending on the month) the system will calculate commission for this user based on orders and volume from the past month BETWEEN '2015-03-31' AND '2015-04-29'.
So what I need is two fold. First, I need to make sure I'm calculating the commission on the correct day ie: the monthly anniversary of their join date OR that same month's equivalent. Second, I need to find the time frame in between which I'll calculate commissions as demonstrated in the mysql snippit above.
For obvious reasons I can't just say:
if ($user_join_date->format('d') == $today_date->format('d')){
calc_commission();
}
Because this wouldn't get run every month. Let me know if I'm unclear on anything.

I think you're saying you want to credit each user on an integral number of months since her signup date. There's an aspect of MySQL's date arithmetic you will find very convenient -- INTERVAL n MONTH addition.
DATE('2015-01-30') + INTERVAL 1 MONTH ==> '2016-02-28'
DATE('2016-01-30') + INTERVAL 1 MONTH ==> '2016-02-29'
This feature deals with all the oddball Gregorian Calendar trouble around weekdays, quite nicely. I'm going to call the date the renewal date.
Now, let us say that the column signup contains the date/time stamp for when the user signed up. This expression determines the most recent monthly renewal date. In particular, if it is equal to CURDATE(), today is the renewal date.
DATE(signup) + INTERVAL TIMESTAMPDIFF(MONTH, DATE(signup), CURDATE()) MONTH
Next: This closely related (duh!) expression is equal to the previous month's renewal date.
DATE(signup) + INTERVAL TIMESTAMPDIFF(MONTH, DATE(signup), CURDATE())-1 MONTH
You could just take CURDATE() - INTERVAL 1 MONTH but this is much more accurate around Gregorian month-end monkey business. Try it: it works right even at the end of February 2016 (leap year day).
Now, you'll want to use transactions that happened beginning on the previous renewal date >=, up until the end of the day before < the present renewal date, in your computation. In MySQL filtering by that date range looks like this.
WHERE transdate >= DATE(signup) +
INTERVAL TIMESTAMPDIFF(MONTH, DATE(signup), CURDATE())-1 MONTH
AND transdate < DATE(signup) +
INTERVAL TIMESTAMPDIFF(MONTH, DATE(signup), CURDATE()) MONTH

$base_dt is your joining date. So If you pass it to checkIt function It will provide you true or false accordingly today's day is billing day or not.
$base_dt = new DateTime('2015-04-30', new DateTimeZone("America/Chicago"));
if(checkIt($base_dt)) {
echo "true";
//this is the day.
} else {
echo "false";
//this is not the day.
}
So the check it function should be like this .....
function checkIt($base_dt) {
$base_d = $base_dt->format('j'); //without zeroes
echo $base_d;
$today = new DateTime('now');
$d = $today->format('j');
echo $d;
if($base_d == $d) {
return true;
} else if($base_d>28) {
$base_dt->modify('last day of this month');
$dif_base = $base_dt->format('j') - $base_d;
//echo $dif_base;
$today->modify('last day of this month');
$diff_today = $today->format('j') - $d;
//echo $diff_today;
if(($base_d==31 && $diff_today==0)||($base_d==30 && $diff_today==0 && $d<30)||($base_d==29 && $diff_today==0 && $d<29)) {
return true;
}
if($dif_base==0 && $diff_today==0) {
return true;
}
}
return false;
}

Related

Get number of occurrences/times between two dates

I'm trying to get the number of times between two dates. I'm provided with 4 types of info: freq, interval, startDate, and endDate.
For example:
freq = weekly
interval = 1
startDate = 2019-03-10
endDate = 2019-03-24
I'm trying to get the number of times/dates/events are between those two dates with the given info. (The count or the number of events)
In this example, since the frequency is weekly and the interval is 1, it means repeat the event on the startDate every 1 week. If the interval was changed to 2, it would be every 2 weeks. If the frequency was changed to daily, and the interval was 3, it would be every 3 days.
In this example, the first event would be on 2019-03-10 and then the second event would be on 2019-03-17, and then the third would be on 2019-03-24. The count is 3 in this example because there are three dates.
I am trying to find the count.
Thanks
Well, if you use Datetime class or Carbon, you can easily loop through the days/weeks/months between those dates. For example, if your freq = weekly and interval = 1, given the startDate and endDate you provided, the code would be something like this:
use Carbon\Carbon;
$startDate = Carbon::create(2019,03,10,0,0);
$endDate = Carbon::create(2019,03,24,0,0);
$count = 0;
while(! $startDate->greaterThan($endDate) )
{
$count += 1;
if($freq === 'weekly')
{
$startDate->addWeeks($interval);
}
else if($freq === 'monthly')
{
$stardDate->addMonths($interval);
}
}
In this example i've made using carbon because it saves you a lot of time, but you could do this by using PHP Datetime Class.

Calculate how many days (Monday - Wednesday) between two dates - Woocommerce Bookings?

I am struggling with the price calculation of Woocommerce Bookings for the type "days". If the price on a daterange (e.g. Monday to Wednesday or Wednesday-Saturday or Sunday-Tuesday...) is higher than on the other days.
So far I know how many days:
$days = date_diff($from, $to)->format('%d');
This question helps to get the number of single days: calculate sundays between two dates
But how do I get not only "sundays" but every daterange (e.g. from 6 to 2) with this:
$sundays = intval($days / 7) + ($from->format('N') + $days % 7 >= 7);
I guess I need to iterate through daterange and use this formula but I cannot figure out how.
You can use a completely different approach:
Create a DatePeriod object and loop through the Period object and increment a variable each time you encounter the range.
The following code achieve it but only return the number of complete range:
$from='2018/09/1';
$to='2018/09/31';
$start='Sunday';
$end='Thursday';
$go=false;
$period=new DatePeriod(date_create($from),date_interval_create_from_date_string('1 days'),date_create($to));
$nRange=0;
foreach($period as $date){
if($date->format('l')===$start){
$go=true;
}
if($go&&$date->format('l')===$end){
$go=false;
$nRange++;
}
}
print_r($nRange);
output :
4
It works fine for any chosen range.
However if the need is to get any valid day within the given range complete or not you can alter the previous code this way:
$from='2018/09/17';
$to='2018/09/31';
$start='Sunday';
$end='Thursday';
$day_num=['Monday'=>1,'Tuesday'=>2,'Wednesday'=>3,'Thursday'=>4,'Friday'=>5,'Saturday'=>6,'Sunday'=>7];
$start_num=$day_num[$start];
$end_num=$day_num[$end];
$period=new DatePeriod(date_create($from),date_interval_create_from_date_string('1 days'),date_create($to));
$nRange=0;
if($start_num===$end_num){
$valid_range=array_values($day_num);//keep the number of each day of the week
}
elseif($start_num>$end_num){
$valid_range=array_values($day_num);//keep the number of each day of the week
while($valid_range[0]!==$start_num){//rotate the values to keep the start day first
$pop=array_shift($valid_range);
array_push($valid_range,$pop);
}
$valid_range=array_slice($valid_range,array_search($start_num,$valid_range),$start_num-$end_num);//keep only the days in the chosen range
}else{
$valid_range=array_values($day_num);//keep the number of each day of the week
$valid_range=array_slice($valid_range,array_search($start_num,$valid_range),$end_num-$start_num); //keep only the days in the chosen range
}
$valid_range=array_flip($valid_range);//flip the array to make the search more fast
foreach($period as $date){
if(isset($valid_range[(int)$date->format('N')])){
$nRange++;
}
}
print_r($nRange);//output the number of valid days
ouput:
6

Generating a date based on a weekinterval, a day, and an existing date

I have a database with different workdates, and I have to make a calculation that generates more dates based on a weekinterval (stored in the database) and the (in the database stored) days on which the workdays occur.
What my code does now is the following:
Read the first two workdates -> Calculate the weeks inbetween and save the week interval
Read all the workdates -> fill in the days on which a workdate occurs and save it in a contract.
Generate workdates for the next year, based on the week interval.
The point is: for each week with a week interval of 1, more days of the week should be saved as a workdate. I've used this code to do this, but it doesn't work.
// Get the last workdate's actdate.
$workdate_date = $linked_workdate['Workdate']['workdate_actdate'];
// Calculate the new workdate's date
$date = date("Y-m-d", strtotime($workdate_date . "+" . $interval . " week"));
// If 'Monday' is filled in for this contract, calculate on which day the
// Monday after the last interval is. Same for each day, obviously.
// The days are boolean.
if ($contract['Contract']['contract_maandag'] = 1){
$date = date("Y-m-d", strtotime($date, "next Monday"));
}
if ($contract['Contract']['contract_dinsdag'] = 1){
$date = date("Y-m-d", strtotime($date, "next Tuesday"));
}
// After this, save $date in the database, but that works.
Here is the error that i get:
strtotime() expects parameter 2 to be long, string given
I'm quite stuck right now, so help is appreciated!
if ($contract['Contract']['contract_maandag'] = 1){
if ($contract['Contract']['contract_dinsdag'] = 1){
This won't work. You're doing an assignment (=), so it's always true. But you want a comparison (===). It is recommended to do always (except required otherwise) to use strict (===) comparison.
Well, the = doesn't seem to be the problem, since the error is about the part that's after the comparison. Try
strtotime("$date next Monday");

Calculate the first date of a pay cycle based on odd/even weeks

We are creating a pretty generic ordering system, which bases payouts to users on the current week of a pay cycle.
Our pay cycles are bi-weekly, so we consider that WEEK 1 will be an ODD week, and WEEK 2 will be an EVEN week.
Week 1 (odd) [start period A]
Week 2 (even) [end period A]
Week 3 (odd) [start period B]
Week 4 (even) [end period B] [Payout period A on Friday of this week]
Week 5 (odd) [start period C]
Week 6 (even) [end period C] [Payout period B on Friday of this week]
And so forth.
We can use the following to determine whether or not it is an odd or even week:
self::$now = new DateTime();
if (self::$now->format('W') % 2 === 0) {
// Week is even -
} else {
// Week is odd
}
If it is an odd week, we want to take the Sunday of that week to use that as the 'Start Date' of the current pay cycle. On the other hand, if it is an even week, we want to take the Saturday of that week and use that as the 'End Date' of the current pay cycle.
Previously, our method of calculating these start and end dates was rather crude. We merely selected an arbitrary date to use as the first date of the two week pay cycle, and would use some messy DateTime() code to calculate diffs and so forth. We do not want to do it this way, but instead rely on whether or not the week is EVEN or ODD.
Here is the code we were using to calculate the previous start and end dates:
public function getPreviousPeriodStart() {
$daysIntoCurrentPeriod = ((int)self::$now->diff(self::$refStart)->format('%a') % self::PERIOD_LENGTH);
self::$prevPeriodStart = new DateTime('2 weeks ago');
self::$prevPeriodStart->sub(new DateInterval('P'.$daysIntoCurrentPeriod.'D'));
return self::$prevPeriodStart;
}
public function getPreviousPeriodEnd() {
$daysLeftCurrentPeriod = self::PERIOD_LENGTH - ((int)self::$now->diff(self::$refStart)->format('%a') % self::PERIOD_LENGTH) - 1;
self::$prevPeriodStart = new DateTime('2 weeks ago');
self::$prevPeriodStart->add(new DateInterval('P'.$daysLeftCurrentPeriod.'D'));
return (self::$prevPeriodStart);
}
Again, i know this is poor and sloppy, which is why I want to improve it!
Once we have established the start and end dates of the current pay cycle, we would like to be able to determine the following values:
current pay period start
current pay period end
previous pay period start
previous pay period end
previous period pay date (this will be the pay date for the PREVIOUS pay period - the one that ended before the current pay period began) [This will occur the Friday of the following EVEN pay period]
current period pay date (this will be the pay date for the current pay period) [This will occur the Friday of the NEXT EVEN pay period]
I am trying to find the cleanest, sanest way to handle this. If this approach or methodology is not ideal, I would welcome any alternate suggestions - I just want to make sure this is accurate and maintainable!
Like I said in the comment, odd/even approach will fail on years that have odd number of weeks, like year 2015, which has 53 weeks. So last week of the year 2015 will be odd, and then next week (first week in year 2016) will also be odd. Probably this is not intended?
Your first approach was better. Choose one date as a reference, and that reference is an odd start period date. Based on that, you now know if your current period is odd or even, see method isOdd() bellow.
Example:
class DateTimeExtended extends DateTime {
# ref start date is odd
const REF_START = '2013-W43-1';
protected function isOdd(DateTime $dt)
{
$ref = new DateTime(self::REF_START);
return floor($dt->diff($ref)->days / 7) % 2 == 0;
}
public function getCurrentPeriodStart()
{
$dt = new DateTime($this->format('o-\WW-1'));
if (!$this->isOdd($dt)) {
$dt->modify('-1 week');
}
return $dt;
}
public function getCurrentPeriodEnd()
{
$dt = new DateTime($this->format('o-\WW-7'));
if ($this->isOdd($dt)) {
$dt->modify('+1 week');
}
return $dt;
}
public function getPreviousPeriodStart()
{
$dt = $this->getCurrentPeriodStart();
return $dt->modify('-2 week');
}
public function getPreviousPeriodEnd()
{
$dt = $this->getCurrentPeriodEnd();
return $dt->modify('-2 week');
}
}
Use (demo):
$dt = new DateTimeExtended;
print_r( $dt->getCurrentPeriodStart() ); # 2013-10-21
print_r( $dt->getCurrentPeriodEnd() ); # 2013-11-03
print_r( $dt->getPreviousPeriodStart() ); # 2013-10-07
print_r( $dt->getPreviousPeriodEnd() ); # 2013-10-20
Like Dwayne Towell already said in the question comment, ISO8601 weeks start on Monday, not Sunday, so you will need to adjust code to make it work like that. Just replace ISO formats, like this.

Calculate amount of weeks after a certain date

Im currently trying to calculate the amount of periods (each period is 1 week, after a certain date or day). Lets say first period start on 01-01-2013 (tuesday) until 29-01-2013 (tuesday)(4 periods in total).
I have a table that looks like:
id | startDate (datetime) | endDate (datetime)
I have two input fields where a user can fill in a start date and end date. I would like to fill any date between 01-01-2013 and 29-01-2013 and decide how many times it passes a tuesday (the date the period starts) again.
So if I would select 01-01-2013 until 07-01-2013 would result in: 1, but if i would select 06-01-2013 till 09-01-2013 this would result in 2 and 06-01-2013 till 16-01-2013 would result in 3 etc.
To be clear, I dont want to know the amount of weeks between the two dates, only the amount of times it 'crosses' the same day (tuesday) that was given in the database. Every period is 1 week. Is there anyone that could help me out?
Use PHP's date() function.
$startday = strtotime($start_date_from_db);//date from db;
$endday = strtotime($end_date_from_db);//date from db;
$counter = 0;
for($i=$startday; $i<=$endday; $i+=86400) {
$current_day = date('w', $i);
if($current_day == 2) { //0 for sunday, 1 for monday, 2 for tuesday
$counter++;
}
}
When comparing dates a good method is:
1- transform the dates to timestamps:
$timestamp1 = strtotime($date1);
$timestamp2 = strtotime($date2);
2- do the desired operation, in this case:
$timebetween = $timestamp2 - $timestamp1;
3- Transform result (number of seconds) to desired value, in this case:
$weeks= $timebetween / 604800;

Categories