Trying to get the last four Sundays, decrementing in a loop starting with the most recent Sunday first.
// most recent sunday
$mostRecentSunday = new DateTime('last sunday');
// four Sundays ago
$maxDateAttempt = clone $mostRecentSunday;
$maxDateAttempt->modify('-4 weeks');
// interval of one week (same as 7 days or "P7D")
$dateInterval = new DateInterval('P1W');
// isn't this supposedly supposed to switch the increasing interval to decreasing?
$dateInterval->invert = 1;
$dateRange = new DatePeriod($mostRecentSunday, $dateInterval, $maxDateAttempt);
foreach ($dateRange as $day) {
echo $day->format('F j, Y');
}
Taking #hijarian's answer in this similar question, I thought setting the invert property would solve this, but I cannot get it to work. Then this comment in the PHP docs claims the DatePeriod class isn't even compatible with negative intervals. Anyone have some clarity in the issue? Maybe the PHP docs could use some improvement here.
That comment in the PHP docs is only partially correct. Everything I've read and experimented with so far seems to indicate that DatePeriod doesn't work with negative DateIntervals when using an end date. Maybe there's some initial check that the minimum is less than the maximum before it does anything, but I'm really not sure why it doesn't work.
However, it does work if you use the recurrences constructor rather than setting an end date.
$dateRange = new DatePeriod($mostRecentSunday, $dateInterval, 3);
// using 3 rather than 4 because the initial value is one occurrence
But you have to create your DateInterval like this instead:
$dateInterval = DateInterval::createFromDateString('-1 week');
Interestingly enough, that does not create a 7 day interval with invert=1. If you var_dump($dateInterval), you'll see public 'd' => int -7 and public 'invert' => int 0.
But technically you don't need DateInterval or DatePeriod to accomplish this.
for ($i=0, $date = new DateTime; $i < 4; $i++) {
echo $date->modify('last sunday')->format('F j, Y');
}
Related
I've not seen anything like this before. This is part of a function that returns the expected answer (a series of five dates)... sometimes. For example, it's been run at 6am, and the result is sometimes incorrect: one of the five dates, either at the first or last, can be missing. Other times, it's fine. Same code, run just a few hours later.
I know working with dates can be a lot more complicated than it first appears, but this has stumped me. I can only hope my inexperience with the DateTime objects is to blame.
$start = new \DateTime(date("Y-m-d", strtotime("-1 day")));
$end = new \DateTime(date("Y-m-d", strtotime("-5 days")));
$diff = $end->diff($start);
$interval = \DateInterval::createFromDateString('-1 day');
$period = new \DatePeriod($start, $interval, $diff->days);
foreach($period as $date) {
echo $date->format("Y-m-d"); // Sometimes first or last date will be missing
}
So for example, if the code is run between 2020-07-05 00:00:00 and 2020-07-05 23:59:59, it should return the last five dates:
2020-07-04
2020-07-03
2020-07-02
2020-07-01
2020-06-30
I've run the code with various date/times manually, and I cannot recreate the bug... and yet it happens once every few days in production.
This is just vanilla PHP, but it is being run as part of a Laravel project, should that factor into things. (The app timezone is set to "Europe/London".)
I'm not keen on how you're defining $start and $end. If I'm not mistaken, if the server clock happens to tick to the next second between the two variables being defined, then your interval will be 3 days, 23 hours, 59 minutes, 59 seconds - instead of exactly 4 days. This messes up your definition of $diff->days to be 3 instead of 4, leading to a missing date.
I would suggest a different approach here. Specifically, start with the current date, and subtract a day the number of times you want - since this appears to be hard-coded to 5.
$date = new DateTime();
$interval = new DateInterval("P1D");
for( $i=0; $i<5; $i++) {
$date->sub($interval);
echo $date->format("Y-m-d")."\n";
}
That $i<5 can, of course, be refactored to $i < DAYS for some appropriate constant definition, to avoid the "magic number" and allow for changing in future development.
With DateTime (and also strtotime) it is possible to process expressions like 'Today -3 Days'. Today is always 00:00 today. The calculation can be simplified as follows:
$days = 5;
for( $i=1; $i<=$days; $i++) {
echo date_create("today -$i days")->format('Y-m-d')."<br>\n";
}
I have a loop who counts weekend days, it works like this:
$weekends = 0;
while ($begin <= $today) {
$no_days++;
$what_day = date("N", $begin);
if ($what_day > 5) {
$weekends++;
};
$begin += 86400;
};
$begin is the first day of the month, and $today is today and I get them like this:
$begin = date("Y-m-01");
$today = date("Y-m-d");
$begin and $today are correct, but for some reason my loop is saying that 3 consecutive days are weekend. I printed te value of $begin on inside the if for debuggin reasons and I get the following output:
2016-11-05 12:11:00(1478329200)
2016-11-06 12:11:00(1478415600)
2016-11-06 11:11:00(1478502000)
Instead, what I expect to get is:
2016-11-05 12:11:00(1478329200)
2016-11-06 12:11:00(1478415600)
2016-11-07 12:11:00(1478502000)
This script as been working good for several months, but today stop working. I have a date_default_timezone_set('America/Los_Angeles'); at the beggining.
I don't completely understand how dates works, so, I'm not really sure how to start looking for the problem, so, if this is a timezone related problem and somebody want to point me to a source of information to learn on this topic I will be really happy :)
EDIT: As #Devon points out. I changed $end for $today to avoid misunderstandings.
You should consider working with PHP's DateTime, DateInterval, and DatePeriod classes.
function countWeekendDaysBetweenDateTimes(DateTime $start, DateTime $end) {
$weekend_count = 0;
// one day interval
$interval = new DateInterval('P1D');
// get date period object based on start, end, interval
$date_period = new DatePeriod($start, $interval, $end);
foreach($date_period as $dt) {
if ((int)$dt->format('N') > 5) {
$weekend_count++;
}
}
return $weekend_count;
}
These classes will be much more reliable to work with when dealing with ranges and recurrences of dates and will generally also simplify dealing with timezones (DateTimeZone), daylight savings, leap years, and other anomalies of our calendar and time systems.
I was trying to find a simple solution to getting a date range, specifically the start of a week to the end of a week.
I found a few solutions here which were very complicated. I wanted a two line solution.
I thought my solution might help someone so I posted it.
$date = new \DateTime(/* any point in time and space */);
$week_start = new \DateTime(sprintf('%s today this week', $date->format('Y-m-d H:i:s')));
$week_end = new \DateTime(sprintf('%s today next week -1 second', $date->format('Y-m-d H:i:s')));
echo sprintf('%s -> %s', $week_start->format('Y-m-d H:i:s'), $week_end->format('Y-m-d H:i:s'));
A commonly overlooked feature of PHP's english to date expressions is how you can inherit words and dates from each other in what might appear to be conflicting expressions.
However you can add multiple expressions, and each one will inherit from the last.
So by adding a specific date and formatting it at the start of the expression, you can add today to get the start of that date.
After that this week to get the start of the week, inherits from the same, plus today, so the start of this week becomes the start of that week.
For the end date, I used the next week on the same principle, but applied -1 second afterwards, which gives us the first and last seconds of the week.
An alternative approach to the same result:-
$sunday = 0;
$monday = 1;
$date = new \DateTime(/* any point in time and space */);
$weekStart = (new \DateTime())->setISODate((int)$date->format('o'), (int)$date->format('W'), $monday)->setTime(0, 0, 0);
$weekEnd = (new \DateTime())->setISODate((int)$date->format('o'), (int)$date->format('W') + 1, $sunday)->setTime(23, 59, 59);
echo sprintf('%s -> %s', $weekStart->format('Y m d H:i:s'), $weekEnd->format('Y m d H:i:s'));
Personally, I prefer this type of method to passing a 'natural language' type string which, to me, has ambiguous meaning.
I want to add an x number of week days (e.g. 48 weekday hours) to the current timestamp. I am trying to do this using the following
echo (strtotime('2 weekdays');
However, this doesn't seem to take me an exact 48 hours ahead in time. For example, inputting the current server time of Tuesday 18/03/2014 10:47 returns Thursday 20/03/2014 00:00. using the following function:
echo (strtotime('2 weekdays')-mktime())/86400;
It can tell that it's returning only 1.3 weekdays from now.
Why is it doing this? Are there any existing functions which allow an exact amount of weekday hours?
Given you want to preserve the weekdays functionality and not loose the hours, minutes and seconds, you could do this:
$now = new DateTime();
$hms = new DateInterval(
'PT'.$now->format('H').'H'.
$now->format('i').'M'.
$now->format('s').'S'.
);
$date = new DateTime('2 weekdays');
$date->add($hms);//add hours here again
The reason why weekday doesn't add the hours is because, if you add 1 weekday at any point in time on a monday, the next weekday has to be tuesday.
The hour simply does not matter. Say your date is 2014-01-02 12:12:12, and you want the next weekday, that day starts at 2014-01-03 00:00:00, so that's what you get.
My last solution works though, and here's how: I use the $now instance of DateTime, and its format method to construct a DateInterval format string, to be passed to the constructor. An interval format is quite easy: it starts with P, for period, then a digit and a char to indicate what that digit represents: 1Y for 1 Year, and 2D for 2 Days.
However, we're only interested in hours, minutes and seconds. Actual time, which is indicated using a T in the interval format string, hence we start the string with PT (Period Time).
Using the format specifiers H, i and s, we construct an interval format that in the case of 12:12:12 looks like this:
$hms = new DateInterval(
'PT12H12M12S'
);
Then, it's a simple matter of calling the DateTime::add method to add the hours, minutes and seconds to our date + weekdays:
$weekdays = new DateTime('6 weekdays');
$weekdays->add($hms);
echo $weekdays->format('Y-m-d H:i:s'), PHP_EOL;
And you're there.
Alternatively, you could just use the basic same trick to compute the actual day-difference between your initial date, and that date + x weekdays, and then add that diff to your initial date. It's the same basic principle, but instead of having to create a format like PTXHXMXS, a simple PXD will do.
Working example here
I'd urge you to use the DateInterface classes, as it is more flexible, allows for type-hinting to be used and makes dealing with dates just a whole lot easier for all of us. Besides, it's not too different from your current code:
$today = new DateTime;
$tomorrow = new DateTime('tomorrow');
$dayAfter = new DateTime('2 days');
In fact, it's a lot easier if you want to do frequent date manipulations on a single date:
$date = new DateTime();//or DateTime::createFromFormat('Y-m-d H:i:s', $dateString);
$diff = new DateInterval('P2D');//2 days
$date->add($diff);
echo $date->format('Y-m-d H:i:s'), PHP_EOL, 'is the date + 2 days', PHP_EOL;
$date->sub($diff);
echo $date->format('Y-m-d H:i:s'), PHP_EOL, 'was the original date, now restored';
Easy, once you've spent some time browsing through the docs
I think I have found a solution. It's primitive but after some quick testing it seems to work.
The function calculates the time passed since midnight of the current day, and adds it onto the date returned by strtotime. Since this could fall into a weekend day, I've checked and added an extra day or two accordingly.
function weekDays($days) {
$tstamp = (strtotime($days.' weekdays') + (time() - strtotime("today")));
if(date('D',$tstamp) == 'Sat') {
$tstamp = $tstamp + 86400*2;
}
elseif(date('D',$tstamp) == 'Sun') {
$tstamp = $tstamp + 86400;
}
return $tstamp;
}
Function strtotime('2 weekdays') seems to add 2 weekdays to the current date without the time.
If you want to add 48 hours why not adding 2*24*60*60 to mktime()?
echo(date('Y-m-d', mktime()+2*24*60*60));
The currently accepted solution works, but it will fail when you want to add weekdays to a timestamp that is not now. Here's a simpler snippet that will work for any given point in time:
$start = new DateTime('2021-09-29 15:12:10');
$start->add(date_interval_create_from_date_string('+ 3 weekdays'));
echo $start->format('Y-m-d H:i:s'); // 2021-10-04 15:12:10
Note that this will also work for a negative amount of weekdays:
$start = new DateTime('2021-09-29 15:12:10');
$start->add(date_interval_create_from_date_string('- 3 weekdays'));
echo $start->format('Y-m-d H:i:s'); // 2021-09-24 15:12:10
I am aware this topic is pretty exhausted, but obviously not quite enough!
$temp_d1 = new DateTime(date('Y-m-d', $fromTime)); // 2012-01-01
$temp_d2 = new DateTime(date('Y-m-d', $endTime)); // 2013-02-01
$interval = $temp_d2->diff($temp_d1);
$monthsAhead = $interval->format('%m'); // returns 1, but I am expecting 13
How do you calculate the number of months between two dates without wrapping within a 12 month scale?
I was confusing exactly what:
$monthsAhead = $interval->format('%m');
does.
Obviously, format('%m') is just formatting the month component of the DateInterval object, not necessarily 'give me the interval as a number of months'.
In my case, I was looking for/to do this:
$monthsAhead = $interval->m + ($interval->y * 12);
http://www.php.net/manual/en/class.dateinterval.php
Hope this helps other fools in the future!