1
1
Fork 0
mirror of https://code.mensbeam.com/MensBeam/Arsse.git synced 2024-12-22 21:22:40 +00:00

Standardize date normalization to immutables

Also move date formats to the ValueInfo class

Standardizing on immutables avoids any possible ambiguity in the API of the resultant value, as well as any ambiguity as to whether a DateTime output instance is the same instance or a clone (they had been clones)
This commit is contained in:
J. King 2018-01-02 16:27:58 -05:00
parent 87faededc6
commit 89bfc23d32
4 changed files with 53 additions and 32 deletions

View file

@ -827,7 +827,7 @@ class Database {
$limit = Date::normalize("now");
if (Arsse::$conf->purgeFeeds) {
// if there is a retention period specified, compute it; otherwise feed are deleted immediatelty
$limit->sub(new \DateInterval(Arsse::$conf->purgeFeeds));
$limit = Date::sub(Arsse::$conf->purgeFeeds, $limit);
}
$out = (bool) $this->db->prepare("DELETE from arsse_feeds where orphaned <= ?", "datetime")->run($limit);
// commit changes and return

View file

@ -328,12 +328,12 @@ class Feed {
return [$new, $edited];
}
protected function computeNextFetch(): \DateTime {
protected function computeNextFetch(): \DateTimeImmutable {
$now = Date::normalize(time());
if (!$this->modified) {
$diff = $now->getTimestamp() - $this->lastModified->getTimestamp();
$offset = $this->normalizeDateDiff($diff);
$now->modify("+".$offset);
return $now->modify("+".$offset);
} else {
// the algorithm for updated feeds (returning 200 rather than 304) uses the same parameters as for 304,
// save that the last three intervals between item dates are computed, and if any two fall within
@ -347,20 +347,19 @@ class Feed {
$offsets[] = $this->normalizeDateDiff($diff);
}
if ($offsets[0]==$offsets[1] || $offsets[0]==$offsets[2]) {
$now->modify("+".$offsets[0]);
return $now->modify("+".$offsets[0]);
} elseif ($offsets[1]==$offsets[2]) {
$now->modify("+".$offsets[1]);
return $now->modify("+".$offsets[1]);
} else {
$now->modify("+ 1 hour");
return $now->modify("+ 1 hour");
}
} else {
$now->modify("+ 1 hour");
return $now->modify("+ 1 hour");
}
}
return $now;
}
public static function nextFetchOnError($errCount): \DateTime {
public static function nextFetchOnError($errCount): \DateTimeImmutable {
if ($errCount < 3) {
$offset = "5 minutes";
} elseif ($errCount < 15) {

View file

@ -19,7 +19,7 @@ class ValueInfo {
// strings
const EMPTY = 1 << 2;
const WHITE = 1 << 3;
//normalization types
// normalization types
const T_MIXED = 0; // pass through unchanged
const T_NULL = 1; // convert to null
const T_BOOL = 2; // convert to boolean
@ -28,11 +28,23 @@ class ValueInfo {
const T_DATE = 5; // convert to DateTimeInterface instance
const T_STRING = 6; // convert to string
const T_ARRAY = 7; // convert to array
//normalization modes
// 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
const M_STRICT = 1 << 30; // throw an exception if the type doesn't match
const M_ARRAY = 1 << 31; // the value should be a flat array of values of the specified type; indexed and associative are both acceptable
// symbolic date and time formats
const DATE_FORMATS = [ // in out
'iso8601' => ["!Y-m-d\TH:i:s", "Y-m-d\TH:i:s\Z" ], // NOTE: ISO 8601 dates require special input processing because of varying formats for timezone offsets
'iso8601m' => ["!Y-m-d\TH:i:s.u", "Y-m-d\TH:i:s.u\Z" ], // NOTE: ISO 8601 dates require special input processing because of varying formats for timezone offsets
'microtime' => ["U.u", "0.u00 U" ], // NOTE: the actual input format at the user level matches the output format; pre-processing is required for PHP not to fail
'http' => ["!D, d M Y H:i:s \G\M\T", "D, d M Y H:i:s \G\M\T"],
'sql' => ["!Y-m-d H:i:s", "Y-m-d H:i:s" ],
'date' => ["!Y-m-d", "Y-m-d" ],
'time' => ["!H:i:s", "H:i:s" ],
'unix' => ["U", "U" ],
'float' => ["U.u", "U.u" ],
];
public static function normalize($value, int $type, string $dateInFormat = null, $dateOutFormat = null) {
$allowNull = ($type & self::M_NULL);
@ -131,14 +143,14 @@ class ValueInfo {
if (is_string($value)) {
return $value;
}
$dateOutFormat = $dateOutFormat ?? "iso8601";
$dateOutFormat = isset(Date::FORMAT[$dateOutFormat]) ? Date::FORMAT[$dateOutFormat][1] : $dateOutFormat;
if ($value instanceof \DateTimeImmutable) {
return $value->setTimezone(new \DateTimeZone("UTC"))->format($dateOutFormat);
} elseif ($value instanceof \DateTime) {
$out = clone $value;
$out->setTimezone(new \DateTimeZone("UTC"));
return $out->format($dateOutFormat);
if ($value instanceof \DateTimeInterface) {
$dateOutFormat = $dateOutFormat ?? "iso8601";
$dateOutFormat = isset(self::DATE_FORMATS[$dateOutFormat]) ? self::DATE_FORMATS[$dateOutFormat][1] : $dateOutFormat;
if ($value instanceof \DateTimeImmutable) {
return $value->setTimezone(new \DateTimeZone("UTC"))->format($dateOutFormat);
} elseif ($value instanceof \DateTime) {
return \DateTimeImmutable::createFromMutable($value)->setTimezone(new \DateTimeZone("UTC"))->format($dateOutFormat);
}
} elseif (is_float($value) && is_finite($value)) {
$out = (string) $value;
if (!strpos($out, "E")) {
@ -168,13 +180,11 @@ class ValueInfo {
if ($value instanceof \DateTimeImmutable) {
return $value->setTimezone(new \DateTimeZone("UTC"));
} elseif ($value instanceof \DateTime) {
$out = clone $value;
$out->setTimezone(new \DateTimeZone("UTC"));
return $out;
return \DateTimeImmutable::createFromMutable($value)->setTimezone(new \DateTimeZone("UTC"));
} elseif (is_int($value)) {
return \DateTime::createFromFormat("U", (string) $value, new \DateTimeZone("UTC"));
return \DateTimeImmutable::createFromFormat("U", (string) $value, new \DateTimeZone("UTC"));
} elseif (is_float($value)) {
return \DateTime::createFromFormat("U.u", sprintf("%F", $value), new \DateTimeZone("UTC"));
return \DateTimeImmutable::createFromFormat("U.u", sprintf("%F", $value), new \DateTimeZone("UTC"));
} elseif (is_string($value)) {
try {
if (!is_null($dateInFormat)) {
@ -187,28 +197,28 @@ class ValueInfo {
throw new \Exception;
}
}
$f = isset(Date::FORMAT[$dateInFormat]) ? Date::FORMAT[$dateInFormat][0] : $dateInFormat;
$f = isset(self::DATE_FORMATS[$dateInFormat]) ? self::DATE_FORMATS[$dateInFormat][0] : $dateInFormat;
if ($dateInFormat=="iso8601" || $dateInFormat=="iso8601m") {
// DateTime::createFromFormat() doesn't provide one catch-all for ISO 8601 timezone specifiers, so we try all of them till one works
// DateTimeImmutable::createFromFormat() doesn't provide one catch-all for ISO 8601 timezone specifiers, so we try all of them till one works
if ($dateInFormat=="iso8601m") {
$f2 = Date::FORMAT["iso8601"][0];
$f2 = self::DATE_FORMATS["iso8601"][0];
$zones = [$f."", $f."\Z", $f."P", $f."O", $f2."", $f2."\Z", $f2."P", $f2."O"];
} else {
$zones = [$f."", $f."\Z", $f."P", $f."O"];
}
do {
$ftz = array_shift($zones);
$out = \DateTime::createFromFormat($ftz, $value, new \DateTimeZone("UTC"));
$out = \DateTimeImmutable::createFromFormat($ftz, $value, new \DateTimeZone("UTC"));
} while (!$out && $zones);
} else {
$out = \DateTime::createFromFormat($f, $value, new \DateTimeZone("UTC"));
$out = \DateTimeImmutable::createFromFormat($f, $value, new \DateTimeZone("UTC"));
}
if (!$out) {
throw new \Exception;
}
return $out;
} else {
return new \DateTime($value, new \DateTimeZone("UTC"));
return new \DateTimeImmutable($value, new \DateTimeZone("UTC"));
}
} catch (\Exception $e) {
if ($strict && !$drop) {

View file

@ -506,6 +506,18 @@ class TestValueInfo extends \JKingWeb\Arsse\Test\AbstractTest {
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 {
@ -517,7 +529,7 @@ class TestValueInfo extends \JKingWeb\Arsse\Test\AbstractTest {
}
}
protected function t(float $spec): \DateTime {
return \DateTime::createFromFormat("U.u", sprintf("%F", $spec), new \DateTimeZone("UTC"));
protected function t(float $spec): \DateTimeImmutable {
return \DateTimeImmutable::createFromFormat("U.u", sprintf("%F", $spec), new \DateTimeZone("UTC"));
}
}