mirror of
https://code.mensbeam.com/MensBeam/Arsse.git
synced 2024-12-22 13:12:41 +00:00
Add handling of DateInterval objects to ValueInfo
This commit is contained in:
parent
4670dfc849
commit
b0643de21c
2 changed files with 412 additions and 175 deletions
|
@ -28,6 +28,7 @@ class ValueInfo {
|
|||
const T_DATE = 5; // convert to DateTimeInterface instance
|
||||
const T_STRING = 6; // convert to string
|
||||
const T_ARRAY = 7; // convert to array
|
||||
const T_INTERVAL = 8; // convert to time interval
|
||||
// normalization modes
|
||||
const M_NULL = 1 << 28; // pass nulls through regardless of target type
|
||||
const M_DROP = 1 << 29; // drop the value (return null) if the type doesn't match
|
||||
|
@ -95,6 +96,29 @@ class ValueInfo {
|
|||
throw new ExceptionType("strictFailure", $type);
|
||||
}
|
||||
return (!$drop) ? (int) $value->getTimestamp(): null;
|
||||
} elseif ($value instanceof \DateInterval) {
|
||||
if ($strict && !$drop) {
|
||||
throw new ExceptionType("strictFailure", $type);
|
||||
} elseif ($drop) {
|
||||
return null;
|
||||
} else {
|
||||
// returns the number of seconds in the interval
|
||||
// days are assumed to contain (60 * 60 * 24) seconds
|
||||
// months are assumed to contain 30 days
|
||||
// years are assumed to contain 365 days
|
||||
$s = 0;
|
||||
if ($value->days !== false) {
|
||||
$s += ($value->days * 24 * 60 * 60);
|
||||
} else {
|
||||
$s += ($value->y * 365 * 24 * 60 * 60);
|
||||
$s += ($value->m * 30 * 24 * 60 * 60);
|
||||
$s += ($value->d * 24 * 60 * 60);
|
||||
}
|
||||
$s += ($value->h * 60 * 60);
|
||||
$s += ($value->i * 60);
|
||||
$s += $value->s;
|
||||
return $s;
|
||||
}
|
||||
}
|
||||
$info = self::int($value);
|
||||
if ($strict && !($info & self::VALID)) {
|
||||
|
@ -124,6 +148,16 @@ class ValueInfo {
|
|||
throw new ExceptionType("strictFailure", $type);
|
||||
}
|
||||
return (!$drop) ? (float) $value->getTimestamp(): null;
|
||||
} elseif ($value instanceof \DateInterval) {
|
||||
if ($drop) {
|
||||
return null;
|
||||
} elseif ($strict) {
|
||||
throw new ExceptionType("strictFailure", $type);
|
||||
}
|
||||
// convert the interval to an integer, and then add microseconds if available (since PHP 7.1, for intervals created from a DateTime difference operation)
|
||||
$out = (float) self::normalize($value, self::T_INT);
|
||||
$out += isset($value->f) ? $value->f : 0.0;
|
||||
return $out;
|
||||
} elseif (is_bool($value) && $strict) {
|
||||
if ($drop) {
|
||||
return null;
|
||||
|
@ -151,6 +185,25 @@ class ValueInfo {
|
|||
} elseif ($value instanceof \DateTime) {
|
||||
return \DateTimeImmutable::createFromMutable($value)->setTimezone(new \DateTimeZone("UTC"))->format($dateOutFormat);
|
||||
}
|
||||
} elseif ($value instanceof \DateInterval) {
|
||||
$dateSpec = "";
|
||||
$timeSpec = "";
|
||||
if ($value->days) {
|
||||
$dateSpec = $value->days."D";
|
||||
} else {
|
||||
$dateSpec .= $value->y ? $value->y."Y": "";
|
||||
$dateSpec .= $value->m ? $value->m."M": "";
|
||||
$dateSpec .= $value->d ? $value->d."D": "";
|
||||
}
|
||||
$timeSpec .= $value->h ? $value->h."H": "";
|
||||
$timeSpec .= $value->i ? $value->i."M": "";
|
||||
$timeSpec .= $value->s ? $value->s."S": "";
|
||||
$timeSpec = $timeSpec ? "T".$timeSpec : "";
|
||||
if (!$dateSpec && !$timeSpec) {
|
||||
return "PT0S";
|
||||
} else {
|
||||
return "P".$dateSpec.$timeSpec;
|
||||
}
|
||||
} elseif (is_float($value) && is_finite($value)) {
|
||||
$out = (string) $value;
|
||||
if (!strpos($out, "E")) {
|
||||
|
@ -183,7 +236,7 @@ class ValueInfo {
|
|||
return \DateTimeImmutable::createFromMutable($value)->setTimezone(new \DateTimeZone("UTC"));
|
||||
} elseif (is_int($value)) {
|
||||
return \DateTimeImmutable::createFromFormat("U", (string) $value, new \DateTimeZone("UTC"));
|
||||
} elseif (is_float($value)) {
|
||||
} elseif (is_float($value) && is_finite($value)) {
|
||||
return \DateTimeImmutable::createFromFormat("U.u", sprintf("%F", $value), new \DateTimeZone("UTC"));
|
||||
} elseif (is_string($value)) {
|
||||
try {
|
||||
|
@ -252,6 +305,92 @@ class ValueInfo {
|
|||
}
|
||||
}
|
||||
break; // @codeCoverageIgnore
|
||||
case self::T_INTERVAL:
|
||||
if ($value instanceof \DateInterval) {
|
||||
if ($value->invert) {
|
||||
$value = clone $value;
|
||||
$value->invert = 0;
|
||||
}
|
||||
$value->f = $value->f ?? 0.0; // add microseconds for PHP 7.0
|
||||
return $value;
|
||||
} elseif (is_null($value)) {
|
||||
if ($strict && !$drop && !$allowNull) {
|
||||
throw new ExceptionType("strictFailure", $type);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} elseif (is_bool($value) || is_array($value) || (is_float($value) && (is_infinite($value) || is_nan($value))) || $value instanceof \DateTimeInterface || (is_object($value) && !method_exists($value, "__toString"))) {
|
||||
if ($strict && !$drop) {
|
||||
throw new ExceptionType("strictFailure", $type);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} elseif (is_string($value) || is_object($value)) {
|
||||
try {
|
||||
$out = new \DateInterval((string) $value);
|
||||
$out->f = 0.0;
|
||||
return $out;
|
||||
} catch (\Exception $e) {
|
||||
if ($strict && !$drop) {
|
||||
throw new ExceptionType("strictFailure", $type);
|
||||
} elseif ($drop) {
|
||||
return null;
|
||||
} elseif (strtotime("now + $value") !== false) {
|
||||
$out = \DateInterval::createFromDateString($value);
|
||||
$out->f = 0.0;
|
||||
return $out;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} elseif ($drop) {
|
||||
return null;
|
||||
} elseif ($strict) {
|
||||
throw new ExceptionType("strictFailure", $type);
|
||||
} else {
|
||||
// input is a number, assume this is a number of seconds
|
||||
// for legibility we convert large numbers to minutes, hours, and days as necessary
|
||||
// the DateInterval constructor only allows 12 digits for any given part of an interval,
|
||||
// so we also convert days to 365-day years where we must, and cap the number of years
|
||||
// at (1e11 - 1); this being a very large number, the loss of precision is probably not
|
||||
// significant in practical usage
|
||||
$sec = abs($value);
|
||||
$msec = (float) ($sec - (int) $sec);
|
||||
$sec = (int) $sec;
|
||||
$min = 0;
|
||||
$hour = 0;
|
||||
$day = 0;
|
||||
$year = 0;
|
||||
if ($sec >= 60) {
|
||||
$min = ($sec - ($sec % 60)) / 60;
|
||||
$sec %= 60;
|
||||
}
|
||||
if ($min >= 60) {
|
||||
$hour = ($min - ($min % 60)) / 60;
|
||||
$min %= 60;
|
||||
}
|
||||
if ($hour >= 24) {
|
||||
$day = ($hour - ($hour % 24)) / 24;
|
||||
$hour %= 24;
|
||||
}
|
||||
if ($day >= 999999999999) {
|
||||
$year = ($day - ($day % 365)) / 365;
|
||||
$day %= 365;
|
||||
}
|
||||
$spec = "P";
|
||||
$spec .= $year ? $year."Y" : "";
|
||||
$spec .= $day ? $day."D" : "";
|
||||
$spec .= "T";
|
||||
$spec .= $hour ? $hour."H" : "";
|
||||
$spec .= $min ? $min."M" : "";
|
||||
$spec .= $sec ? $sec."S" : "";
|
||||
$spec .= ($spec === "PT") ? "0S" : "";
|
||||
$spec = trim($spec, "T");
|
||||
$out = new \DateInterval($spec);
|
||||
$out->f = $msec;
|
||||
return $out;
|
||||
}
|
||||
break; // @codeCoverageIgnore
|
||||
default:
|
||||
throw new ExceptionType("typeUnknown", $type); // @codeCoverageIgnore
|
||||
}
|
||||
|
|
|
@ -310,107 +310,8 @@ class TestValueInfo extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
}
|
||||
}
|
||||
|
||||
public function testNormalizeValues() {
|
||||
$tests = [
|
||||
/* The test data are very dense for this set. Each value is normalized to each of the following types:
|
||||
|
||||
- mixed (no normalization performed)
|
||||
- null
|
||||
- boolean
|
||||
- integer
|
||||
- float
|
||||
- string
|
||||
- array
|
||||
|
||||
For each of these types, there is an expected output value, as well as a boolean indicating whether
|
||||
the value should pass or fail a strict normalization. Conversion to DateTime is covered below by a different data set
|
||||
*/
|
||||
/* Input value null bool int float string array */
|
||||
[null, [null,true], [false,false], [0, false], [0.0, false], ["", false], [[], false]],
|
||||
["", [null,true], [false,true], [0, false], [0.0, false], ["", true], [[""], false]],
|
||||
[1, [null,true], [true, true], [1, true], [1.0, true], ["1", true], [[1], false]],
|
||||
[PHP_INT_MAX, [null,true], [true, false], [PHP_INT_MAX, true], [(float) PHP_INT_MAX,true], [(string) PHP_INT_MAX, true], [[PHP_INT_MAX], false]],
|
||||
[1.0, [null,true], [true, true], [1, true], [1.0, true], ["1", true], [[1.0], false]],
|
||||
["1.0", [null,true], [true, true], [1, true], [1.0, true], ["1.0", true], [["1.0"], false]],
|
||||
["001.0", [null,true], [true, true], [1, true], [1.0, true], ["001.0", true], [["001.0"], false]],
|
||||
["1.0e2", [null,true], [true, false], [100, true], [100.0, true], ["1.0e2", true], [["1.0e2"], false]],
|
||||
["1", [null,true], [true, true], [1, true], [1.0, true], ["1", true], [["1"], false]],
|
||||
["001", [null,true], [true, true], [1, true], [1.0, true], ["001", true], [["001"], false]],
|
||||
["1e2", [null,true], [true, false], [100, true], [100.0, true], ["1e2", true], [["1e2"], false]],
|
||||
["+1.0", [null,true], [true, true], [1, true], [1.0, true], ["+1.0", true], [["+1.0"], false]],
|
||||
["+001.0", [null,true], [true, true], [1, true], [1.0, true], ["+001.0", true], [["+001.0"], false]],
|
||||
["+1.0e2", [null,true], [true, false], [100, true], [100.0, true], ["+1.0e2", true], [["+1.0e2"], false]],
|
||||
["+1", [null,true], [true, true], [1, true], [1.0, true], ["+1", true], [["+1"], false]],
|
||||
["+001", [null,true], [true, true], [1, true], [1.0, true], ["+001", true], [["+001"], false]],
|
||||
["+1e2", [null,true], [true, false], [100, true], [100.0, true], ["+1e2", true], [["+1e2"], false]],
|
||||
[0, [null,true], [false,true], [0, true], [0.0, true], ["0", true], [[0], false]],
|
||||
["0", [null,true], [false,true], [0, true], [0.0, true], ["0", true], [["0"], false]],
|
||||
["000", [null,true], [false,true], [0, true], [0.0, true], ["000", true], [["000"], false]],
|
||||
[0.0, [null,true], [false,true], [0, true], [0.0, true], ["0", true], [[0.0], false]],
|
||||
["0.0", [null,true], [false,true], [0, true], [0.0, true], ["0.0", true], [["0.0"], false]],
|
||||
["000.000", [null,true], [false,true], [0, true], [0.0, true], ["000.000", true], [["000.000"], false]],
|
||||
["+0", [null,true], [false,true], [0, true], [0.0, true], ["+0", true], [["+0"], false]],
|
||||
["+000", [null,true], [false,true], [0, true], [0.0, true], ["+000", true], [["+000"], false]],
|
||||
["+0.0", [null,true], [false,true], [0, true], [0.0, true], ["+0.0", true], [["+0.0"], false]],
|
||||
["+000.000", [null,true], [false,true], [0, true], [0.0, true], ["+000.000", true], [["+000.000"], false]],
|
||||
[-1, [null,true], [true, false], [-1, true], [-1.0, true], ["-1", true], [[-1], false]],
|
||||
[-1.0, [null,true], [true, false], [-1, true], [-1.0, true], ["-1", true], [[-1.0], false]],
|
||||
["-1.0", [null,true], [true, false], [-1, true], [-1.0, true], ["-1.0", true], [["-1.0"], false]],
|
||||
["-001.0", [null,true], [true, false], [-1, true], [-1.0, true], ["-001.0", true], [["-001.0"], false]],
|
||||
["-1.0e2", [null,true], [true, false], [-100, true], [-100.0, true], ["-1.0e2", true], [["-1.0e2"], false]],
|
||||
["-1", [null,true], [true, false], [-1, true], [-1.0, true], ["-1", true], [["-1"], false]],
|
||||
["-001", [null,true], [true, false], [-1, true], [-1.0, true], ["-001", true], [["-001"], false]],
|
||||
["-1e2", [null,true], [true, false], [-100, true], [-100.0, true], ["-1e2", true], [["-1e2"], false]],
|
||||
[-0, [null,true], [false,true], [0, true], [0.0, true], ["0", true], [[-0], false]],
|
||||
["-0", [null,true], [false,true], [0, true], [-0.0, true], ["-0", true], [["-0"], false]],
|
||||
["-000", [null,true], [false,true], [0, true], [-0.0, true], ["-000", true], [["-000"], false]],
|
||||
[-0.0, [null,true], [false,true], [0, true], [-0.0, true], ["-0", true], [[-0.0], false]],
|
||||
["-0.0", [null,true], [false,true], [0, true], [-0.0, true], ["-0.0", true], [["-0.0"], false]],
|
||||
["-000.000", [null,true], [false,true], [0, true], [-0.0, true], ["-000.000", true], [["-000.000"], false]],
|
||||
[false, [null,true], [false,true], [0, false], [0.0, false], ["", false], [[false], false]],
|
||||
[true, [null,true], [true, true], [1, false], [1.0, false], ["1", false], [[true], false]],
|
||||
["on", [null,true], [true, true], [0, false], [0.0, false], ["on", true], [["on"], false]],
|
||||
["off", [null,true], [false,true], [0, false], [0.0, false], ["off", true], [["off"], false]],
|
||||
["yes", [null,true], [true, true], [0, false], [0.0, false], ["yes", true], [["yes"], false]],
|
||||
["no", [null,true], [false,true], [0, false], [0.0, false], ["no", true], [["no"], false]],
|
||||
["true", [null,true], [true, true], [0, false], [0.0, false], ["true", true], [["true"], false]],
|
||||
["false", [null,true], [false,true], [0, false], [0.0, false], ["false", true], [["false"], false]],
|
||||
[INF, [null,true], [true, false], [0, false], [INF, true], ["INF", false], [[INF], false]],
|
||||
[-INF, [null,true], [true, false], [0, false], [-INF, true], ["-INF", false], [[-INF], false]],
|
||||
[NAN, [null,true], [false,false], [0, false], [NAN, true], ["NAN", false], [[], false]],
|
||||
[[], [null,true], [false,false], [0, false], [0.0, false], ["", false], [[], true] ],
|
||||
["some string", [null,true], [true, false], [0, false], [0.0, false], ["some string", true], [["some string"], false]],
|
||||
[" ", [null,true], [true, false], [0, false], [0.0, false], [" ", true], [[" "], false]],
|
||||
[new \StdClass, [null,true], [true, false], [0, false], [0.0, false], ["", false], [[new \StdClass], false]],
|
||||
[new StrClass(""), [null,true], [false,true], [0, false], [0.0, false], ["", true], [[new StrClass("")], false]],
|
||||
[new StrClass("1"), [null,true], [true, true], [1, true], [1.0, true], ["1", true], [[new StrClass("1")], false]],
|
||||
[new StrClass("0"), [null,true], [false,true], [0, true], [0.0, true], ["0", true], [[new StrClass("0")], false]],
|
||||
[new StrClass("-1"), [null,true], [true, false], [-1, true], [-1.0, true], ["-1", true], [[new StrClass("-1")], false]],
|
||||
[new StrClass("Msg"), [null,true], [true, false], [0, false], [0.0, false], ["Msg", true], [[new StrClass("Msg")], false]],
|
||||
[new StrClass(" "), [null,true], [true, false], [0, false], [0.0, false], [" ", true], [[new StrClass(" ")], false]],
|
||||
[2.5, [null,true], [true, false], [2, false], [2.5, true], ["2.5", true], [[2.5], false]],
|
||||
[0.5, [null,true], [true, false], [0, false], [0.5, true], ["0.5", true], [[0.5], false]],
|
||||
["2.5", [null,true], [true, false], [2, false], [2.5, true], ["2.5", true], [["2.5"], false]],
|
||||
["0.5", [null,true], [true, false], [0, false], [0.5, true], ["0.5", true], [["0.5"], false]],
|
||||
[$this->d("2010-01-01T00:00:00", 0, 0), [null,true], [true, false], [1262304000, false], [1262304000.0, false], ["2010-01-01T00:00:00Z",true], [[$this->d("2010-01-01T00:00:00", 0, 0)],false]],
|
||||
[$this->d("2010-01-01T00:00:00", 0, 1), [null,true], [true, false], [1262304000, false], [1262304000.0, false], ["2010-01-01T00:00:00Z",true], [[$this->d("2010-01-01T00:00:00", 0, 1)],false]],
|
||||
[$this->d("2010-01-01T00:00:00", 1, 0), [null,true], [true, false], [1262322000, false], [1262322000.0, false], ["2010-01-01T05:00:00Z",true], [[$this->d("2010-01-01T00:00:00", 1, 0)],false]],
|
||||
[$this->d("2010-01-01T00:00:00", 1, 1), [null,true], [true, false], [1262322000, false], [1262322000.0, false], ["2010-01-01T05:00:00Z",true], [[$this->d("2010-01-01T00:00:00", 1, 1)],false]],
|
||||
[1e14, [null,true], [true, false], [100000000000000,true], [1e14, true], ["100000000000000", true], [[1e14], false]],
|
||||
[1e-6, [null,true], [true, false], [0, false], [1e-6, true], ["0.000001", true], [[1e-6], false]],
|
||||
[[1,2,3], [null,true], [true, false], [0, false], [0.0, false], ["", false], [[1,2,3], true] ],
|
||||
[['a'=>1,'b'=>2], [null,true], [true, false], [0, false], [0.0, false], ["", false], [['a'=>1,'b'=>2], true] ],
|
||||
[new Result([['a'=>1,'b'=>2]]), [null,true], [true, false], [0, false], [0.0, false], ["", false], [[['a'=>1,'b'=>2]], true] ],
|
||||
];
|
||||
$params = [
|
||||
[I::T_MIXED, "Mixed" ],
|
||||
[I::T_NULL, "Null", ],
|
||||
[I::T_BOOL, "Boolean", ],
|
||||
[I::T_INT, "Integer", ],
|
||||
[I::T_FLOAT, "Floating point"],
|
||||
[I::T_STRING, "String", ],
|
||||
[I::T_ARRAY, "Array", ],
|
||||
];
|
||||
/** @dataProvider provideSimpleNormalizationValues */
|
||||
public function testNormalizeSimpleValues($input, string $typeName, $exp, bool $pass, bool $strict, bool $drop) {
|
||||
$assert = function($exp, $act, string $msg) {
|
||||
if (is_null($exp)) {
|
||||
$this->assertNull($act, $msg);
|
||||
|
@ -422,33 +323,259 @@ class TestValueInfo extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
$this->assertEquals($exp, $act, $msg);
|
||||
}
|
||||
};
|
||||
foreach ($params as $index => $param) {
|
||||
list($type, $name) = $param;
|
||||
$assert(null, I::normalize(null, $type | I::M_STRICT | I::M_NULL), $name." null-passthrough test failed");
|
||||
foreach ($tests as $test) {
|
||||
list($exp, $pass) = $index ? $test[$index] : [$test[$index], true];
|
||||
$value = $test[0];
|
||||
$assert($exp, I::normalize($value, $type), $name." test failed for value: ".var_export($value, true));
|
||||
if ($pass) {
|
||||
$assert($exp, I::normalize($value, $type | I::M_DROP), $name." drop test failed for value: ".var_export($value, true));
|
||||
$assert($exp, I::normalize($value, $type | I::M_STRICT), $name." error test failed for value: ".var_export($value, true));
|
||||
} else {
|
||||
$assert(null, I::normalize($value, $type | I::M_DROP), $name." drop test failed for value: ".var_export($value, true));
|
||||
$exc = new ExceptionType("strictFailure", $type);
|
||||
try {
|
||||
$act = I::normalize($value, $type | I::M_STRICT);
|
||||
} catch (ExceptionType $e) {
|
||||
$act = $e;
|
||||
} finally {
|
||||
$assert($exc, $act, $name." error test failed for value: ".var_export($value, true));
|
||||
}
|
||||
$typeConst = [
|
||||
'Mixed' => I::T_MIXED,
|
||||
'Null' => I::T_NULL,
|
||||
'Boolean' => I::T_BOOL,
|
||||
'Integer' => I::T_INT,
|
||||
'Floating point' => I::T_FLOAT,
|
||||
'String' => I::T_STRING,
|
||||
'Array' => I::T_ARRAY,
|
||||
'Date interval' => I::T_INTERVAL,
|
||||
][$typeName];
|
||||
if ($strict && $drop) {
|
||||
$modeName = "strict drop";
|
||||
$modeConst = I::M_STRICT | I::M_DROP;
|
||||
} elseif ($strict) {
|
||||
$modeName = "strict conversion";
|
||||
$modeConst = I::M_STRICT;
|
||||
} elseif ($drop) {
|
||||
$modeName = "drop";
|
||||
$modeConst = I::M_DROP;
|
||||
} else {
|
||||
$modeName = "loose conversion";
|
||||
$modeConst = 0;
|
||||
}
|
||||
if (is_null($input)) {
|
||||
// if the input value is null, perform a null passthrough test in addition to the test itself
|
||||
$this->assertNull(I::normalize($input, $typeConst | $modeConst | I::M_NULL), "$typeName null passthrough test failed.");
|
||||
}
|
||||
if (!$drop && $strict && !$pass) {
|
||||
// if we're performing a strict comparison and the value is supposed to fail, we should be getting an exception
|
||||
$this->assertException("strictFailure", "", "ExceptionType");
|
||||
I::normalize($input, $typeConst | $modeConst);
|
||||
$this->assertTrue(false, "$typename $modeName test expected exception");
|
||||
} elseif ($drop && !$pass) {
|
||||
// if we're performing a drop comparison and the value is supposed to fail, change the expectation to null
|
||||
$exp = null;
|
||||
}
|
||||
$assert($exp, I::normalize($input, $typeConst | $modeConst), "$typeName $modeName test failed.");
|
||||
// check that the result is the same even in null mode
|
||||
if (!is_null($input)) {
|
||||
$assert($exp, I::normalize($input, $typeConst | $modeConst | I::M_NULL), "$typeName $modeName (null pass-through) test failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/** @dataProvider provideDateNormalizationValues */
|
||||
public function testNormalizeDateValues($input, $format, $exp, bool $strict, bool $drop) {
|
||||
if ($strict && $drop) {
|
||||
$modeName = "strict drop";
|
||||
$modeConst = I::M_STRICT | I::M_DROP;
|
||||
} elseif ($strict) {
|
||||
$modeName = "strict conversion";
|
||||
$modeConst = I::M_STRICT;
|
||||
} elseif ($drop) {
|
||||
$modeName = "drop";
|
||||
$modeConst = I::M_DROP;
|
||||
} else {
|
||||
$modeName = "loose conversion";
|
||||
$modeConst = 0;
|
||||
}
|
||||
if (is_null($exp)) {
|
||||
if (is_null($input)) {
|
||||
// if the input value is null, perform a null passthrough test before the test itself
|
||||
$this->assertNull(I::normalize($input, I::T_DATE | $modeConst | I::M_NULL, $format, $format), "Date input format ".var_export($input, true)." failed $modeName (null passthrough) test.");
|
||||
}
|
||||
if (!$drop && $strict && is_null($exp)) {
|
||||
// if we're performing a strict comparison and the value is supposed to fail, we should be getting an exception
|
||||
$this->assertException("strictFailure", "", "ExceptionType");
|
||||
}
|
||||
$this->assertNull(I::normalize($input, I::T_DATE | $modeConst, $format, $format), "Date input format ".var_export($input, true)." failed $modeName test.");
|
||||
$this->assertNull(I::normalize($input, I::T_DATE | $modeConst | I::M_NULL, $format, $format), "Date input format ".var_export($input, true)." failed $modeName (null passthrough) test.");
|
||||
} else {
|
||||
$this->assertEquals($exp, I::normalize($input, I::T_DATE | $modeConst | I::M_NULL, $format, $format), "Date input format ".var_export($input, true)." failed $modeName (null passthrough) test.");
|
||||
$this->assertEquals($exp, I::normalize($input, I::T_DATE | $modeConst, $format, $format), "Date input format ".var_export($input, true)." failed $modeName test.");
|
||||
}
|
||||
}
|
||||
|
||||
public function testNormalizeComplexValues() {
|
||||
// Array-mode tests
|
||||
$tests = [
|
||||
[I::T_INT | I::M_DROP, [1, 2, 2.2, 3], [1,2,null,3] ],
|
||||
[I::T_INT, [1, 2, 2.2, 3], [1,2,2,3] ],
|
||||
[I::T_INT | I::M_DROP, new Result([1, 2, 2.2, 3]), [1,2,null,3] ],
|
||||
[I::T_INT, new Result([1, 2, 2.2, 3]), [1,2,2,3] ],
|
||||
[I::T_STRING | I::M_STRICT, "Bare string", ["Bare string"]],
|
||||
];
|
||||
foreach ($tests as $index => $test) {
|
||||
list($type, $value, $exp) = $test;
|
||||
$this->assertEquals($exp, I::normalize($value, $type | I::M_ARRAY, "iso8601"), "Failed test #$index");
|
||||
}
|
||||
// Date-to-string format tests
|
||||
$test = new \DateTimeImmutable("now", new \DateTimezone("UTC"));
|
||||
$exp = $test->format(I::DATE_FORMATS['iso8601'][1]);
|
||||
$this->assertSame($exp, I::normalize($test, I::T_STRING, null), "Failed test for null output date format");
|
||||
foreach (I::DATE_FORMATS as $name => $formats) {
|
||||
$exp = $test->format($formats[1]);
|
||||
$this->assertSame($exp, I::normalize($test, I::T_STRING, null, $name), "Failed test for output date format '$name'");
|
||||
}
|
||||
foreach (["U", "M j, Y (D)", "r", "c"] as $format) {
|
||||
$exp = $test->format($format);
|
||||
$this->assertSame($exp, I::normalize($test, I::T_STRING, null, $format), "Failed test for output date format '$format'");
|
||||
}
|
||||
}
|
||||
|
||||
public function provideSimpleNormalizationValues() {
|
||||
$types = [
|
||||
"Mixed",
|
||||
"Null",
|
||||
"Boolean",
|
||||
"Integer",
|
||||
"Floating point",
|
||||
"String",
|
||||
"Array",
|
||||
"Date interval",
|
||||
];
|
||||
$dateDiff = (new \DateTime("2017-01-01T00:00:00Z"))->diff((new \DateTime("2016-01-01T00:00:00Z"))); // 2016 was a leap year
|
||||
$dateNorm = clone $dateDiff;
|
||||
$dateNorm->f = 0.0;
|
||||
$dateNorm->invert = 0;
|
||||
foreach ([
|
||||
/* The test data are very dense for this set. Each value is normalized to each of the following types:
|
||||
|
||||
- mixed (no normalization performed)
|
||||
- null
|
||||
- boolean
|
||||
- integer
|
||||
- float
|
||||
- string
|
||||
- array
|
||||
- interval
|
||||
|
||||
For each of these types, there is an expected output value, as well as a boolean indicating whether
|
||||
the value should pass or fail a strict normalization. Conversion to DateTime is covered below by a different data set
|
||||
*/
|
||||
/* Input value null bool int float string array interval */
|
||||
[null, [null,true], [false,false], [0, false], [0.0, false], ["", false], [[], false], [null, false]],
|
||||
["", [null,true], [false,true], [0, false], [0.0, false], ["", true], [[""], false], [null, false]],
|
||||
[1, [null,true], [true, true], [1, true], [1.0, true], ["1", true], [[1], false], [$this->i("PT1S"), false]],
|
||||
[PHP_INT_MAX, [null,true], [true, false], [PHP_INT_MAX, true], [(float) PHP_INT_MAX,true], [(string) PHP_INT_MAX, true], [[PHP_INT_MAX], false], [$this->i("P292471208677Y195DT15H30M7S"), false]],
|
||||
[1.0, [null,true], [true, true], [1, true], [1.0, true], ["1", true], [[1.0], false], [$this->i("PT1S"), false]],
|
||||
["1.0", [null,true], [true, true], [1, true], [1.0, true], ["1.0", true], [["1.0"], false], [null, false]],
|
||||
["001.0", [null,true], [true, true], [1, true], [1.0, true], ["001.0", true], [["001.0"], false], [null, false]],
|
||||
["1.0e2", [null,true], [true, false], [100, true], [100.0, true], ["1.0e2", true], [["1.0e2"], false], [null, false]],
|
||||
["1", [null,true], [true, true], [1, true], [1.0, true], ["1", true], [["1"], false], [null, false]],
|
||||
["001", [null,true], [true, true], [1, true], [1.0, true], ["001", true], [["001"], false], [null, false]],
|
||||
["1e2", [null,true], [true, false], [100, true], [100.0, true], ["1e2", true], [["1e2"], false], [null, false]],
|
||||
["+1.0", [null,true], [true, true], [1, true], [1.0, true], ["+1.0", true], [["+1.0"], false], [null, false]],
|
||||
["+001.0", [null,true], [true, true], [1, true], [1.0, true], ["+001.0", true], [["+001.0"], false], [null, false]],
|
||||
["+1.0e2", [null,true], [true, false], [100, true], [100.0, true], ["+1.0e2", true], [["+1.0e2"], false], [null, false]],
|
||||
["+1", [null,true], [true, true], [1, true], [1.0, true], ["+1", true], [["+1"], false], [null, false]],
|
||||
["+001", [null,true], [true, true], [1, true], [1.0, true], ["+001", true], [["+001"], false], [null, false]],
|
||||
["+1e2", [null,true], [true, false], [100, true], [100.0, true], ["+1e2", true], [["+1e2"], false], [null, false]],
|
||||
[0, [null,true], [false,true], [0, true], [0.0, true], ["0", true], [[0], false], [$this->i("PT0S"), false]],
|
||||
["0", [null,true], [false,true], [0, true], [0.0, true], ["0", true], [["0"], false], [null, false]],
|
||||
["000", [null,true], [false,true], [0, true], [0.0, true], ["000", true], [["000"], false], [null, false]],
|
||||
[0.0, [null,true], [false,true], [0, true], [0.0, true], ["0", true], [[0.0], false], [$this->i("PT0S"), false]],
|
||||
["0.0", [null,true], [false,true], [0, true], [0.0, true], ["0.0", true], [["0.0"], false], [null, false]],
|
||||
["000.000", [null,true], [false,true], [0, true], [0.0, true], ["000.000", true], [["000.000"], false], [null, false]],
|
||||
["+0", [null,true], [false,true], [0, true], [0.0, true], ["+0", true], [["+0"], false], [null, false]],
|
||||
["+000", [null,true], [false,true], [0, true], [0.0, true], ["+000", true], [["+000"], false], [null, false]],
|
||||
["+0.0", [null,true], [false,true], [0, true], [0.0, true], ["+0.0", true], [["+0.0"], false], [null, false]],
|
||||
["+000.000", [null,true], [false,true], [0, true], [0.0, true], ["+000.000", true], [["+000.000"], false], [null, false]],
|
||||
[-1, [null,true], [true, false], [-1, true], [-1.0, true], ["-1", true], [[-1], false], [$this->i("PT1S"), false]],
|
||||
[-1.0, [null,true], [true, false], [-1, true], [-1.0, true], ["-1", true], [[-1.0], false], [$this->i("PT1S"), false]],
|
||||
["-1.0", [null,true], [true, false], [-1, true], [-1.0, true], ["-1.0", true], [["-1.0"], false], [null, false]],
|
||||
["-001.0", [null,true], [true, false], [-1, true], [-1.0, true], ["-001.0", true], [["-001.0"], false], [null, false]],
|
||||
["-1.0e2", [null,true], [true, false], [-100, true], [-100.0, true], ["-1.0e2", true], [["-1.0e2"], false], [null, false]],
|
||||
["-1", [null,true], [true, false], [-1, true], [-1.0, true], ["-1", true], [["-1"], false], [null, false]],
|
||||
["-001", [null,true], [true, false], [-1, true], [-1.0, true], ["-001", true], [["-001"], false], [null, false]],
|
||||
["-1e2", [null,true], [true, false], [-100, true], [-100.0, true], ["-1e2", true], [["-1e2"], false], [null, false]],
|
||||
[-0, [null,true], [false,true], [0, true], [0.0, true], ["0", true], [[-0], false], [$this->i("PT0S"), false]],
|
||||
["-0", [null,true], [false,true], [0, true], [-0.0, true], ["-0", true], [["-0"], false], [null, false]],
|
||||
["-000", [null,true], [false,true], [0, true], [-0.0, true], ["-000", true], [["-000"], false], [null, false]],
|
||||
[-0.0, [null,true], [false,true], [0, true], [-0.0, true], ["-0", true], [[-0.0], false], [$this->i("PT0S"), false]],
|
||||
["-0.0", [null,true], [false,true], [0, true], [-0.0, true], ["-0.0", true], [["-0.0"], false], [null, false]],
|
||||
["-000.000", [null,true], [false,true], [0, true], [-0.0, true], ["-000.000", true], [["-000.000"], false], [null, false]],
|
||||
[false, [null,true], [false,true], [0, false], [0.0, false], ["", false], [[false], false], [null, false]],
|
||||
[true, [null,true], [true, true], [1, false], [1.0, false], ["1", false], [[true], false], [null, false]],
|
||||
["on", [null,true], [true, true], [0, false], [0.0, false], ["on", true], [["on"], false], [null, false]],
|
||||
["off", [null,true], [false,true], [0, false], [0.0, false], ["off", true], [["off"], false], [null, false]],
|
||||
["yes", [null,true], [true, true], [0, false], [0.0, false], ["yes", true], [["yes"], false], [null, false]],
|
||||
["no", [null,true], [false,true], [0, false], [0.0, false], ["no", true], [["no"], false], [null, false]],
|
||||
["true", [null,true], [true, true], [0, false], [0.0, false], ["true", true], [["true"], false], [null, false]],
|
||||
["false", [null,true], [false,true], [0, false], [0.0, false], ["false", true], [["false"], false], [null, false]],
|
||||
[INF, [null,true], [true, false], [0, false], [INF, true], ["INF", false], [[INF], false], [null, false]],
|
||||
[-INF, [null,true], [true, false], [0, false], [-INF, true], ["-INF", false], [[-INF], false], [null, false]],
|
||||
[NAN, [null,true], [false,false], [0, false], [NAN, true], ["NAN", false], [[], false], [null, false]],
|
||||
[[], [null,true], [false,false], [0, false], [0.0, false], ["", false], [[], true], [null, false]],
|
||||
["some string", [null,true], [true, false], [0, false], [0.0, false], ["some string", true], [["some string"], false], [null, false]],
|
||||
[" ", [null,true], [true, false], [0, false], [0.0, false], [" ", true], [[" "], false], [null, false]],
|
||||
[new \StdClass, [null,true], [true, false], [0, false], [0.0, false], ["", false], [[new \StdClass], false], [null, false]],
|
||||
[new StrClass(""), [null,true], [false,true], [0, false], [0.0, false], ["", true], [[new StrClass("")], false], [null, false]],
|
||||
[new StrClass("1"), [null,true], [true, true], [1, true], [1.0, true], ["1", true], [[new StrClass("1")], false], [null, false]],
|
||||
[new StrClass("0"), [null,true], [false,true], [0, true], [0.0, true], ["0", true], [[new StrClass("0")], false], [null, false]],
|
||||
[new StrClass("-1"), [null,true], [true, false], [-1, true], [-1.0, true], ["-1", true], [[new StrClass("-1")], false], [null, false]],
|
||||
[new StrClass("Msg"), [null,true], [true, false], [0, false], [0.0, false], ["Msg", true], [[new StrClass("Msg")], false], [null, false]],
|
||||
[new StrClass(" "), [null,true], [true, false], [0, false], [0.0, false], [" ", true], [[new StrClass(" ")], false], [null, false]],
|
||||
[2.5, [null,true], [true, false], [2, false], [2.5, true], ["2.5", true], [[2.5], false], [$this->i("PT2S", 0.5), false]],
|
||||
[0.5, [null,true], [true, false], [0, false], [0.5, true], ["0.5", true], [[0.5], false], [$this->i("PT0S", 0.5), false]],
|
||||
["2.5", [null,true], [true, false], [2, false], [2.5, true], ["2.5", true], [["2.5"], false], [null, false]],
|
||||
["0.5", [null,true], [true, false], [0, false], [0.5, true], ["0.5", true], [["0.5"], false], [null, false]],
|
||||
[$this->d("2010-01-01T00:00:00", 0, 0), [null,true], [true, false], [1262304000, false], [1262304000.0, false], ["2010-01-01T00:00:00Z",true], [[$this->d("2010-01-01T00:00:00", 0, 0)],false], [null, false]],
|
||||
[$this->d("2010-01-01T00:00:00", 0, 1), [null,true], [true, false], [1262304000, false], [1262304000.0, false], ["2010-01-01T00:00:00Z",true], [[$this->d("2010-01-01T00:00:00", 0, 1)],false], [null, false]],
|
||||
[$this->d("2010-01-01T00:00:00", 1, 0), [null,true], [true, false], [1262322000, false], [1262322000.0, false], ["2010-01-01T05:00:00Z",true], [[$this->d("2010-01-01T00:00:00", 1, 0)],false], [null, false]],
|
||||
[$this->d("2010-01-01T00:00:00", 1, 1), [null,true], [true, false], [1262322000, false], [1262322000.0, false], ["2010-01-01T05:00:00Z",true], [[$this->d("2010-01-01T00:00:00", 1, 1)],false], [null, false]],
|
||||
[1e14, [null,true], [true, false], [pow(10, 14), true], [1e14, true], ["100000000000000", true], [[1e14], false], [$this->i("P1157407407DT9H46M40S"), false]],
|
||||
[1e-6, [null,true], [true, false], [0, false], [1e-6, true], ["0.000001", true], [[1e-6], false], [$this->i("PT0S", 1e-6), false]],
|
||||
[[1,2,3], [null,true], [true, false], [0, false], [0.0, false], ["", false], [[1,2,3], true], [null, false]],
|
||||
[['a'=>1,'b'=>2], [null,true], [true, false], [0, false], [0.0, false], ["", false], [['a'=>1,'b'=>2], true], [null, false]],
|
||||
[new Result([['a'=>1,'b'=>2]]), [null,true], [true, false], [0, false], [0.0, false], ["", false], [[['a'=>1,'b'=>2]], true], [null, false]],
|
||||
[$this->i("PT1H"), [null,true], [true, false], [60*60, false], [60.0*60.0, false], ["PT1H", true], [[$this->i("PT1H")], false], [$this->i("PT1H"), true]],
|
||||
[$this->i("P2DT1H"), [null,true], [true, false], [(48+1)*60*60, false], [1.0*(48+1)*60*60, false], ["P2DT1H", true], [[$this->i("P2DT1H")], false], [$this->i("P2DT1H"), true]],
|
||||
[$this->i("PT0H"), [null,true], [true, false], [0, false], [0.0, false], ["PT0S", true], [[$this->i("PT0H")], false], [$this->i("PT0H"), true]],
|
||||
[$dateDiff, [null,true], [true, false], [366*24*60*60, false], [1.0*366*24*60*60, false], ["P366D", true], [[$dateDiff], false], [$dateNorm, true]],
|
||||
["1 year, 2 days", [null,true], [true, false], [0, false], [0.0, false], ["1 year, 2 days", true], [["1 year, 2 days"], false], [$this->i("P1Y2D"), false]],
|
||||
["P1Y2D", [null,true], [true, false], [0, false], [0.0, false], ["P1Y2D", true], [["P1Y2D"], false], [$this->i("P1Y2D"), true]],
|
||||
] as $set) {
|
||||
// shift the input value off the set
|
||||
$input = array_shift($set);
|
||||
// shift a mixed-type passthrough test onto the set
|
||||
array_unshift($set, [$input, true]);
|
||||
// generate a set of tests for each target data type
|
||||
foreach ($set as $type => list($exp, $pass)) {
|
||||
// emit one test each for loose mode, strict mode, drop mode, and strict+drop mode
|
||||
foreach ([
|
||||
[false, false],
|
||||
[true, false],
|
||||
[false, true],
|
||||
[true, true],
|
||||
] as list($strict, $drop)) {
|
||||
yield [$input, $types[$type], $exp, $pass, $strict, $drop];
|
||||
}
|
||||
}
|
||||
}
|
||||
// DateTimeInterface tests
|
||||
$tests = [
|
||||
}
|
||||
|
||||
public function provideDateNormalizationValues() {
|
||||
$formats = [
|
||||
"microtime",
|
||||
"iso8601",
|
||||
"iso8601m",
|
||||
"http",
|
||||
"sql",
|
||||
"date",
|
||||
"time",
|
||||
"unix",
|
||||
"float",
|
||||
"!M j, Y (D)",
|
||||
null,
|
||||
];
|
||||
foreach([
|
||||
/* Input value microtime iso8601 iso8601m http sql date time unix float '!M j, Y (D)' *strtotime* (null) */
|
||||
[null, null, null, null, null, null, null, null, null, null, null, null, ],
|
||||
[INF, null, null, null, null, null, null, null, null, null, null, null, ],
|
||||
[NAN, null, null, null, null, null, null, null, null, null, null, null, ],
|
||||
[$this->d("2010-01-01T00:00:00", 0, 0), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), ],
|
||||
[$this->d("2010-01-01T00:00:00", 0, 1), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), $this->t(1262304000), ],
|
||||
[$this->d("2010-01-01T00:00:00", 1, 0), $this->t(1262322000), $this->t(1262322000), $this->t(1262322000), $this->t(1262322000), $this->t(1262322000), $this->t(1262322000), $this->t(1262322000), $this->t(1262322000), $this->t(1262322000), $this->t(1262322000), $this->t(1262322000), ],
|
||||
|
@ -474,60 +601,25 @@ class TestValueInfo extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
["1262304000.42", null, null, null, null, null, null, null, null, $this->t(1262304000.42), null, null, ],
|
||||
["Jan 1, 2010 (Fri)", null, null, null, null, null, null, null, null, null, $this->t(1262304000), null, ],
|
||||
["First day of Jan 2010 12AM", null, null, null, null, null, null, null, null, null, null, $this->t(1262304000), ],
|
||||
];
|
||||
$formats = [
|
||||
"microtime",
|
||||
"iso8601",
|
||||
"iso8601m",
|
||||
"http",
|
||||
"sql",
|
||||
"date",
|
||||
"time",
|
||||
"unix",
|
||||
"float",
|
||||
"!M j, Y (D)",
|
||||
null,
|
||||
];
|
||||
$exc = new ExceptionType("strictFailure", I::T_DATE);
|
||||
foreach ($formats as $index => $format) {
|
||||
foreach ($tests as $test) {
|
||||
$value = $test[0];
|
||||
$exp = $test[$index+1];
|
||||
$this->assertEquals($exp, I::normalize($value, I::T_DATE, $format), "Test failed for format ".var_export($format, true)." using value ".var_export($value, true));
|
||||
$this->assertEquals($exp, I::normalize($value, I::T_DATE | I::M_DROP, $format), "Drop test failed for format ".var_export($format, true)." using value ".var_export($value, true));
|
||||
// test for exception in case of errors
|
||||
$exp = $exp ?? $exc;
|
||||
try {
|
||||
$act = I::normalize($value, I::T_DATE | I::M_STRICT, $format);
|
||||
} catch (ExceptionType $e) {
|
||||
$act = $e;
|
||||
} finally {
|
||||
$this->assertEquals($exp, $act, "Error test failed for format ".var_export($format, true)." using value ".var_export($value, true));
|
||||
[[], null, null, null, null, null, null, null, null, null, null, null, ],
|
||||
[$this->i("P1Y2D"), null, null, null, null, null, null, null, null, null, null, null, ],
|
||||
["P1Y2D", null, null, null, null, null, null, null, null, null, null, null, ],
|
||||
] as $set) {
|
||||
// shift the input value off the set
|
||||
$input = array_shift($set);
|
||||
// generate a set of tests for each target date formats
|
||||
foreach ($set as $format => $exp) {
|
||||
// emit one test each for loose mode, strict mode, drop mode, and strict+drop mode
|
||||
foreach ([
|
||||
[false, false],
|
||||
[true, false],
|
||||
[false, true],
|
||||
[true, true],
|
||||
] as list($strict, $drop)) {
|
||||
yield [$input, $formats[$format], $exp, $strict, $drop];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Array-mode tests
|
||||
$tests = [
|
||||
[I::T_INT | I::M_DROP, new Result([1, 2, 2.2, 3]), [1,2,null,3] ],
|
||||
[I::T_INT, new Result([1, 2, 2.2, 3]), [1,2,2,3] ],
|
||||
[I::T_STRING | I::M_STRICT, "Bare string", ["Bare string"]],
|
||||
];
|
||||
foreach ($tests as $index => $test) {
|
||||
list($type, $value, $exp) = $test;
|
||||
$this->assertEquals($exp, I::normalize($value, $type | I::M_ARRAY, "iso8601"), "Failed test #$index");
|
||||
}
|
||||
// Date-to-string format tests
|
||||
$test = new \DateTimeImmutable("now", new \DateTimezone("UTC"));
|
||||
$exp = $test->format(I::DATE_FORMATS['iso8601'][1]);
|
||||
$this->assertSame($exp, I::normalize($test, I::T_STRING, null), "Failed test for null output date format");
|
||||
foreach (I::DATE_FORMATS as $name => $formats) {
|
||||
$exp = $test->format($formats[1]);
|
||||
$this->assertSame($exp, I::normalize($test, I::T_STRING, null, $name), "Failed test for output date format '$name'");
|
||||
}
|
||||
foreach (["U", "M j, Y (D)", "r", "c"] as $format) {
|
||||
$exp = $test->format($format);
|
||||
$this->assertSame($exp, I::normalize($test, I::T_STRING, null, $format), "Failed test for output date format '$format'");
|
||||
}
|
||||
}
|
||||
|
||||
protected function d($spec, $local, $immutable): \DateTimeInterface {
|
||||
|
@ -542,4 +634,10 @@ class TestValueInfo extends \JKingWeb\Arsse\Test\AbstractTest {
|
|||
protected function t(float $spec): \DateTimeImmutable {
|
||||
return \DateTimeImmutable::createFromFormat("U.u", sprintf("%F", $spec), new \DateTimeZone("UTC"));
|
||||
}
|
||||
|
||||
protected function i(string $spec, float $msec = 0.0): \DateInterval {
|
||||
$out = new \DateInterval($spec);
|
||||
$out->f = $msec;
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue