Calculate how many days in a date range are within another date range

From October 1 to March 31, the commission is $ 1 (season 1). From April 1 to September 30, the fee is $ 2 (season 2).

How can I calculate the total fee for a certain date range (user input) depending on how many days of this date range fall in season 1 and season 2?

The following gives me the number of days in the user's date range, but I don't know how to pass the test against season 1 or season 2:

$user_input_start_date = getdate( $a ); $user_input_end_date = getdate( $b ); $start_date_new = mktime( 12, 0, 0, $user_input_start_date['mon'], $user_input_start_date['mday'], $user_input_start_date['year'] ); $end_date_new = mktime( 12, 0, 0, $user_input_end_date['mon'], $user_input_end_date['mday'], $user_input_end_date['year'] ); return round( abs( $start_date_new - $end_date_new ) / 86400 ); 

Given that the date range starts and ends in 2012 or starts in 2012 and ends only in 2013, it gives me 10 different possibilities during which the date range can begin and where it can end.

There should be a better solution than repeating if / else and comparing dates again and again under the following conditions:

  • Date range completely for season 1
  • The date range starts in season 1 and ends in season 2
  • The date range starts in season 1, goes through season 2 and ends in the second part of season 1

... etc. with "Starts in Season 2," etc.

This is not a duplicate. How many days before the XYZ date? , since this only applies to counting the number of days. It does not address the problem of comparing one date range with another.

+4
source share
2 answers

The key to this problem is to simplify it as much as possible. I think using an array as a lookup table for the value of each day of the year is the way to go. The first thing to do is create an array. An array simply represents every day of the year and does not represent any particular year. I decided to use 2012 to generate a search array, since it is a leap year, and therefore has all kinds of possibilities in it.

 function getSeasonArray() { /** * I have chosen 2012 as it was a leap year. All we want to do is * generate an array which has avery day of the year in it. */ $startDate = new DateTime('1st January 2012'); //DatePeriod always drops the last day. $endDate = new DateTime('1st January 2013'); $season2Start = new DateTime('1st April 2012'); $season2End = new DateTime('1st October 2012'); $allDays = new DatePeriod($startDate, new DateInterval('P1D'), $endDate); $season2Days = new DatePeriod($season2Start, new DateInterval('P1D'), $season2End); $seasonArray = array(); foreach($allDays as $day){ $seasonArray[] = $day->format('d-M'); $seasonArray[$day->format('d-M')]['season'] = 1; } foreach($season2Days as $day){ $seasonArray[$day->format('d-M')]['season'] = 2; } return $seasonArray; } 

Once this is done, you just need a period for which you need to calculate: -

 $bookingStartDate = new DateTime();//Or wherever you get this from $bookingEndDate = new DateTime(); $bookingEndDate->setTimestamp(strtotime('+ 7 month'));//Or wherever you get this from $bookingPeriod = new DatePeriod($bookingStartDate, new DateInterval('P1D'), $bookingEndDate); 

Then we can make the calculation: -

 $seasons = getSeasonArray(); $totalCost = 0; foreach($bookingPeriod as $day){ $totalCost += $seasons[$day->format('d-M')]['season']; var_dump($day->format('d-M') . ' = $' . $seasons[$day->format('d-M')]['season']); } var_dump($totalCost); 

I chose a long booking period so you can look at the result of var_dump () and check the correct price for each day of the year.

This is a quick blow made between distracting actions at work, and I am sure that with a little thought you can turn it into a more elegant solution. I would like to get rid of the double iteration, for example, unfortunately, the working pressure prevents me from spending extra time on this.

Read more about these useful classes on the DateTime page for more information about these useful classes.

+3
source

At first I suggested using the DateTime class that PHP provides, naively believing that it has some kind of thoughtful API that could be used, It turns out that it is not. Although it has very basic DateTime functionality, it is mostly unusable because for most operations it uses the DateInterval class. Combined, these classes are another masterpiece of poor API design.

The interval should be defined as follows:

An interval in Joda-Time is an interval of time from one millisecond moment to another moment. Both points are fully defined points in the datetime continuum, with a time zone.

In PHP, however, an interval is simply a duration:

A date interval stores a fixed amount of time (in years, months, days, hours, etc.) or a relative time string [for example, “2 days”].

Unfortunately, the PHP definition of DateInterval does not allow you to calculate the intersection / overlap (which is required by the OP), since PHP intervals do not have a specific position in the datetime continuum. So I implemented a (very rudimentary) class that adheres to the definition of a JodaTime interval. It does not pass rigorous testing, but it must do the job:

 class ProperDateInterval { private $start = null; private $end = null; public function __construct(DateTime $start, DateTime $end) { $this->start = $start; $this->end = $end; } /** * Does this time interval overlap the specified time interval. */ public function overlaps(ProperDateInterval $other) { $start = $this->getStart()->getTimestamp(); $end = $this->getEnd()->getTimestamp(); $oStart = $other->getStart()->getTimestamp(); $oEnd = $other->getEnd()->getTimestamp(); return $start < $oEnd && $oStart < $end; } /** * Gets the overlap between this interval and another interval. */ public function overlap(ProperDateInterval $other) { if(!$this->overlaps($other)) { // I haven't decided what should happen here yet. // Returning "null" doesn't seem like a good solution. // Maybe ProperDateInterval::EMPTY? throw new Exception("No intersection."); } $start = $this->getStart()->getTimestamp(); $end = $this->getEnd()->getTimestamp(); $oStart = $other->getStart()->getTimestamp(); $oEnd = $other->getEnd()->getTimestamp(); $overlapStart = NULL; $overlapEnd = NULL; if($start === $oStart || $start > $oStart) { $overlapStart = $this->getStart(); } else { $overlapStart = $other->getStart(); } if($end === $oEnd || $end < $oEnd) { $overlapEnd = $this->getEnd(); } else { $overlapEnd = $other->getEnd(); } return new ProperDateInterval($overlapStart, $overlapEnd); } /** * @return long The duration of this interval in seconds. */ public function getDuration() { return $this->getEnd()->getTimestamp() - $this->getStart()->getTimestamp(); } public function getStart() { return $this->start; } public function getEnd() { return $this->end; } } 

It can be used like this:

 $seasonStart = DateTime::createFromFormat('jM-Y', '01-Apr-2012'); $seasonEnd = DateTime::createFromFormat('jM-Y', '30-Sep-2012'); $userStart = DateTime::createFromFormat('jM-Y', '01-Jan-2012'); $userEnd = DateTime::createFromFormat('jM-Y', '02-Apr-2012'); $i1 = new ProperDateInterval($seasonStart, $seasonEnd); $i2 = new ProperDateInterval($userStart, $userEnd); $overlap = $i1->overlap($i2); var_dump($overlap->getDuration()); 
+2
source

Source: https://habr.com/ru/post/1444055/


All Articles