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
Related
I have written some code where I have a start date and number of days duration and then get an end date ie start date 18th December, duration 21 days, end date 8th January. However I want to push the end date forward to avoid certain holidays (25th December through to 6th January) so that the end date becomes 15th January. All the answers I have seen include how to calculate business days and take out weekends, which I don't need. I just want to be able to define specific holidays in an array, get it to see if the holiday is within the start date and end date and if it is move the end date forward by that number of days.
Oh, then the date needs to be inserted into a database. Thanks.
Iterate through the $holidays array. If a holiday is in the given range, then move end date one day in the future:
$startDate = new DateTime('2015-12-18');
$endDate = new DateTime('2016-01-08');
// $holidays array must be sorted. if not, then sort it first
$holidays = array("2015-12-25","2015-12-26","2016-01-01");
$newEndDate = $endDate;
foreach ($holidays as $holiday) {
$holidayDate = new DateTime($holiday);
if ($startDate <= $holidayDate && $holidayDate <= $newEndDate) {
// there is a holiday in the range
$newEndDate->add(new DateInterval('P1D'));
}
}
echo($newEndDate->format('Y-m-d'));
Thank you for all those that posted possible solutions. I solved the problem by adding a selector to the php page which adds 7, 14 or 21 days to the end date (the user sees this as 1 week, 2 weeks etc) and this solved the problem.
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;
}
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.
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;
I have starting dates and ending dates in my database (MySQL).
How can I get the answer, how many weeks(or days) are inside of those 2 dates? (mysql or php)
For example I have this kind of database:
Started and | will_end
2009-12-17 | 2009-12-24
2009-12-12 | 2009-12-26
...
Update to the question:
How to use DATEDIFF?
How can I make this to work? or should I use DATEDIFF completly differently?
SELECT DATEDIFF('Started ','will_end') AS 'Duration' FROM my_table WHERE id = '110';
If the two columns $d1 and $d2 store unix timestamp obtained from time() then this simple line suffices:
$diffweek = abs($d1 - $d2) / 604800;
Otherwise if the columns are of DATETIME type, then:
$diffweek = abs(strtotime($d1) - strtotime($d2)) / 604800;
p/s: 604800 is the number of seconds in a week (60 * 60 * 24 * 7)
p/s2: you might want to intval($diffweek) or round($diffweek)
Calculating the number of days and dividing by seven won't give you the number of weeks between the two dates. Instead it will return the result of division by 7 that doesn't always correspond to the number of weeks between the two dates when thinking in terms of the number of weeks in the ISO calculation.
For example, given start_date = "2010-12-26" and end_date = "2011-01-25" you will be going through W51,52,01,02,03,04 and those are 6 weeks as per ISO, but if you simply calculate the difference and divide by 7, you'll get 5.
The issue appears when the start date and end date belong to different years.
The best way to do the calculation is to get the last week number of the start_date year and it should refer to the December, 28.
function weeks($ladate2,$ladate3) {
$start_week= date("W",strtotime($ladate2));
$end_week= date("W",strtotime($ladate3));
$number_of_weeks= $end_week - $start_week;
$weeks=array();
$weeks[]=$start_week;
$increment_date=$ladate2;
$i="1";
if ($number_of_weeks<0){
$start_year=date("Y",strtotime($ladate2));
$last_week_of_year= date("W",strtotime("$start_year-12-28"));
$number_of_weeks=($last_week_of_year-$start_week)+$end_week;
}
while ($i<=$number_of_weeks)
{
$increment_date=date("Y-m-d", strtotime($ladate2. " +$i week"));
$weeks[]=date("W",strtotime($increment_date));
$i=$i+1;
}
return $weeks;
}
function diff_weeks($ladate2,$ladate3) {
$weeks=weeks($ladate2,$ladate3);
$diff_weeks=count($weeks);
return $diff_weeks;
}
Best regards,
Manikam
MySQL has datediff which returns the difference in days between two dates, since MySQL 4.1.1.
Do note that, as per the manual, DATEDIFF(expr1,expr2) returns expr1 – expr2 expressed as a value in days from one date to the other. expr1 and expr2 are date or date-and-time expressions. Only the date parts of the values are used in the calculation.
You can use the TO_DAYS function on each date and subtract the two to calculate the difference in days.
DATEDIFF
Find the days and divide by 7
<?php
$dayDif = date('z',strtotime('2009-12-17)') - date('z',strtotime('2009-12-24)');
$numWeeks = $dayDif / 7;
?>
The z option for php's date function gives you the day of the year (0 - 365). By subtracting the two values you find how many days between dates. Then factor by seven for the number of weeks.
Read this page closely, the date() function is rich. http://php.net/manual/en/function.date.php