Calculating an End Date - php

I need a bit of help using symfony to calculate an end date. The user will select a start date, a frequency (Ex. Every 7 Days, Every 14 Days, Every Month, Semi-Annually - Every 6 Months, etc), and the number of occurrences.
Examples:
Start date: 08/01/2010
Frequency: Every 7 days
Occurrences: 4
End Date = 08/29/2010
Start date: 08/01/2010
Frequency: Every Month
Occurrences: 3
End Date = 11/01/2010
Some notes- Our server is running PHP 5.2.13 and Symfony 1.1. It's not a requirement that I use symfony to adjust the date, as long as I can get it back to a format that symfony can validate.
Thanks in advance.

You can use a strtotime for this, e.g.
echo date("Y-m-d", strtotime("+1 month", strtotime("2010-08-06")));
gives 2010-09-06.
Of course, you will have to multiply "+1 month" for the number of occurrences, so that it becomes e.g. "+7 months" for 7 occurrences.
Alternatively (more simple but less efficient), you can use a fixed interval (e.g. "+1 month") and use a for loop:
$end = $start = strtotime("2010-08-06");
for ($i = 1; $i <= $occurrences; $i++) {
$end = strtotime("+1 month", $end);
}

Related

PHP - Intelligent Adding Months to Date

I've just discovered a problem with the usually used method to sum months to a PHP Date. If you search on google or this forum, you usually find somethings like these:
$date = strtotime(date("Y-m-d", strtotime($date)) . " +1 month");
or
$months = DateInterval::createFromDateString('1 month');
$dateInDateTime->add($months);
Both approach are not correct, in my opinion.
For example in my code I have to increment 3 times the month of a starting date beginning with last day of April and return the last day of that months.
So my code generates this results:
2017-04-30
2017-05-31
2017-07-31
The second time the script add +1 month to date, goes from 2017-05-31 to 2017-07-01 because 31-05 + 30 days is over the last day of JUNE.
What Im expecting is 06-30 because you are summing MONTHS not DAYS and if you have an overflow, the code has to correct it, not me.
This is a common error that explode when you manage February or December (due to change of year).
Im expecting a script that increment month. So if I have 2017-03-23 and sum +1 month, I get 2017-04-23 and if I sum +1 month to 2017-03-31 I got 2017-04-30.
So. Pay attention when using this functions.
I think you are trying something dangerous.
What s going on for february? if you want all the time to change only month number it will break for latest days of this month, same for months with 30 days instead of 31...
You have to think about your approach in another way, because changing the month alone won't make an existing date sometimes.
+30 days seems to be the best thing to do
What Im expecting is 06-30 because you are summing MONTHS not DAYS and if you have an overflow, the code has to correct it, not me.
PHP corrects it, indeed. It never returns 31st of June as such a date doesn't exist. It corrects it to 1st of July.
What you apparently expect is that when you add 1 month to the last day of a month to get the last day of the next month. But this doesn't make any sense.
What should strtotime('2017-06-30 +1 month') return?
2017-07-31, because you are adding 1 month to the last day of June or 2017-07-30 because you are adding 1 month to the 30th day of June?
The times runs forward, counting the days from the end of the month is not natural. Sometimes it's useful but not that many times. And there always is a better solution: subtract 1 day from the first day of the next month. This way you don't have to do any correction or care about months with different number of days or even about leap years.
This is the function I wrote:
//it accept negative month value
public static function realAddMonthsToDate($month,$dateToModify,
$dateFormatInput = DEFAULT_SQL_DATE_FORMAT, $dateFormatOutput = DEFAULT_SQL_DATE_FORMAT)
{
$currentDate = DateTime::createFromFormat($dateFormatInput, $dateToModify);
$cDay = $currentDate->format('d');
$cMonth = $currentDate->format('m');
$cYear = $currentDate->format('Y');
$monthRest = $month;
$yearOffset = 0;
if ($month > 12)
{
$yearOffset = floor($month / 12);
$monthRest = $month - ($yearOffset * 12);
}
$cMonth += $monthRest;
if ($cMonth > 12) {
$cMonth = $cMonth - 12;
$cYear += 1;
}
if ($cMonth <= 0)
{
$cMonth = 12 + $cMonth;
$cYear -= 1;
}
$cYear += $yearOffset;
$arrivalMonthDays = cal_days_in_month(CAL_GREGORIAN, $cMonth, $cYear);
if ($cDay >= $arrivalMonthDays) $cDay = $arrivalMonthDays;
$newDate = new DateTime($cYear.'-'.$cMonth.'-'.$cDay);
return $newDate->format($dateFormatOutput);
}

PHP - find next future recurring date?

I have a database of events that are both "static" (on a specific day) and "recurring" (starting on a specific day, but set to be "recurring" either every week or every other week). I understand that there needs to be intervals of 7 and 14, but I don't know how to get to that point to find a date > today and spit it out.
so example;
I want to find the next upcoming recurring events and spit out their relevant dates. Side note: the data I'm stuck working with is in strings (I know, I know) of Ymd so 20150821 would be Aug 21st 2015.
if today was Aug 21 2015 and there's a recurring event for "Every other Friday" starting on Aug 7, it would be +14 which would get you to today, Friday Aug 21.
But say there was one for "Every Wednesday" starting Aug 19, I'd want to get the date for Wednesday, Aug 26 and spit that out.
This would need to work for infinite dates in the future, with the start dates never changing.
So running the script on Jan 1 2016, I'd need to know that the next "Every Wednesday" was Jan 6 2016.
pseudo code:
if(start_date < today_date) {
// start date is in the past
add recurring_inverval to start_date until result >= today_date
echo result
} elseif(start_date > today_date {
// start date is in the future
echo start_date
}
it's the adding until x that I'm lost at. not sure how to do that within an if statement.
also not sure if that's the best way to go about it. I know PHP can also do complicated strings and convert them to a date. like "Next Saturday"
I know you answered this yourself, however another option is to just use the modulus (remainder after devision) of the current date minus the start date to compute your next date. Here is a quick script to do just that :
<?php
function nextDate($start_date,$interval_days,$output_format){
$start = strtotime($start_date);
$end = strtotime(date('Y-m-d'));
$days_ago = ($end - $start) / 24 / 60 / 60;
if($days_ago < 0)return date($output_format,$start);
$remainder_days = $days_ago % $interval_days;
if($remainder_days > 0){
$new_date_string = "+" . ($interval_days - $remainder_days) . " days";
} else {
$new_date_string = "today";
}
return date($output_format,strtotime($new_date_string));
}
echo nextDate('20151210',14,'Ymd') . "<br />";
echo nextDate('20150808',14,'Ymd') . "<br />";
?>
You also don't want to return an early date if the "start date" is in the distant future. Code updated to prevent that.
solved it myself:
$begin = new DateTime($start_date); // start searching on the start date of the event
$end = new DateTime(date('Ymd')); // end on today's date
$end = $end->modify('+1 month'); // extend search to the next month
$interval = new DateInterval('P'. $freq_math .'D'); // intervals of Plus X Days - uses frequency math of 7 or 14 for every or every other
$daterange = new DatePeriod($begin, $interval, $end);
foreach($daterange as $date) {
if($date->format('Ymd') >= date('Ymd')) {
$next_recurring_date = $date->format('Ymd');
break;
}
}
echo $next_recurring_date;

recurring date functions in php

Good Day,
I am trying to create a recurring date system that has the following:
nth day of nth month (2nd day of every 3rd month)
$this_months_friday = strtotime('+3 days +4 months');
the output of that will always be current day + 3 days of the 4th month.
how do I get it to display the nth day of the nth month?
since i also tried
$this_months_friday = strtotime('every 3 days +4 months');
and it did not return any result. Should i stick with strtotime on this one or move to DateTime function of php. though i wont still be able to formulate the proper argument for that kind of date sequence.
Any help would be greatly appreciated.
Thank You.
Probably better off using DateTime with a couple intervals:
$d = new DateTime();
$d->add(new DateInterVal('P' . $days . 'D'))->add('new DateInterVal('P' . $months . 'M'));
not sure what youre two example intervals are wanting.
You want an internval to start in 4 months, which then repeats every 3 days?
That'd be something more like
$d = new DateTime();
$d->add(new DateInterval('P4M')); // jump ahead 4 months immediately
$day3 = new DateInterval('P3D');
for ($i = 0; $i < 100; $i++) {
$d->add($day3); // jump ahead 3 days
... do something with this new date
}
for a basic recurring event, +4 months + 3 days, you'd simply have one interval:
$interval = new DateInteval('P4M3D'); // +4 months +3 days
$date = new DateTime();
while($some_condition) {
$date->add($interval);
do_something();
}
You can do this by saving the values in variables like that :
$day=3;
$month=4;
echo date("d-m-y",strtotime('+'.$day .'days' .'+'.$month.'months'));
Explanation:
7(july)+4 months = 11 month(November)
8 july+ 3 days = 11 july
Output:
11-11-13
NOTE: just for the example I have put the values hard coded, You can make them dynamic.

Calculating amount of months between dates

How would you go about calculating the amount of months between two arbitrary dates? Given that even if just one day falls on a month, it is considered a full month.
Examples:
2010-01-01 - 2010-03-31 = three months
2010-06-15 - 2010-09-01 = four months
Et cetera. I thought of just dividing the difference of timestamps with 2592000 (average number of seconds in a month) but that seems hacky and prone to errors. And I'd like to keep it as fast as possible (needs to run thousands of times quick), so I guess using strtotime isn't optimal either?
If I am reading your question correctly, you would want to return "2" for January 31st and February 1st, because it spans both January and February, even though they are only 1 day apart.
You could work out (psuedocode):
monthno1 = (date1_year * 12) + date1_month;
monthno2 = (date2_year * 12) + date2_month;
return (monthno2 - monthno1) + 1;
This assumes that the second date is the later date.
Assuming the dates are in a known format:
function getMonths($start, $end) {
$startParsed = date_parse_from_format('Y-m-d', $start);
$startMonth = $startParsed['month'];
$startYear = $startParsed['year'];
$endParsed = date_parse_from_format('Y-m-d', $end);
$endMonth = $endParsed['month'];
$endYear = $endParsed['year'];
return ($endYear - $startYear) * 12 + ($endMonth - $startMonth) + 1;
}
This gives:
print(getMonths('2010-01-01', '2010-03-31')); // 3
print(getMonths('2010-06-15', '2010-09-01')); // 4

Counting the age of an item in weekdays only

I've been trying to count the age of something in weekdays. I've tried the method detailed in this question, Given a date range how to calculate the number of weekends partially or wholly within that range? but it doesn't seem to fit my usecase.
An item has a created DATETIME in the database, and I need to mark it as old if the created date is over 2 days old. However, the client has requested that age only count week days (Mon to Fri) and exclude Sat+Sun.
So far, my pseudo code looks like the following,
now - created_datetime = number_of_days
for(i number_of_days)
if(created_datetime - i)
is a weekday, then age++
There must be a cleaner way of achieving this? As if an item were to get very old, looping through each day of it's age, looking for a weekend day would impact speed quite a bit.
Any ideas would be great! Thanks
You only have to check the last 7 days to know the exact age in weekdays.
Here's the intuition:
Subtract from the total age of the object, the number of weekends in its lifetime. Note that every 7 days there are exactly 2 weekends. Add to this the number of weekend days in the remainder days (the ones that aren't in a full week) and you have to total number of weekend days in its lifetime. Subtract from the real age to get the weekday age.
int weekendCount = 0;
// Check the remainder days that are not in a full week.
for (int i = totalAge % 7; i < 7; i++) {
if (created_datetime - i is weekend) {
weekendCount++;
}
}
weekdayAge = totalNumberOfDays - (((totalNumberOfDays / 7) * 2) + weekendCount);
Note that the division is an integer division.
I'm sure you can do this with some math and a bit of careful thought. You need to check what day of the week the item was created and what day of the week it currently is. First, you calculate the number of days old it is, as I'm sure you were doing at first.
// if today is saturday, subtract 1 from the day. if its sunday, subtract 2
if (now() is sunday) $now = now() - 2 days;
if (now() is saturday) $now = now() - 1 day;
// same thing for $date posted. but adding 1 or 2 days
if ( $date is saturday) $date = $date + 2;
if ( $date is sunday) $date = $date + 1;
// get days difference
$days = $today - $date;
// have to subtract 2 days for every 7
$days = $days - floor($days/7)*2
You'd have to check if that works. Maybe you can do the calculation without moving your date to before/after the weekend. It may not be perfect, but its the right idea. No need to iterate.

Categories