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";
}
Related
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');
}
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 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 have a table which shows the time since a job was raised.
// These are unix epoch times...
$raised = 1360947684;
$now = 1361192598;
$difference = 244914;
$difference needs to exclude any time outside of business hours (ex, 9-5 and weekends).
How could I tackle this?
The thing you have to do are 3 in numbers.
You take your start date and calculate the rest time on this day (if it is a business day)
You take your end date and calulate the time on this day and
you take the days in between and multiply them with your business hours (just those, that are business days)
And with that you are done.
Find a little class attached, which does those things. Be aware that there is no error handling, time zone settings, daylight saving time, ...
input:
start date
end date
output:
difference time in seconds
adjustable constants:
Business hours
Days that are not business days
Very bad idea, but I had no choice because I'm on php 5.2
<?php
date_default_timezone_set('Asia/Seoul');
$start = 1611564957;
$end = 1611670000;
$res = 0;
for($i = $start; $i<$end; $i++){
$h = date("H", $i);
if($h >= 9 && $h < 18){
//echo date("Y-m-d H:i:s", $i) . "<br>";
$res = $res + 1;
}
}
echo $res;
Use DateTime.
Using UNIX time for this is slightly absurd, and you would have to literally remake DateTime.
Look up relative formats where you can specify the hour on the day, e.g.
$date = new DateTime($raised);
$business_start = new DateTime("09:00"); // 9am today
$business_end = new DateTime("17:00"); // 5pm today
The rest is for you to work out.
Oh, and instead of start/end, you could probably use DateInterval with a value of P8H ("period 8 hours")
The problem with using timestamps directly is that you are assigning a context to a counter of seconds. You have to work backwards from the times you want to exclude and work out their timestamps beforehand. You might want to try redesigning your storage of when a job is raised. Maybe set an expiry time for it instead?
So what I am trying to do is add two strings together in the form of military time. Here is the example.
Time1 = "01:00";
Time2 = "04:30";
Adding the two together would produce "05:30". This seems incredibly simple, but it has been a long long day for a new learner. Any help would be greatly appreciated.
You can do something like this in PHP 5.3:
$d = new DateTime("01:00");
$f = $d->add(new DateInterval("PT04H30M"));
echo $f->format("H:i"); //outputs 05:30
Converting 04:30 to PT04H30M should be trivial. Note that whether this is actually what you want depends on what you mean by "04:30". This code may or may not sum 4*60+30 minutes to the actual time.
If you mean "4 hours and 30 minutes on the clock" this is always correct. However, if you mean "4*60+30 minutes", it depends on the day and timezone. Example:
$d = new DateTime("2010-03-28 01:00 Europe/Lisbon");
$f = $d->setTimestamp($d->getTimestamp() + (4*60+30)*60);
echo $f->format("H:i"); //outputs 06:30 due to DST
An alternate implementation of the last case (assuming current day and default timezone) for PHP 5.2 is
echo date("H:i", strtotime("+4 hours +30 mins", strtotime("01:00")));
For the first case, we must do:
date_default_timezone_set("UTC");
echo date("H:i", strtotime("+4 hours +30 mins", strtotime("01:00")));