is_empty(); } ) ); usort( $intervals, static function ( Interval $a, Interval $b ) { return $a->from <=> $b->from; } ); $current_index = null; foreach ( $intervals as $index => $interval ) { if ( $interval->covers( $now ) ) { $current_index = $index; } } if ( null === $current_index ) { return new Reference( Reference::STATUS_NO_DATA ); } $current = $intervals[ $current_index ]; if ( ! $current->reduced ) { if ( self::MODE_ALWAYS !== $mode ) { return new Reference( Reference::STATUS_NOT_REDUCED ); } // Sliding window that includes today's price. return $this->lowest( $intervals, $now - $days * self::DAY, $now + 1, null, $extra ); } $t0 = $this->reduction_start( $intervals, $current_index ); return $this->lowest( $intervals, $t0 - $days * self::DAY, $t0, $t0, $extra ); } /** * Walks back over contiguous reduced intervals whose price never went up, * to find where the progressive reduction began. * * @param Interval[] $intervals Sorted by start. */ private function reduction_start( array $intervals, int $index ): int { while ( $index > 0 ) { $previous = $intervals[ $index - 1 ]; $current = $intervals[ $index ]; if ( ! $previous->reduced || $previous->to !== $current->from || $current->price > $previous->price + 0.00005 ) { break; } --$index; } return $intervals[ $index ]->from; } /** * Lowest price among intervals overlapping [start, end). * * @param Interval[] $intervals Sorted by start. * @param int $start Start of the period. * @param int $end End of the period (exclusive). * @param int|null $t0 Start of the reduction. * @param Interval[] $extra Count for the price, not for the completeness of the history. */ private function lowest( array $intervals, int $start, int $end, ?int $t0, array $extra = array() ): Reference { $lowest = null; $earliest = null; foreach ( $intervals as $interval ) { if ( ! $interval->overlaps( $start, $end ) ) { continue; } if ( null === $lowest || $interval->price < $lowest ) { $lowest = $interval->price; } if ( null === $earliest || $interval->from < $earliest ) { $earliest = $interval->from; } } if ( null === $lowest ) { return new Reference( Reference::STATUS_NO_HISTORY, null, $t0, false ); } foreach ( $extra as $interval ) { if ( ! $interval->is_empty() && $interval->overlaps( $start, $end ) && $interval->price < $lowest ) { $lowest = $interval->price; } } return new Reference( Reference::STATUS_OK, $lowest, $t0, $earliest <= $start ); } }