How to manipulate the date and exclude saturday and sunday?. The objective is, I need to create a cron job that will run and execute on datas that were created 5 days ago,"BUT", saturday and sunday shouldn't be included in that 5 days period.
here's what I have so far
$numdays = 5;
$today = strtotime('now');
$start = date('Y-m-d',strtotime('-'.$numdays.' day',$today));
echo $start;
if you try to run my code snippet above, it will show you the exact date 5 days ago 2016-02-10. But that one doesn't "exclude" saturday and sunday in the computation. it should be be 2016-02-08. So how to do that?
You can use PHP's date week of day, there are several versions, here is one using N:
<?php
$current = new DateTime();
$interval = new DateInterval('P1D');
$x = 5;
while ($x > 1) {
// Check if day of week is not saturday/sunday (1 => Monday ... 7 -> Sunday)
if ($current->format('N') >= 6) {
$x++;
}
$current->sub($interval);
$x--;
}
echo $current->format('Y-m-d') . PHP_EOL;
Example Run.
You can get a whole week and discard the weekends, keeping the furthest element in the array as a result.
$days = array_filter(array_map(function ($daysBack) {
$date = new \DateTimeImmutable("$daysBack days ago", new \DateTimeZone('UTC'));
return (!in_array($date->format('N'), [6, 7])) ? $date : null;
}, Range(1, 7)));
$fiveWorkingDaysAgo = end($days);
Related
I'm solving following task>
I have two dates - $start and $end and target year as $year.
dates are php DateTime objects, year is string.
add:dates comes acutaly from MySql field from this format 2017-02-01 15:00:00 ...
add2: if end date is null, I use todays date ...
I need to figure out how many days are between these two dates for specific year.
Also I need to round it for whole days, even if one minute in day should be counted as whole day ...
I can solve it by many many following ifs.
Expected results for values I used in example are
2016 is 0 days
2017 is 31 days
2018 is 32 days
2019 is 0 days
But are there any elegant php functions which can help me with this ?
What I did it seems to be wrong way and giving bad results - seems it counts full days only ...
Please see my code here >
<?php
$diff = True;
$start = DateTime::createFromFormat('Y-m-d H:i:s','2017-12-01 23:05:00');
$end = DateTime::createFromFormat('Y-m-d H:i:s','2017-12-03 00:05:00');
$year = '2017';
// start date
if ($start->format('Y')<$year)
{
$newstart = new DateTime('first day of January '. $year);
}
if ($start->format('Y')==$year)
{
$newstart = $start;
}
if ($start->format('Y')>$year)
{
$result = 0;
$diff = False;
}
// end date
if ($end->format('Y')>$year)
{
$newend = new DateTime('last day of December '. $year);
}
if ($end->format('Y')==$year)
{
$newend = $end;
}
if ($end->format('Y')<$year)
{
$result = 0;
$diff = False;
}
// count if diff is applicable
if ($diff)
{
$result = $newend->diff($newstart)->format("%a");
}
echo $result;
?>
But are there any elegant php functions which can help me with this ?
Read about DateTime::diff(). It returns a DateInterval object that contains the number of days (in $days) and by inspecting the values of $h, $i and $s you can tell if you have to increment it to round the result. You can also use min() and max() to crop the time interval to the desired year.
function getDays(DateTimeInterface $start, DateTimeInterface $end, $year)
{
// Extend the start date and end date to include the entire day
$s = clone $start; // Don't modify $start and $end, use duplicates
$s->setTime(0, 0, 0);
$e = clone $end;
$e->setTime(0, 0, 0)->add(new DateInterval('P1D')); // start of the next day
// Crop the input interval to the desired year
$s = min($s, new DateTime("$year-01-01 00:00:00"));
$year ++;
$e = max(new DateTime("$year-01-01 00:00:00"), $end); // start of the next year
if ($e <= $s) {
// The input interval does not span across the desired year
return 0;
}
// Compute the difference and return the number of days
$diff = $e->diff($s);
return $diff->days;
}
$d1 = strtotime('2017-05-15');
$d2 = strtotime('2017-05-31');
$div = 24 * 3600;
echo abs(($d2 - $d1) / $div); // 16 days
Just make sure and ONLY have the date part and you shouldn't have to deal with rounding.
I'm a newbie in both php and codeigniter.
I have a function which is used to label the x-axis of a chart of weekly sums of kilometers rode on a bicycle. It works (or is supposed to work) by counting back from today's date either zero Sundays or 1, 2, 3, etc as passed by an array.
I am using PHP Version 5.6.19, and this is my code:
In the Model:
public function week_start($date, $counter){
$tstring = strtotime($date);
$wknumber = ($counter + 1);
$start = strtotime("-$wknumber weeks sunday", $tstring);
return array(date('M d', $start));}
In the Controller:
$datenumbers = array(0, 1, 2, 3, 4, 5, 6, 7);
$dateResult = $this->bike_model->week_start(date("Y/m/d"), $datenumber);
In the view:
for ($i = 0; $i < 8; $i++){
echo "<text class = \"axis\" y = \"357\" x = \"";
echo 26 + ($i*70);
echo "\" dy = \".35em\">";
echo $dates_for_table[$i];}
I have had it up for few days and it has been showing Sunday July 3 as the first number on on my x-axis, as it should have. But today, it is Sunday and I expect it to now say Sunday July 10 as my most recent week. However, it is still showing last Sunday!
So my question is, is there a bug here or am I misunderstanding how '- one week Sunday' should work?
My goal is to have my function with zero as the argument return the most recent week start--either the current Sunday if it is Sunday or the past Sunday. I have found a workaround by adding an if else to the function checking if it is Sunday.
if ($tstring == strtotime('this Sunday')){
$wknumber = $counter;}
else{
$wknumber = ($counter + 1);}
However, this feels clunky and I'm looking for a more elegant way. I also want to understand what went wrong with "- one week Sunday" since I thought I had understood how it was working.
Thank you!
Here is how I did something similar using the object oriented DateTime style:
$startDate = new DateTime('NOW'); // can pass date string if not today
# reset start date to last Sunday or today if it is Sunday:
$startDate->sub(new DateInterval('P'.($startDate->format('w')).'D'));
# for debugging, output first Sunday:
echo $startDate->format('Y-m-d H:i:s') . " => full date and time (check for timezone issues) \n\n";
$weeks = 8;
for ($i = 0; $i < $weeks; $i++) {
# don't decrement first pass; sub 1 week on subsequent:
$startDate->sub(new DateInterval('P'. (($i) ? 1 : 0) .'W'));
echo $startDate->format('Y-m-d') . "\n";
};
You should be able to adapt for your purpose.
If you want to get fancy, you can also use DatePeriod instead of using sub within a loop:
$period = new DatePeriod(
$startDate,
DateInterval::createFromDateString('-1 week'),
7
);
foreach ($period as $date) {
echo $date->format('Y-m-d') . "\n";
}
I want to calculate a date based on a timestamp and some other informations.
My function looks like:
function getLastDeliveryDate($timestamp,$endOfMonth=true,$extraMonth=0){
$days = 0;
$extraDays = 0;
$endOfCurrentMonth = 0;
$tsDay = 86400;
if($endOfMonth){
$endOfCurrentMonth = date("t", $timestamp) - date("d",$timestamp);
//rest of days in current month. In this sample 16 days
}
for($i=0;$i<$extraMonth;$i++){
$x = $i + 1;
$date = new DateTime(date("Y-m-d", $timestamp)); //create dateobject to add a month
$date->modify("+{$x} month"); // add the month (next month)
$extraDays += date("t", strtotime($date->format("Y-m-d")));
// get the days of the selected month and add them to count
// in this case its 31 + 30 + 31 = 92
}
$days = $endOfCurrentMonth + $extraDays;
// count everything together 16 + 92 = 108 days
return date("d.m.y", $timestamp + ($tsDay*$days));
//returning date with 108 days added.
}
As a sample I call the function like:
// the timestamp is 2015-07-15
echo getLastDeliveryDate(1436911200, true, 3);
// should return 2015-10-31
But this return 2015-10-30 and I don't know why. But 108 Days shold be 2015-10-31. Whats going wrong here ?
If I call
echo getLastDeliveryDate(1436911200, true, 2);
Its correct and gives me 2015-09-30
Actually I allways want the last day of the month.
EDIT:
Wired, if I test this here: IDEONE everything works fine. Im my Project it doesn't :(
You need to create the datetime object before the loop:
$date = new DateTime(date("Y-m-d", $timestamp)); //create dateobject to add month
// simpler alternative: $date = new DateTime("#$timestamp");
for($i=0;$i<$extraMonth;$i++){
$date->modify("+1 month"); // add the month (next month)
// $extraDays += date("t", strtotime($date->format("Y-m-d")));
// you can reduce this line to:
$extraDays += $date->format("t");
}
// Result: 15-10-31
otherwise there is always 31 added because you use the day of the timestamp + 1 month.
Note:
You can reduce the whole function to this:
function getLastDeliveryDate($timestamp,$endOfMonth=true,$extraMonth=0){
$date = new DateTime("#$timestamp");
$date->modify("+$extraMonth month");
if ($endOfMonth)
$date->modify("last day of this month");
return $date->format("d.m.y");
}
The problem is the daylight savings time. You loose one hour on the 25th of october 2015. Since your timestamp is exactly 0:00:00 you lose one hour resulting in "30.10.2015 23:00:00" what should actually be 0:00:00
function getLastDeliveryDate($timestamp,$endOfMonth=true,$extraMonth=0){
$days = 0;
$extraDays = 0;
$endOfCurrentMonth = 0;
$tag = 86400;
if(date( 'H',$timestamp)==0){$timestamp+=3601;}
if($endOfMonth){
$endOfCurrentMonth = date("t", $timestamp) - date("d",$timestamp);
}
$date = new DateTime(date("Y-m-d", $timestamp));
for($i=0;$i<$extraMonth;$i++){
$date->modify("+1 month");
$extraDays += $date->format("t");
}
$days = $endOfCurrentMonth + $extraDays;
return date("d.m.y", $timestamp + ($tag*$days));
}
echo getLastDeliveryDate(1436911200, true, 3);
This code has a dirty fix for this problem by adding one hour and one second if your datetime is fixed to 0:00:00. When you don't care about the hours themselves, then this solution will fix your problem and is viable in any case. If you care about the hours, you have to check whether you are in daylight savings time or not and act acordingly.
I want to calculate all Sunday's between given two dates. I tried following code. It works fine if days are less but if i enter more days. It keeps processing and Maximum execution time exceeds i changed the time but it even keeps processing even execution time is 200sec.
code is
<?php
$one="2013-01-01";
$two="2013-02-30";
$no=0;
for($i=$one;$i<=$two;$i++)
{
$day=date("N",strtotime($i));
if($day==7)
{
$no++;
}
}
echo $no;
?>
please help.
John Conde's answer is correct, but here is a more efficient and mathy solution:
$start = new DateTime('2013-01-06');
$end = new DateTime('2013-01-20');
$days = $start->diff($end, true)->days;
$sundays = intval($days / 7) + ($start->format('N') + $days % 7 >= 7);
echo $sundays;
Let me break it down for you.
$start = new DateTime('2013-01-06');
$end = new DateTime('2013-01-20');
First, create some DateTime objects, which are powerful built-in PHP objects meant for exactly this kind of problem.
$days = $start->diff($end, true)->days;
Next, use DateTime::diff to find the difference from $start to $end (passing true here as the second parameter ensures that this value is always positive), and get the number of days between them.
$sundays = intval($days / 7) + ($start->format('N') + $days % 7 >= 7);
Here comes the big one - but it's not so complicated, really. First, we know there is one Sunday for every week, so we have at least $days / 7 Sundays to begin with, rounded down to the nearest int with intval.
On top of that, there could be a Sunday in a span of time less than a week; for example, Friday to Monday of the next week contains 4 days; one of them is a Sunday. So, depending on when we start and end, there could be another. This is easy to account for:
$start->format('N') (see DateTime::format) gives us the ISO-8601 day of the week for the start date, which is a number from 1 to 7 (1 is Monday, 7 is Sunday).
$days % 7 gives us the number of leftover days that don't divide evenly into weeks.
If our starting day and the number of leftover days add up to 7 or more, then we reached a Sunday. Knowing that, we just have to add that expression, which will give us 1 if it's true or 0 if it's false, since we're adding it to an int value.
And there you have it! The advantage of this method is that it doesn't require iterating over every day between the given times and checking to see if it's a Sunday, which will save you a lot computation, and also it will make you look really clever. Hope that helps!
<?php
$no = 0;
$start = new DateTime('2013-01-01');
$end = new DateTime('2013-04-30');
$interval = DateInterval::createFromDateString('1 day');
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $dt)
{
if ($dt->format('N') == 7)
{
$no++;
}
}
echo $no;
See it in action
Here is a solution if you want the Sundays in a specific date range.
function dateRange($begin, $end, $interval = null)
{
$begin = new DateTime($begin);
$end = new DateTime($end);
$end = $end->modify('+1 day');
$interval = new DateInterval($interval ? $interval : 'P1D');
return iterator_to_array(new DatePeriod($begin, $interval, $end));
}
/* define date range */
$dates = dateRange('2018-03-01', '2018-03-31');
/* define weekdays */
$weekends = array_filter($dates, function ($date) {
$day = $date->format("N");
return $day === '6' || $day === '7';
});
/* weekdays output */
foreach ($weekends as $date) {
echo $date->format("D Y-m-d") . "</br>";
}
/* define sundays */
$sundays = array_filter($dates, function ($date) {
return $date->format("N") === '7';
});
/* sundays output */
foreach ($sundays as $date) {
echo $date->format("D Y-m-d") . "</br>";
}
/* define mondays */
$mondays = array_filter($dates, function ($date) {
return $date->format("N") === '1';
});
/* mondays output */
foreach ($mondays as $date) {
echo $date->format("D Y-m-d") . "</br>";
}
Just change the number for any days you want in your output:
Monday = 1
Tuesday = 2
Wednesday = 3
Thursday = 4
Friday = 5
Saturday = 6
Sunday = 7
How do I go about getting all the work days (mon-fri) in a given time period (let's say, today till the end of the next month) ?
If you're using PHP 5.2+ you can use the library I wrote in order to handle date recursion in PHP called When.
With the library, the code would be something like:
$r = new When();
$r->recur(<start date here>, 'weekly')
->until(<end date here>)
->wkst('SU')
->byday(array('MO', 'TU', 'WE', 'TH', 'FR'));
while($result = $r->next())
{
echo $result->format('c') . '<br />';
}
This sample does exactly what you need, in an quick and efficient way.
It doesn't do nested loops and uses the totally awesome DateTime object.
$oDateTime = new DateTime();
$oDayIncrease = new DateInterval("P1D");
$aWeekDays = array();
$sStart = $oDateTime->format("m-Y");
while($oDateTime->format("m-Y") == $sStart) {
$iDayInWeek = $oDateTime->format("w");
if ($iDayInWeek > 0 && $iDayInWeek < 6) {
$aWeekDays[] = clone $oDateTime;
}
$oDateTime->add($oDayIncrease);
}
Try it here: http://codepad.org/wuAyAqnF
To use it, simply pass a timestamp to get_weekdays. You'll get back an array of all the weekdays, as timestamps, for the rest of the current month. Optionally, you can pass a $to argument - you will get all weekdays between $from and $to.
function get_weekdays ($from, $to=false) {
if ($to == false)
$to = last_day_of_month($from);
$days = array();
for ($x = $from; $x < $to; $x+=86400 ) {
if (date('w', $x) > 0 && date('w', $x) < 6)
$days[] = $x;
}
return $days;
}
function last_day_of_month($ts=false) {
$m = date('m', $ts);
$y = date('y', $ts);
return mktime(23, 59, 59, ($m+1), 0, $y);
}
I suppose you could loop through the dates and check the day for each one, and increment a counter.
Can't think of anything else off the top of my head.
Pseudocode coming your way:
Calculate the number of days between now and the last day of the month
Get the current day of the week (i.e. Wednesday)
Based on the current day of the week, and the number of days left in the month, it's simple calculation to figure out how many weekend days are left in the month - it's going to be the number of days remaining in the month, minus the number of Sundays/Saturdays left in the month.
I would write a function, something like:
daysLeftInMonth(daysLeftInMonth, startingDayOfWeek, dayOfWeekToCalculate)
where:
daysLeftInMonth is last day of the month (30), minus the current date (15)
startingDayOfWeek is the day of the week you want to start on (for today it would be Wednesday)
dayOfWeekToCalculate is the day of the week you want to count, e.g. Saturday or Sunday. June 2011 currently has 2 Sunday, and 2 Saturdays left 'til the end of the month
So, your algorithm becomes something like:
getWeekdaysLeft(todaysDate)
...getWeekdaysLeft is something like:
sundaysLeft = daysLeftInMonth(lastDayOfMonth - todaysDate, "Wednesday", "Sunday");
saturdaysLeft = daysLeftInMonth(lastDayOfMonth - todaysDate, "Wednesday", "Saturday");
return ((lastDayOfMonth - todaysDate) - (sundaysLeft + saturdaysLeft));
This code does at least one part you ask for. Instead of "end of next month" it simply works with a given number of days.
$dfrom = time();
$fourweeks = 7 * 4;
for ($i = 0; $i < $fourweeks; $i ++) {
$stamp = $dfrom + ($i * 24 * 60 * 60);
$weekday = date("D", $stamp);
if (in_array($weekday, array("Mon", "Tue", "Wed", "Thu", "Fri"))) {
print date(DATE_RSS, $stamp) . "\n";
}
}
// Find today's day of the month (i.e. 15)
$today = intval(date('d'));
// Define the array that will hold the work days.
$work_days = array()
// Find this month's last day. (i.e. 30)
$last = intval(date('d', strtotime('last day of this month')));
// Loop through all of the days between today and the last day of the month (i.e. 15 through 30)
for ( $i = $today; $i <= $last; $i++ )
{
// Create a timestamp.
$timestamp = mktime(null, null, null, null, $i);
// If the day of the week is greater than Sunday (0) but less than Saturday (6), add the timestamp to an array.
if ( intval(date('w', $timestamp)) > 0 && intval(date('w', $timestamp)) < 6 )
$work_days[] = mktime($timestamp);
}
The $work_days array will contain timestamps which you could use this way:
echo date('Y-m-d', $work_days[0]);
The code above with work in PHP 4 as well as PHP 5. It does not rely on the functionality of the DateTime class which was not available until PHP 5.2 and does not require the use of "libraries" created by other people.