4

从 10 月 1 日到 3 月 31 日,费用为 1 美元(第 1 季)。从 4 月 1 日到 9 月 30 日,费用为 2 美元(第 2 季)。

如何根据该日期范围的第 1 季和第 2 季的天数计算给定日期范围(用户输入)的总费用?

以下给出了用户日期范围的天数,但我不知道如何针对第 1 季或第 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 );

鉴于日期范围从 2012 年开始和结束,或者仅在 2012 年开始和 2013 年结束,就给了我 10 种不同的可能性,即日期范围可以在哪个季节开始以及在哪里结束。

对于以下情况,必须有比迭代 if/else 和一遍又一遍地比较日期更好的解决方案:

  1. 日期范围完全在第 1 季内
  2. 日期范围从第 1 季开始,到第 2 季结束
  3. 日期范围从第 1 季开始,跨越第 2 季并在第 1 季的第二部分结束

...等等“从第 2 季开始”等等

这不是距离 XYZ 日期还有多少天?因为这只涉及计算天数。它没有解决将一个日期范围与另一个日期范围进行比较的问题。

4

2 回答 2

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 手册页

于 2012-11-05T10:57:41.170 回答
2

起初我建议使用 PHP 提供的DateTime 类,天真地假设它有某种可以使用的深思熟虑的 API。事实证明,事实并非如此。虽然它具有非常基本的 DateTime 功能,但它大多无法使用,因为对于大多数操作,它依赖于DateInterval类。结合起来,这些类代表了糟糕的 API 设计的另一个杰作。

间隔应该像这样定义:

Joda-Time 中的间隔表示从一个毫秒瞬间到另一个瞬间的时间间隔。这两个瞬间都是日期时间连续体中完全指定的瞬间,并带有时区。

然而,在 PHP 中,间隔只是一个持续时间:

日期间隔存储固定的时间量(以年、月、日、小时等为单位)或相对时间字符串 [例如“2 天”]。

不幸的是,PHP 的 DateInterval 定义不允许交叉/重叠计算(OP 需要),因为 PHP 的 Intervals 在日期时间连续体中没有特定位置。因此,我实现了一个(非常基本的)类,它遵循 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());
于 2012-11-05T07:50:44.340 回答