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

この問題の鍵は、可能な限り単純化することです。年間の各日のコストのルックアップ テーブルとして配列を使用するのがよい方法だと思います。最初に行うことは、配列を生成することです。配列は 1 年の各日を表すだけで、特定の年を表すものではありません。2012 年はうるう年であり、可能なすべての日が含まれているため、ルックアップ配列を生成するために 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 ページを参照してください。

于 2012-11-05T10:57:41.170 に答える
2

最初に私は、PHP が提供するDateTime クラスを使用することを提案しました。単純に、使用できる何らかの考え抜かれた API があると想定していました。そうではないことがわかりました。非常に基本的な DateTime 機能を備えていますが、ほとんどの操作でDateIntervalクラスに依存しているため、ほとんど使用できません。これらのクラスの組み合わせは、不適切な API 設計のもう 1 つの傑作です。

間隔は次のように定義する必要があります。

Joda-Time の間隔は、あるミリ秒の瞬間から別の瞬間までの時間間隔を表します。両方のインスタントは、日時連続体の完全に指定されたインスタントであり、タイム ゾーンを備えています。

ただし、PHP では、Interval は単なる期間です。

日付間隔には、一定の時間 (年、月、日、時間など) または相対時間文字列 (「2 日」など) が格納されます。

残念ながら、PHP の Intervals には日時連続体の特定の位置がないため、PHP の DateInterval 定義では交差/重複計算 (OP が必要とする) が許可されていません。したがって、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 に答える