I hope someone can help with this, i think perhaps the issue is i am overwriting the DateTime value in my second for loop, because its not outputting correct values, but not entirely sure.
<?php
$begin_from = new DateTime( "2023-01-01" );
$end_from = new DateTime( "2023-12-31" );
$begin_to = new DateTime( "2023-01-31" );
$end_to = new DateTime( "2023-12-31" );
for($i = $begin_from; $i <= $end_from; $i->modify('+1 month')){
for($k = $begin_to; $k <= $end_to; $k->modify('first day of')->modify('+1 month')->modify('last day of')){
echo $i->format("Y-m-d"),'..',$k->format("Y-m-d");
echo "\n";
}
}
From the code above its outputting this:
2023-01-01..2023-01-31
2023-01-01..2023-02-28
2023-01-01..2023-03-31
2023-01-01..2023-04-30
2023-01-01..2023-05-31
2023-01-01..2023-06-30
2023-01-01..2023-07-31
2023-01-01..2023-08-31
2023-01-01..2023-09-30
2023-01-01..2023-10-31
2023-01-01..2023-11-30
2023-01-01..2023-12-31
But, if you run these for loops separately you will get the correct values like below.
2023-01-01..2023-01-31
2023-02-01..2023-02-28
2023-03-01..2023-03-31
2023-04-01..2023-04-30
2023-05-01..2023-05-31
2023-06-01..2023-06-30
2023-07-01..2023-07-31
2023-08-01..2023-08-31
2023-09-01..2023-09-30
2023-10-01..2023-10-31
2023-11-01..2023-11-30
2023-12-01..2023-12-31
Can anyone tell what i am doing wrong here?
Mutable objects are really hard to reason about. In particular, you need to be aware that $bar = $foo makes $bar point at the same object as $foo, not at a new object with the same value.
With that in mind, let's "unroll" your loops (never write code like this!):
$begin_from = new DateTime( "2023-01-01" );
$end_from = new DateTime( "2023-12-31" );
$begin_to = new DateTime( "2023-01-31" );
$end_to = new DateTime( "2023-12-31" );
$i = $begin_from;
outerforloop:
if ( $i <= $end_from ) {
$k = $begin_to;
innerforloop:
if ( $k <= $end_to ) {
echo $i->format("Y-m-d"),'..',$k->format("Y-m-d");
echo "\n";
$k->modify('first day of')->modify('+1 month')->modify('last day of');
goto innerforloop;
}
$i->modify('+1 month');
goto outerforloop;
}
In particular, look at the start and end of the inner loop:
$k = $begin_to; will make $k point at the same object as $begin_to
$k->modify(...) will then modify that object, meaning both $k and $begin_to have moved forward by a month
Next time around the loop, we check $k <= $end_to, with the expected result
However, when we come back around the outer loop, we run $k = $begin_to; again, expecting this to reset the value; but $k and $begin_to already point at the same object, which has been modified; the assignment doesn't do anything
So now when we check $k <= $end_to, it will already be false: we won't go into the loop at all
To actually copy the value of object, you can use the clone keyword, e.g. $k = clone $begin_to;
However, this particular case is why the DateTimeImmutable class was created. With DateTimeImmutable, you never change the value of an existing object, and instead always assign the result somewhere. In short, replace $i->modify(...) with $i = $i->modify(...) and $k->modify(...) with $k = $k->modify(...):
$begin_from = new DateTimeImmutable( "2023-01-01" );
$end_from = new DateTimeImmutable( "2023-12-31" );
$begin_to = new DateTimeImmutable( "2023-01-31" );
$end_to = new DateTimeImmutable( "2023-12-31" );
for($i = $begin_from; $i <= $end_from; $i = $i->modify('+1 month')){
for($k = $begin_to; $k <= $end_to; $k = $k->modify('first day of')->modify('+1 month')->modify('last day of')){
echo $i->format("Y-m-d"),'..',$k->format("Y-m-d");
echo "\n";
}
}
That fixes your for loops ... but it doesn't give the results you wanted. That's because if you have two nested loops with 12 iterations each, the result is going to be 144 iterations - think of filling out a grid with 12 columns and 12 rows.
What you actually wanted was a single loop, which controls both the start and the end date. There are a few ways to write that, but the most similar to your existing code is probably to keep the loop for $i, then define $k based on it:
$begin_from = new DateTimeImmutable( "2023-01-01" );
$end_from = new DateTimeImmutable( "2023-12-31" );
for($i = $begin_from; $i <= $end_from; $i = $i->modify('+1 month')){
$k = $i->modify('last day of');
echo $i->format("Y-m-d"),'..',$k->format("Y-m-d");
echo "\n";
}
I'm not sure why your code produces that output, but it can be simplified using a while loop. You just need one date to manually modify within each loop, and one target date (DateTimeImmutable is recommended for datetimes that are meant to remain unchanged)
$i_date = new DateTime( "2023-01-01" );
$end_date = new DateTimeImmutable( "2023-12-31" );
while($i_date <= $end_date){
// echo first of month..
echo $i_date->format("Y-m-d..");
// goto last of month
$i_date->modify('last day of');
// echo last of month \n
echo $i_date->format("Y-m-d") . "\n";
// goto first of next month for the next iteration
$i_date->modify('first day of')->modify('+1 month');
}
The above will produce the following in PHP v7+:
2023-01-01..2023-01-31
2023-02-01..2023-02-28
2023-03-01..2023-03-31
2023-04-01..2023-04-30
2023-05-01..2023-05-31
2023-06-01..2023-06-30
2023-07-01..2023-07-31
2023-08-01..2023-08-31
2023-09-01..2023-09-30
2023-10-01..2023-10-31
2023-11-01..2023-11-30
2023-12-01..2023-12-31
PHP v5.6.40 requires you set the timezone or the default timezone like date_default_timezone_set('UTC'); before creating the DateTimes, but then you get the same output as above.
Run it live here.
To use datetime objects in a loop like #ArleighHix's answer, I'd only modify the start date and use a calls of min() and max() in conjunction with 01 and t to ensure that date boundaries are not exceeded.
max() and min() are safe in this case because Y-m-d is a "big-endian" date format -- this means that the string can be evaluated as a simple string.
Code: (Demo)
$start_date = new DateTime("2023-01-03");
$end_date = new DateTime("2023-12-15");
while ($start_date <= $end_date) {
printf(
"%s..%s\n",
max($start_date->format("Y-m-01"), $start_date->format("Y-m-d")),
min($end_date->format("Y-m-d"), $start_date->format("Y-m-t"))
);
$start_date->modify('+1 month first day of');
}
If your actual project requirement is to have non-full date ranges each month, then a refactored approach would be necessary. Please provide a more challenging example by editing your question if this is a real concern/possibility.
If your main goal is to output the start and end of each month within a given date range, inclusive of the end month, you can simplify this down to:
$from = new DateTime("2023-01-01");
$to = new DateTime("2023-12-31");
while ($from <= $to) {
echo $from->format("Y-m-01") . ".." . $from->format("Y-m-t") . "\n";
$from->add(new DateInterval("P1M"));
}
If you need to respect the $from and $to date, that is; if you need first date range to start at $from and the last date range to end at $to, you can adjust the above code slightly using the max() and min() functions:
$from = new DateTime("2023-01-05");
$to = new DateTime("2023-12-25");
$current = new DateTime($from->format("Y-m-01"));
while ($current <= $to) {
echo max($from->format("Y-m-d"), $current->format("Y-m-01")) . ".." . min($to->format("Y-m-d"), $current->format("Y-m-t")) . "\n";
$current->add(new DateInterval("P1M"));
}
Related
I'm trying to create a filter, whereby if days (Monday, Tuesday etc) are NOT found in a list, I want that specific DateTime to be removed from my DatePeriod. This is so that I can say, I work Monday and Tuesday. If you find that the day is Thursday, continue out of this loop and don't include it.
However, I cannot seem to do this as when I iterate through a DatePeriod, I cannot unset anything, as it does not count it as an array. Is there a way to do this? The code can be found below:
//Get all the start and end dates of the holidays (This will be run iteratively)
$get_st_date = $row['h_s_date'];
$get_end_date = $row['h_e_date'];
//Convert them to approprariate format to be used with DatePeriod
$get_begin = new DateTime( "$get_st_date" );
$get_end = new DateTime( "$get_end_date");
//Add an extra day or else it will be omitted from the following process.
$get_end = $get_end->add(new DateInterval('P1D'));
//Count per day
$get_interval = DateInterval::createFromDateString('1 day');
$get_period = new DatePeriod($get_begin, $get_interval, $get_end);
//Iteration Count
$iter = 0;
foreach($get_period as $get_dt){
//Find if date is Saturday or Sunday. If it is, break that current loop.
$iter++;
$str_result = $get_dt->format('l');
if($str_result == "Saturday") {continue;}
elseif($str_result == "Sunday") {continue;}
elseif(!preg_match("($str_result)", $e_d_w_p_w)){
echo "<br>Don't count this day" . $str_result;
unset($get_period[$iter]);
continue;
}
Then close the end tags (I haven't included it here as I do some other stuff.
From the above code, I get the following error:
"Fatal error: Uncaught Error: Cannot use object of type DatePeriod as array"
Is there a workaround to this?
For Clarification: $e_d_w_p_w is "Employee Days Worked Per Week"
$e_d_w_p_w is formatted like so "Monday;Tuesday;" etc
The problem is that DatePeriod is not an array, just like the error says. It just has the properties required so as to make the list of days required, but it doesn't store them, so you can't unset() a specific day from the list.
What you can do to accomplish this is create a new array, and instead of removing the days that do not match the criteria from the DatePeriod, only add the days that do to this new array:
<?php
$get_st_date = "2017-09-01";
$get_end_date = "2017-09-20";
//Convert them to approprariate format to be used with DatePeriod
$get_begin = new DateTime( "$get_st_date" );
$get_end = new DateTime( "$get_end_date");
//Add an extra day or else it will be omitted from the following process.
$get_end = $get_end->add(new DateInterval('P1D'));
//Count per day
$get_interval = DateInterval::createFromDateString('1 day');
$get_period = new DatePeriod($get_begin, $get_interval, $get_end);
$e_d_w_p_w = "Monday;Tuesday;";
$workDays = [];
//Iteration Count
$iter = 0;
foreach($get_period as $get_dt) {
//Find if date is Saturday or Sunday. If it is, break that current loop.
$iter++;
$str_result = $get_dt->format('l');
if($str_result == "Saturday") {continue;}
elseif($str_result == "Sunday") {continue;}
elseif(preg_match("($str_result)", $e_d_w_p_w)){
$workDays[] = $get_dt;
}
}
var_dump($workDays);
Demo
Also, I think it might be a bit cleaner (and faster; avoid regular expressions whenever possible) to transform $e_d_w_p_w to an array and check if the current day is in that array:
$e_d_w_p_w = "Monday;Tuesday;";
$days = explode(";", $e_d_w_p_w); // transform to an array, separating by ;
array_pop($days); // remove the last element (assuming you always have a trailing ;
and then
elseif(in_array($str_result, $days)){
$workDays[] = $get_dt;
}
I was researching ways to iterate over certain days within a DatePeriod and Google led me here. I ended up writing a class for it - hopefully this helps the OP.
DatePeriod_Filter gist
To address the OP needs, you may use it like so:
$e_d_w_p_w = "Monday;Tuesday;";
//Add an extra day or else it will be omitted from the following process.
$filter = new DatePeriod_Filter(
new DateTime( '2017-09-01' ),
new DateInterval( 'P1D' ),
new DateTime( '2017-09-20 + 1 day' )
);
foreach( explode( ';', strtolower( trim( $e_d_w_p_w, ';' ) ) ) as $day )
$filter->$day();
foreach( $filter as $date )
{
// do something here
}
I want to loop a date so that every time date is increment by previous date. my code is here. plz reply anyone, thanks in advance
$today = date('Y-m-d');
for($i=1; $i<=4; $i++){
$repeat = strtotime("+2 day",strtotime($today));
echo $rdate = date('Y-m-d',$repeat);
}
I want result as if today is 2016-04-04 than, 2016-04-06, 2016-04-08, 2016-04-10, 2016-04-12.
actually i want to make a reminder date where user enter reminder. lets a user want to add reminder today and want repeat it 5 time after 2days, 3days or what ever he wants, in next comming day. than how i repeat date with for loop.
Try this:
<?php
$today = date('Y-m-d');
for($i=1; $i<=4; $i++)
{
$repeat = strtotime("+2 day",strtotime($today));
$today = date('Y-m-d',$repeat);
echo $today;
}
Output:
2016-04-06
2016-04-08
2016-04-10
2016-04-12
The easiest way is what answer
aslawin
The below example is to go through the date
$begin = new DateTime($check_in);
$end = new DateTime($check_out);
$step = DateInterval::createFromDateString('1 day');
$period = new DatePeriod($begin, $step, $end);
foreach ($period as $dt)
{
<sample code here>
}
You can try this:
$today = date('Y-m-d');
for($i=1; $i<=8; $i++){
if($i%2 == 0){
$repeat = strtotime("+$i day",strtotime($today));
echo $rdate = date('Y-m-d',$repeat);
}
}
Result:
2016-04-06
2016-04-08
2016-04-10
2016-04-12
In this example, you can use $i%2 == 0 with limit <= 8
Use a for loop with base 2, then directly output your dates:
for( $i=2; $i<9; $i=$i+2 )
{
echo date('Y-m-d', strtotime( "+ $i days" )) . PHP_EOL;
}
Result:
2016-04-06
2016-04-08
2016-04-10
2016-04-12
actually i want to make a reminder date where user enter reminder.
lets a user want to add reminder today and want repeat it 5 time after
2days, 3days or what ever he wants, in next comming day. than how i
repeat date with for loop.
I'll help with the above. First of all I will just say I have a huge personal preference towards the DateTime object over simply using date it's more flexible and a hell of a lot more readable in my opinion, so when working with dates I would always suggest using that over date()
So here is some Code:
$date = new DateTime(); // Pretend this is what the User entered. We got it via $_POST or something.
$interval = 2; // Repeat x times at y day intervals. (Not including the initial)
$repeatAmount = 2; // Repeat the reminder x times
for ($i = 0; $i <= $repeatAmount; ++$i) {
echo $date->format('d/m/Y');
$date->modify('+'. $interval .' day');
}
$date = new DateTime()Imagine this is the date the user entered, this is our starting point, our first reminder will at this time.
$interval and $repeatAmount are the interval in days, i.e. I want this to every 2 days and the amount of times you want it to repeat, in our example 2.
for ($i = 0; $i <= $repeatAmount; ++$i) { We want to loop as many times as the user says they want to repeat. Little note ++$i tends to be a very minor performance boost over $i++ in some scenarios, so it is usually better to default to that unless you specifically need to use $i++
echo $date->format('d/m/Y'); Simply print out the date, i'll let you handle the reminder logic.
$date->modify('+' . $interval . ' day'); Increment the dateTime object by the interval that the user has asked for, in our case increment by 2 days.
Any questions let me know.
I've been reading about problems in php with strtotime and "next month" issues. What i want to make is counter of months between two dates.
For example if I have start date 01.02.2012 and stop date 07.04.2012 I'd like to get return value - 3 months. Also 3 months would be the result if start date i 28.02.2012 and 07.04.2012. I am not counting exact number of days/months, just a number of months I have between two dates. It's not a big deal to make it with some strange date, mktime and strtotime usage, but unfortunatelly start and stop dates might be in two different years so
mktime(0,0,0,date('m')+1,1,date('Y');
isnt going to work (i do not now the year and if it changes between start and stop date. i can calculate it but it is not nice solution). Perfect solution would be to use:
$stat = Array('02.01.2012', '07.04.2012')
$cursor = strtotime($stat[0]);
$stop = strtotime($stat[1]);
$counter = 0;
while ( $cursor < $stop ) {
$cursor = strtotime("first day of next month", $cursor);
echo $cursor . '<br>';
$counter++;
if ( $counter > 100) { break; } // safety break;
}
echo $counter . '<br>';
Unfortunatelly strtotime isnt returning proper values. If I use it is returning empty string.
Any ideas how to get timestamp of the first day of next month?
SOLUTION
$stat = Array('02.01.2012', '01.04.2012');
$start = new DateTime( $stat[0] );
$stop = new DateTime( $stat[1] );
while ( $start->format( 'U') <= $stop->format( 'U' ) ) {
$counter ++;
echo $start->format('d:m:Y') . '<br>';
$start->modify( 'first day of next month' );
}
echo '::' . $counter . '..<br>';
<?php
$stat = Array('02.01.2012', '07.04.2012');
$stop = strtotime($stat[1]);
list($d, $m, $y) = explode('.', $stat[0]);
$count = 0;
while (true) {
$m++;
$cursor = mktime(0, 0, 0, $m, $d, $y);
if ($cursor < $stop) $count ++; else exit;
}
echo $count;
?>
the easy way :D
I have 2 dates. Lets say they look like this.
$start = 2010/12/24;
$end = 2012/01/05;
I query the database to look for visits between these two dates. I find some. I then populate an array called stats.
$stats['2010/12/25'] = 50;
$stats['2010/12/31'] = 25;
...
As you can see, there are days missing. I need to fill the missing dates with a value of zero. I was thinking something like this. (I have pulled day / month / year from start and end dates.
for($y=$start_year; $y <= $end_year; $y++) {
for($m=$start_month; $m <=$end_month; $m++) {
for($d=$start_day; $d <= $end_day; $d++) {
This would work fine for the year however the months and days wouldn't work. If the start day is the 15th. Days 1-14 of each subsequent month would be missed. I could have a solution like this then...
for($y=$start_year; $y <= $end_year; $y++) {
for($m=1; $m <13; $m++) {
$total_days = cal_days_in_month(CAL_GREGORIAN, $m, $y) + 1;
for($d=1; $d <= $total_days; $d++) {
I would then need a bunch of if statements making sure starting and end months and days are valid.
Is there a better way of doing this? Or could this even be done in my mysql query?
Just to demonstrate the power of some of PHP's newer interval handling method (mentioned by pgl in his answer):
$startDate = DateTime::createFromFormat("Y/m/d","2010/12/24",new DateTimeZone("Europe/London"));
$endDate = DateTime::createFromFormat("Y/m/d","2012/01/05",new DateTimeZone("Europe/London"));
$periodInterval = new DateInterval( "P1D" ); // 1-day, though can be more sophisticated rule
$period = new DatePeriod( $startDate, $periodInterval, $endDate );
foreach($period as $date){
echo $date->format("Y-m-d") , PHP_EOL;
}
Does require PHP >= 5.3.0
EDIT
If you need to include the actual end date, then you need to add a day to $endDate immediately before the foreach() loop:
$endDate->add( $periodInterval );
EDIT #2
$startDate = new DateTime("2010/12/24",new DateTimeZone("Europe/London"));
$endDate = new DateTime("2012/01/05",new DateTimeZone("Europe/London"));
do {
echo $startDate->format("Y-m-d") , PHP_EOL;
$startDate->modify("+1 day");
} while ($startDate <= $endDate);
For PHP 5.2.0 (or earlier if dateTime objects are enabled)
If you're using PHP5.3 then Mark Baker's answer is the one to use. If (as you say in your comment) you're still on PHP5.2 something like this should help you:
$startdate = strtotime( '2010/12/24' );
$enddate = strtotime( '2012/01/05' );
$loopdate = $startdate;
$datesArray = array();
while( $loopdate <= $enddate ) {
$datesArray[$loopdate] = 0;
$loopdate = strtotime( '+1 day', $loopdate );
}
It will create an array of the unix timestamp of every date between the start and end dates as the index and each value set to zero. You can then overwrite any actual results you have with the correct values.
$start_date = DateTime::createFromFormat('Y/m/d', '2010/12/24');
$end_date = DateTime::createFromFormat('Y/m/d', '2012/01/05');
$current_date = $start_date;
while($current_date <= $end_date) {
$current_date = $current_date->add(new DateInterval('P1D'));
// do your array work here.
}
See DateTime::add() for more information about this.
$i = 1;
while(date("Y/m/d", strtotime(date("Y/m/d", strtotime($start)) . "+ $i days")) < $end) {
... code here ...
$i++;
}
I would calculate the difference between start and end date in days, iterate on that adding a day to the timestamp on each iteration.
$start = strtotime("2010/12/24");
$end = strtotime("2012/01/05");
// start and end are seconds, so I convert it to days
$diff = ($end - $start) / 86400;
for ($i = 1; $i < $diff; $i++) {
// just multiply 86400 and add it to $start
// using strtotime('+1 day' ...) looks nice but is expensive.
// you could also have a cumulative value, but this was quicker
// to type
$date = $start + ($i * 86400);
echo date('r', $date);
}
I have this bit of horrible code saved:
while (($tmptime = strtotime('+' . (int) $d++ . ' days', strtotime($from))) && ($tmptime <= strtotime($to))) // this code makes baby jesus cry
$dates[strftime('%Y-%m-%d', $tmptime)] = 0;
(Set $from and $to to appropriate values.) It may well make you cry, too - but it sort of works.
The proper way to do it is to use DateInterval, of course.
I've got to write a loop that should start and end between two times. I know there are many ways to skin this cat, but I'd like to see a real programmers approach to this function.
Essentially I have Wednesday, for instance, that opens at 6:00pm and closes at 10:30pm.
I'm looking to write a loop that will give me a table with all of the times in between those two in 15 minute intervals.
So, I basically want to build a one column table where each row is
6:00pm
6:15pm
7:15pm
etc...
My two variables to feed this function will be the open time and the close time.
Now don't accuse me of "write my code for me" posting. I'll happily give you my hacked solution on request, I'd just like to see how someone with real experience would create this function.
Thanks :)
$start = new DateTime("2011-08-18 18:00:00");
$end = new DateTime("2011-08-18 22:30:00");
$current = clone $start;
while ($current <= $end) {
echo $current->format("g:ia"), "\n";
$current->modify("+15 minutes");
}
Try it on Codepad: http://codepad.org/JwBDOQQE
PHP 5.3 introduced a class precisely for this purpose, DatePeriod.
$start = new DateTime("6:00pm");
$end = new DateTime("10:30pm");
$interval = new DateInterval('PT15M');
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $time) {
echo $time->format('g:ia'), PHP_EOL;
}
echo $end->format('g:ia'); // end time is not part of the period
$start = strtotime('2011-08-11 18:00:00');
for ($i = 0; $i < 20; $i++) {
echo date('g:ia', $start + ($i * (15 * 60))), '<br>';
}
I would go with the DateTime functions and increase the time by 15 minutes every loop-turn as long as the current time is lower then the end-time.
EDIT: as user576875 has posted
$start_date = '2019-07-30 08:00:00';
$end_date = '2019-09-31 08:00:00';
while (strtotime($start_date) <= strtotime($end_date)) {
echo "$start_date<br>";
$start_date = date ("Y-m-d H:i:s", strtotime("+1 hours", strtotime($start_date)));
}