Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.44% covered (success)
93.44%
57 / 61
33.33% covered (danger)
33.33%
2 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
RangeCheckerHelper
93.44% covered (success)
93.44%
57 / 61
33.33% covered (danger)
33.33%
2 / 6
25.18
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 standardize
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 getComparison
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
6.02
 getDifference
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
5.01
 getDifferenceInYears
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
9
 isFutureTime
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Helper;
4
5use DataValues\DataValue;
6use DataValues\QuantityValue;
7use DataValues\TimeValue;
8use DataValues\TimeValueCalculator;
9use DataValues\UnboundedQuantityValue;
10use InvalidArgumentException;
11use MediaWiki\Config\Config;
12use ValueParsers\IsoTimestampParser;
13use ValueParsers\ValueParser;
14use Wikibase\Lib\Units\UnitConverter;
15
16/**
17 * Class for helper functions for range checkers.
18 *
19 * @author BP2014N1
20 * @license GPL-2.0-or-later
21 */
22class RangeCheckerHelper {
23
24    /**
25     * @var Config
26     */
27    private $config;
28
29    /**
30     * @var ValueParser
31     */
32    private $timeParser;
33
34    /**
35     * @var TimeValueCalculator
36     */
37    private $timeCalculator;
38
39    /**
40     * @var TimeValueComparer
41     */
42    private $timeValueComparer;
43
44    /**
45     * @var UnitConverter|null
46     */
47    private $unitConverter;
48
49    public function __construct(
50        Config $config,
51        UnitConverter $unitConverter = null
52    ) {
53        $this->config = $config;
54        $this->timeParser = new IsoTimestampParser();
55        $this->timeCalculator = new TimeValueCalculator();
56        $this->timeValueComparer = new TimeValueComparer( $this->timeCalculator );
57        $this->unitConverter = $unitConverter;
58    }
59
60    /**
61     * @param UnboundedQuantityValue $value
62     * @return UnboundedQuantityValue $value converted to standard units if possible, otherwise unchanged $value.
63     */
64    private function standardize( UnboundedQuantityValue $value ) {
65        if ( $this->unitConverter !== null ) {
66            $standard = $this->unitConverter->toStandardUnits( $value );
67            if ( $standard !== null ) {
68                return $standard;
69            } else {
70                return $value;
71            }
72        } else {
73            return $value;
74        }
75    }
76
77    /**
78     * Compare two values.
79     * If one of them is null, return 0 (equal).
80     *
81     * @param DataValue|null $lhs left-hand side
82     * @param DataValue|null $rhs right-hand side
83     *
84     * @throws InvalidArgumentException if the values do not both have the same, supported data value type
85     * @return integer An integer less than, equal to, or greater than zero
86     *                 when $lhs is respectively less than, equal to, or greater than $rhs.
87     *                 (In other words, just like the “spaceship” operator <=>.)
88     */
89    public function getComparison( DataValue $lhs = null, DataValue $rhs = null ) {
90        if ( $lhs === null || $rhs === null ) {
91            return 0;
92        }
93
94        if ( $lhs->getType() !== $rhs->getType() ) {
95            throw new InvalidArgumentException( 'Different data value types' );
96        }
97
98        switch ( $lhs->getType() ) {
99            case 'time':
100                /** @var TimeValue $lhs */
101                /** @var TimeValue $rhs */
102                '@phan-var TimeValue $lhs';
103                '@phan-var TimeValue $rhs';
104                return $this->timeValueComparer->getComparison( $lhs, $rhs );
105            case 'quantity':
106                /** @var QuantityValue|UnboundedQuantityValue $lhs */
107                /** @var QuantityValue|UnboundedQuantityValue $rhs */
108                '@phan-var QuantityValue|UnboundedQuantityValue $lhs';
109                '@phan-var QuantityValue|UnboundedQuantityValue $rhs';
110                $lhsStandard = $this->standardize( $lhs );
111                $rhsStandard = $this->standardize( $rhs );
112                return $lhsStandard->getAmount()->compare( $rhsStandard->getAmount() );
113        }
114
115        throw new InvalidArgumentException( 'Unsupported data value type' );
116    }
117
118    /**
119     * Computes $minuend - $subtrahend, in a format depending on the data type.
120     * For time values, the difference is in seconds;
121     * for quantity values, the difference is the numerical difference between the quantities,
122     * after attempting normalization of each side.
123     *
124     * @param TimeValue|QuantityValue|UnboundedQuantityValue $minuend
125     * @param TimeValue|QuantityValue|UnboundedQuantityValue $subtrahend
126     *
127     * @throws InvalidArgumentException if the values do not both have the same, supported data value type
128     * @return UnboundedQuantityValue
129     */
130    public function getDifference( DataValue $minuend, DataValue $subtrahend ) {
131        if ( $minuend->getType() === 'time' && $subtrahend->getType() === 'time' ) {
132            $minuendSeconds = $this->timeCalculator->getTimestamp( $minuend );
133            $subtrahendSeconds = $this->timeCalculator->getTimestamp( $subtrahend );
134            return UnboundedQuantityValue::newFromNumber(
135                $minuendSeconds - $subtrahendSeconds,
136                $this->config->get( 'WBQualityConstraintsSecondUnit' )
137            );
138        }
139        if ( $minuend->getType() === 'quantity' && $subtrahend->getType() === 'quantity' ) {
140            $minuendStandard = $this->standardize( $minuend );
141            $subtrahendStandard = $this->standardize( $subtrahend );
142            $minuendValue = $minuendStandard->getAmount()->getValueFloat();
143            $subtrahendValue = $subtrahendStandard->getAmount()->getValueFloat();
144            $diff = $minuendValue - $subtrahendValue;
145            // we don’t check whether both quantities have the same standard unit –
146            // that’s the job of a different constraint type, Units (T164372)
147            return UnboundedQuantityValue::newFromNumber( $diff, $minuendStandard->getUnit() );
148        }
149
150        throw new InvalidArgumentException( 'Unsupported or different data value types' );
151    }
152
153    public function getDifferenceInYears( TimeValue $minuend, TimeValue $subtrahend ) {
154        if ( !preg_match( '/^([-+]\d{1,16})-(.*)$/', $minuend->getTime(), $minuendMatches ) ||
155            !preg_match( '/^([-+]\d{1,16})-(.*)$/', $subtrahend->getTime(), $subtrahendMatches )
156        ) {
157            throw new InvalidArgumentException( 'TimeValue::getTime() did not match expected format' );
158        }
159        $minuendYear = (float)$minuendMatches[1];
160        $subtrahendYear = (float)$subtrahendMatches[1];
161        $minuendRest = $minuendMatches[2];
162        $subtrahendRest = $subtrahendMatches[2];
163
164        // calculate difference of years
165        $diff = $minuendYear - $subtrahendYear;
166        if ( $minuendYear > 0.0 && $subtrahendYear < 0.0 ) {
167            $diff -= 1.0; // there is no year 0, remove it from difference
168        } elseif ( $minuendYear < 0.0 && $subtrahendYear > 0.0 ) {
169            $diff -= -1.0; // there is no year 0, remove it from negative difference
170        }
171
172        // adjust for date within year by parsing the month-day part within the same year
173        $minuendDateValue = $this->timeParser->parse( '+0000000000001970-' . $minuendRest );
174        $subtrahendDateValue = $this->timeParser->parse( '+0000000000001970-' . $subtrahendRest );
175        $minuendDateSeconds = $this->timeCalculator->getTimestamp( $minuendDateValue );
176        $subtrahendDateSeconds = $this->timeCalculator->getTimestamp( $subtrahendDateValue );
177        if ( $minuendDateSeconds < $subtrahendDateSeconds ) {
178            // difference in the last year is actually less than one full year
179            // e. g. 1975-03-01 - 1974-09-01 is just six months
180            // (we don’t need sub-year precision in the difference, adjusting by 0.5 is enough)
181            $diff -= 0.5;
182        } elseif ( $minuendDateSeconds > $subtrahendDateSeconds ) {
183            // difference in the last year is actually more than one full year
184            // e. g. 1975-09-01 - 1974-03-01 is 18 months
185            // (we don’t need sub-year precision in the difference, adjusting by 0.5 is enough)
186            $diff += 0.5;
187        }
188
189        $unit = $this->config->get( 'WBQualityConstraintsYearUnit' );
190        return UnboundedQuantityValue::newFromNumber( $diff, $unit );
191    }
192
193    public function isFutureTime( TimeValue $timeValue ) {
194        return $this->timeValueComparer->isFutureTime( $timeValue );
195    }
196
197}