2012-11-05 126 views
4

從10月1日至3月31日的費用爲$ 1(第一季)之內。從4月1日到9月30日,費用爲2美元(第2季)。計數的日期範圍內有多少天是另一個日期範圍

如何計算給定日期範圍(用戶輸入)的總費用根據此日期降幅多少天到第1季和第2季?

下使我user's日期範圍內的天數,但我不知道如何測試對第1季或第二季:

$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); 

鑑於日期範圍開始和結束在2012年或2012年開始,僅在2013年結束,這給我10種不同的可能性,在哪個季節日期範圍可以開始,可以結束。

必須有比迭代的if/else和下列條件一遍遍比較日期時更好的解決方案:

  1. 日期範圍完全在賽季1
  2. 日期範圍在第1季開始,在賽季結束2
  3. 日期範圍開始於第1季,整個賽季2橫跨在本賽季的第二部分結束1

...等等與「在賽季開始2" 等

這不是How many days until X-Y-Z date?與計算天數,只有交易的副本。它沒有解決將一個日期範圍與另一個日期範圍進行比較的問題。

+0

可能重複[XXX日期之前的幾天?](http://stackoverflow.com/questions/654363/how-many-days-until-xxx-date) – fresskoma

+1

這不是重複的。他需要比較幾個日期範圍的重疊,而不僅僅是計算天數。 – vascowhite

+0

@vascowhite:是的,我最初誤解了這個問題。不幸的是,PHP DateTime API不是特別精心設計的(意想不到的)。我目前正在檢查是否有簡單的解決方案來使用API​​來完成此操作。 – fresskoma

回答

3

這個問題的關鍵是要簡化它儘可能多地。我認爲使用一個數組作爲一年的每一天的費用查找表是一條路。首先要做的是生成數組。數組僅代表一年中的每一天,並不代表任何特定的年份。我選擇使用2012來生成查找數組,因爲這是一個閏年,所以每一天都有可能。

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

一旦做到這一點,你只需要本期較其計算: -

$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); 

然後我們可以做計算: -

$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); 

我已經選擇了一個長期預訂期間,以便您可以掃描var_dump()輸出並驗證一年中每一天的正確價格。

這是在工作分心之間做的一個快速刺,我敢肯定,有一點思考,你可以把它塑造成一個更優雅的解決方案。例如,我想擺脫雙迭代,不幸的是,工作壓力使我無法在這方面花費更多的時間。

有關這些有用類別的更多信息,請參閱PHP DateTime man page

2

起初我建議使用PHP提供,天真地假設它有某種深思熟慮的API,人們可以使用的DateTime class。事實證明,事實並非如此。雖然它的功能非常基本的日期時間的功能,它主要是不可用,因爲,對於大多數操作,它依賴於DateInterval類。組合起來,這些類代表了糟糕的API設計的另一個傑作。

的間隔定義像這樣:

在約達時間的間隔表示時間從一毫秒時刻到另一時刻的間隔。這兩個時刻都是在日期時間連續體中完全指定的時刻,並且包含時區。

在PHP中,然而,間隔僅僅是一個持續的時間:

甲日期間隔存儲任一固定的時間量(以年,月,日,小時等)或相對時間串[如「2天」]。

不幸的是,PHP的DateInterval定義不允許相交/重疊計算(OP需要),因爲PHP的間隔在日期時間連續區中沒有特定的位置。因此,我實現了一個遵循JodaTime定義的時間間隔的(非常基本的)類。它不是廣泛的測試,但它應該完成工作:

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

它可能像這樣被使用:

$seasonStart = DateTime::createFromFormat('j-M-Y', '01-Apr-2012'); 
$seasonEnd = DateTime::createFromFormat('j-M-Y', '30-Sep-2012'); 

$userStart = DateTime::createFromFormat('j-M-Y', '01-Jan-2012'); 
$userEnd = DateTime::createFromFormat('j-M-Y', '02-Apr-2012'); 

$i1 = new ProperDateInterval($seasonStart, $seasonEnd); 
$i2 = new ProperDateInterval($userStart, $userEnd); 

$overlap = $i1->overlap($i2); 
var_dump($overlap->getDuration());