Missing Week in PHP's DateTime->modify('next week') - php

I think I am fully aware of ISO 8601 and that the first week of a year is the week that has a Monday in it. However I came across a strange behavior in PHP (5.6) DateTime Class.
Here is my code:
$start = new DateTime('2009-01-01 00:00');
$end = new DateTime();
$point = $start;
while($point <= $end){
echo $point->format('YW');
$point = $point->modify('next week');
}
This puts out correctly
200901
200902
200903
...
But if I pick as a start date something earlier in 2008 like $start = new DateTime('2008-01-01 00:00'); then I get a different result:
...
200852
200801 // <=== 2008??
200902
200903
...
Is this a PHP bug or am I'm missing something here?

Tinkered with this and finally figured it out
$start = new DateTime('2008-12-29 00:00');
$end = new DateTime('2009-01-7 00:00');
$point = $start;
while($point <= $end){
echo $point->format('YW') . "\t";
echo $point->format('m-d-Y') . "\n";
$point = $point->modify('next week');
}
So the first date here is 2008-12-29. Thus Y is correct. But 2008-12-29 is also week 1. So the W is also correct
https://3v4l.org/JZtqa

It's not a bug! Inspired by #Machavity and based on this this similar question I found a solution:
echo $point->format('oW');
instead of
echo $point->format('YW')
produces:
...
200852
200901
200902
...
no matter when the start date is. It's really a RTM case, as the PHP manual states:
o ==>
ISO-8601 year number. This has the same value as Y, except that if the
ISO week number (W) belongs to the previous or next year, that year is
used instead. (added in PHP 5.1.0)

Related

Is there any date/time where this function could break?

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";
}

Decreasing DateInterval not producing result

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');
}

PHP: Two same date statements but day of the week is wrong in one of them

I've written this code:
if(!is_null($customDate)){
$referenceDate = strtotime($customDate);
$toDay = date("N", $referenceDate);
echo("/".$referenceDate."/".$toDay."/");
}
else{
$referenceDate = strtotime(date("d:m:y"));
$toDay = date("N");
echo("/".$referenceDate."/".$toDay."/");
}
if I supply $customDate like:
function_name("24:07:17");
it prints out this:
/1500941237/2/
A datestamp (I assume) and 2 as day of week - Tuesday.
If I don't supply $customDate and call function as:
function_name();
I get this:
/1500941237/1/
Which is again - datestamp (I assume) and 1 for day of Week - Monday.
The second one is correct, it's 24th and it's Monday.
I am pretty new to PHP so I am almost 100% sure there is some small nuance in regards to operations with dates I am doing but I am not sure what it is.
How can two identical datestamps produce difference day of week?
Running it on WAMP server on Windows 10 with default config.
Full function:
function nextDate($customDate=null){
$referenceDate;
$toDay;
if(!is_null($customDate)){
$referenceDate = strtotime($customDate);
$toDay = date("N", $referenceDate);
echo("/".$referenceDate."/".$toDay."/");
}
else{
$referenceDate = strtotime(date("d:m:y"));
$toDay = date("N");
echo("/".$referenceDate."/".$toDay."/");
}
}
nextDate("24:07:17"); --> gives wrong result, it says 24th is Tuesday.
nextDate(); --> gives correct result, it says 24th is Monday.
it's because 24:07:17 is not valid date format, PHP thinks it's a time so "today 24:07:17"
echo date("Y-m-d H:i:s", strtotime("24:07:17"));
// outputs 2017-07-25 00:07:17
use valid date format or convert it to something strtotime will understand correctly:
list($d,$m,$y) = explode(":", "24:07:17");
$referenceDate = strtotime("20{$y}-{$m}-{$d}");

Date considers a Timestamp from the future as a date from the past

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.

How can you get the number of days since year 0 in PHP?

It's the equivalent of the MySQL to_days() function.
Is there a builtin PHP function that does this, or do I need to cobble something together?
You'd need to write your own but it's not hard:
$now = new DateTime();
$zero = new DateTime('0000-00-00'); // -0001-11-30 - Nov 30, 1 BC. Interesting.
$diff = $now->diff($zero);
echo $diff->format('%a days'); // 735728 days
Demo using the literal year zero. You obviously would want to put a valid date in there instead.
$now = new DateTime();
$zero = new DateTime('0001-01-01');
$diff = $now->diff($zero);
echo $diff->format('%a days'); // 735330 days
Demo
As a one liner:
echo (new DateTime())->diff(new DateTime('0001-01-01'))->format('%a days');
As a function:
function toDays($date) {
return (new DateTime())->diff(new DateTime($date))->format('%a');
}
You can use the Julian day count, i.e. with cal_to_js(), see http://www.php.net/manual/de/function.cal-to-jd.php, even if there was no year 0 in the Gregorian calendar.

Categories