Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use UTC internally #1451

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/controllers/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public function home(): void {

$lastUpdate = $this->itemsDao->lastUpdate();
$result = [
'lastUpdate' => $lastUpdate !== null ? $lastUpdate->format(\DateTime::ATOM) : null,
'lastUpdate' => $lastUpdate !== null ? $lastUpdate->format(\DateTimeImmutable::ATOM) : null,
'hasMore' => $items['hasMore'],
'entries' => $items['entries'],
'all' => $statsAll,
Expand Down
14 changes: 6 additions & 8 deletions src/controllers/Items/Sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,26 +49,24 @@ public function sync(): void {
$this->view->jsonError(['sync' => 'missing since argument']);
}

$since = new \DateTime($params['since']);
$since->setTimeZone(new \DateTimeZone(date_default_timezone_get()));
// The client should include a timezone offset but let’s default to UTC in case it does not.
$since = new \DateTimeImmutable($params['since'], new \DateTimeZone('UTC'));

$lastUpdate = $this->itemsDao->lastUpdate();

$sync = [
'lastUpdate' => $lastUpdate !== null ? $lastUpdate->format(\DateTime::ATOM) : null,
'lastUpdate' => $lastUpdate !== null ? $lastUpdate->format(\DateTimeImmutable::ATOM) : null,
];

if (array_key_exists('itemsSinceId', $params)) {
$sinceId = (int) $params['itemsSinceId'];
if ($sinceId >= 0) {
$notBefore = isset($params['itemsNotBefore']) ? new \DateTime($params['itemsNotBefore']) : null;
$notBefore = isset($params['itemsNotBefore']) ? new \DateTimeImmutable($params['itemsNotBefore']) : null;
if ($sinceId === 0 || !$notBefore) {
$sinceId = $this->itemsDao->lowestIdOfInterest() - 1;
// only send 1 day worth of items
$notBefore = new \DateTime();
$notBefore->setTimeZone(new \DateTimeZone(date_default_timezone_get()));
$notBefore->sub(new \DateInterval('P1D'));
$notBefore->setTimeZone(new \DateTimeZone(date_default_timezone_get()));
$notBefore = new \DateTimeImmutable();
$notBefore = $notBefore->sub(new \DateInterval('P1D'));
}

$itemsHowMany = $this->configuration->itemsPerpage;
Expand Down
6 changes: 3 additions & 3 deletions src/controllers/Rss.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function rss(): void {
$newItem->setTitle($this->sanitizeTitle($item['title'] . ' (' . $lastSourceName . ')'));
@$newItem->setLink($item['link']);
@$newItem->setId($item['link']);
$newItem->setDate($item['datetime']);
$newItem->setDate(\DateTime::createFromImmutable($item['datetime']));
$newItem->setDescription(str_replace('"', '"', $item['content']));

// add tags in category node
Expand All @@ -96,9 +96,9 @@ public function rss(): void {
}

if ($newestEntryDate === null) {
$newestEntryDate = new \DateTime();
$newestEntryDate = new \DateTimeImmutable();
}
$this->feedWriter->setDate($newestEntryDate);
$this->feedWriter->setDate(\DateTime::createFromImmutable($newestEntryDate));

$this->feedWriter->printFeed();
}
Expand Down
12 changes: 7 additions & 5 deletions src/daos/ItemOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace daos;

use DateTime;
use DateTimeImmutable;

/**
* Object holding parameters for querying items.
Expand All @@ -24,13 +24,13 @@ final class ItemOptions {
public ?int $pageSize = null;

/** @readonly */
public ?DateTime $fromDatetime = null;
public ?DateTimeImmutable $fromDatetime = null;

/** @readonly */
public ?int $fromId = null;

/** @readonly */
public ?DateTime $updatedSince = null;
public ?DateTimeImmutable $updatedSince = null;

/** @readonly */
public ?string $tag = null;
Expand Down Expand Up @@ -71,15 +71,17 @@ public static function fromUser(array $data): self {
}

if (isset($data['fromDatetime']) && is_string($data['fromDatetime']) && strlen($data['fromDatetime']) > 0) {
$options->fromDatetime = new \DateTime($data['fromDatetime']);
// The client should include a timezone offset but let’s default to UTC in case it does not.
$options->fromDatetime = new \DateTimeImmutable($data['fromDatetime'], new \DateTimeZone('UTC'));
}

if (isset($data['fromId']) && is_numeric($data['fromId'])) {
$options->fromId = (int) $data['fromId'];
}

if (isset($data['updatedsince']) && is_string($data['updatedsince']) && strlen($data['updatedsince']) > 0) {
$options->updatedSince = new \DateTime($data['updatedsince']);
// The client should include a timezone offset but let’s default to UTC in case it does not.
$options->updatedSince = new \DateTimeImmutable($data['updatedsince'], new \DateTimeZone('UTC'));
}

if (isset($data['tag']) && is_string($data['tag']) && strlen($tag = trim($data['tag'])) > 0) {
Expand Down
9 changes: 4 additions & 5 deletions src/daos/Items.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace daos;

use DateTime;
use DateTimeImmutable;
use helpers\Authentication;

Expand Down Expand Up @@ -60,7 +59,7 @@ public function updateLastSeen(array $itemIds): void {
$this->backend->updateLastSeen($itemIds);
}

public function cleanup(?DateTime $minDate): void {
public function cleanup(?DateTimeImmutable $minDate): void {
$this->backend->cleanup($minDate);
}

Expand All @@ -69,7 +68,7 @@ public function cleanup(?DateTime $minDate): void {
*
* @param ItemOptions $options search, offset and filter params
*
* @return array<array{id: int, datetime: DateTime, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTime, author: string, sourcetitle: string, tags: string[]}> items as array
* @return array<array{id: int, datetime: DateTimeImmutable, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTimeImmutable, author: string, sourcetitle: string, tags: string[]}> items as array
*/
public function get(ItemOptions $options): array {
$items = $this->backend->get($options);
Expand Down Expand Up @@ -107,7 +106,7 @@ public function hasMore(): bool {
return $this->backend->hasMore();
}

public function sync(int $sinceId, DateTime $notBefore, DateTime $since, int $howMany): array {
public function sync(int $sinceId, DateTimeImmutable $notBefore, DateTimeImmutable $since, int $howMany): array {
return $this->backend->sync($sinceId, $notBefore, $since, $howMany);
}

Expand Down Expand Up @@ -147,7 +146,7 @@ public function lastUpdate(): ?DateTimeImmutable {
return $this->backend->lastUpdate();
}

public function statuses(DateTime $since): array {
public function statuses(DateTimeImmutable $since): array {
return $this->backend->statuses($since);
}

Expand Down
19 changes: 9 additions & 10 deletions src/daos/ItemsInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace daos;

use DateTime;
use DateTimeImmutable;
use helpers\HtmlString;

Expand Down Expand Up @@ -73,16 +72,16 @@ public function updateLastSeen(array $itemIds): void;
/**
* cleanup orphaned and old items
*
* @param ?DateTime $date date to delete all items older than this value
* @param ?DateTimeImmutable $date date to delete all items older than this value
*/
public function cleanup(?DateTime $date): void;
public function cleanup(?DateTimeImmutable $date): void;

/**
* returns items
*
* @param ItemOptions $options search, offset and filter params
*
* @return array<array{id: int, datetime: DateTime, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTime, author: string, sourcetitle: string, tags: string[]}> items as array
* @return array<array{id: int, datetime: DateTimeImmutable, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTimeImmutable, author: string, sourcetitle: string, tags: string[]}> items as array
*/
public function get(ItemOptions $options): array;

Expand All @@ -96,12 +95,12 @@ public function hasMore(): bool;
* Obtain new or changed items in the database for synchronization with clients.
*
* @param int $sinceId id of last seen item
* @param DateTime $notBefore cut off time stamp
* @param DateTime $since timestamp of last seen item
* @param DateTimeImmutable $notBefore cut off time stamp
* @param DateTimeImmutable $since timestamp of last seen item
*
* @return array<array{id: int, datetime: DateTime, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTime, author: string, sourcetitle: string, tags: string[]}> of items
* @return array<array{id: int, datetime: DateTimeImmutable, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTimeImmutable, author: string, sourcetitle: string, tags: string[]}> of items
*/
public function sync(int $sinceId, DateTime $notBefore, DateTime $since, int $howMany): array;
public function sync(int $sinceId, DateTimeImmutable $notBefore, DateTimeImmutable $since, int $howMany): array;

/**
* Lowest id of interest
Expand Down Expand Up @@ -171,11 +170,11 @@ public function lastUpdate(): ?DateTimeImmutable;
/**
* returns the statuses of items last update
*
* @param DateTime $since minimal date of returned items
* @param DateTimeImmutable $since minimal date of returned items
*
* @return array<array{id: int, unread: bool, starred: bool}> of unread, starred, etc. status of specified items
*/
public function statuses(DateTime $since): array;
public function statuses(DateTimeImmutable $since): array;

/**
* bulk update of item status
Expand Down
7 changes: 2 additions & 5 deletions src/daos/StatementsInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,11 @@ public static function rowTouch(string $column): string;
public static function bool(bool $bool): string;

/**
* Convert a date into a representation suitable for comparison by
* the database engine.
*
* @param \DateTime $date datetime
* Convert a date into a representation suitable for storage or comparison.
*
* @return string representation of datetime
*/
public static function datetime(\DateTime $date): string;
public static function datetime(\DateTimeImmutable $date): string;

/**
* Ensure row values have the appropriate PHP type. This assumes we are
Expand Down
36 changes: 19 additions & 17 deletions src/daos/mysql/Items.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use daos\DatabaseInterface;
use daos\ItemOptions;
use DateTime;
use DateTimeImmutable;
use helpers\Configuration;
use helpers\HtmlString;
Expand Down Expand Up @@ -136,7 +135,7 @@ public function add(array $values): void {
:author
)',
[
':datetime' => $values['datetime']->format('Y-m-d H:i:s'),
':datetime' => static::$stmt::datetime($values['datetime']),
':title' => $values['title']->getRaw(),
':content' => $values['content']->getRaw(),
':thumbnail' => $values['thumbnail'],
Expand Down Expand Up @@ -206,9 +205,9 @@ public function updateLastSeen(array $itemIds): void {
/**
* cleanup orphaned and old items
*
* @param ?DateTime $date date to delete all items older than this value
* @param ?DateTimeImmutable $date date to delete all items older than this value
*/
public function cleanup(?DateTime $date): void {
public function cleanup(?DateTimeImmutable $date): void {
$this->database->exec('DELETE FROM ' . $this->configuration->dbPrefix . 'items
WHERE source NOT IN (
SELECT id FROM ' . $this->configuration->dbPrefix . 'sources)');
Expand All @@ -226,7 +225,7 @@ public function cleanup(?DateTime $date): void {
*
* @param ItemOptions $options search, offset and filter params
*
* @return array<array{id: int, datetime: DateTime, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTime, author: string, sourcetitle: string, tags: string[]}> items as array
* @return array<array{id: int, datetime: DateTimeImmutable, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTimeImmutable, author: string, sourcetitle: string, tags: string[]}> items as array
*/
public function get(ItemOptions $options): array {
$params = [];
Expand Down Expand Up @@ -375,12 +374,12 @@ public function hasMore(): bool {
* Obtain new or changed items in the database for synchronization with clients.
*
* @param int $sinceId id of last seen item
* @param DateTime $notBefore cut off time stamp
* @param DateTime $since timestamp of last seen item
* @param DateTimeImmutable $notBefore cut off time stamp
* @param DateTimeImmutable $since timestamp of last seen item
*
* @return array<array{id: int, datetime: DateTime, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTime, author: string, sourcetitle: string, tags: string[]}> of items
* @return array<array{id: int, datetime: DateTimeImmutable, title: string, content: string, unread: bool, starred: bool, source: int, thumbnail: string, icon: string, uid: string, link: string, updatetime: DateTimeImmutable, author: string, sourcetitle: string, tags: string[]}> of items
*/
public function sync(int $sinceId, DateTime $notBefore, DateTime $since, int $howMany): array {
public function sync(int $sinceId, DateTimeImmutable $notBefore, DateTimeImmutable $since, int $howMany): array {
$query = 'SELECT
items.id, datetime, items.title AS title, content, unread, starred, source, thumbnail, icon, uid, link, updatetime, author, sources.title as sourcetitle, sources.tags as tags
FROM ' . $this->configuration->dbPrefix . 'items AS items, ' . $this->configuration->dbPrefix . 'sources AS sources
Expand All @@ -396,8 +395,8 @@ public function sync(int $sinceId, DateTime $notBefore, DateTime $since, int $ho
$params = [
'sinceId' => [$sinceId, \PDO::PARAM_INT],
'howMany' => [$howMany, \PDO::PARAM_INT],
'notBefore' => [$notBefore->format('Y-m-d H:i:s'), \PDO::PARAM_STR],
'since' => [$since->format('Y-m-d H:i:s'), \PDO::PARAM_STR],
'notBefore' => [static::$stmt::datetime($notBefore), \PDO::PARAM_STR],
'since' => [static::$stmt::datetime($since), \PDO::PARAM_STR],
];

return static::$stmt::ensureRowTypes($this->database->exec($query, $params), [
Expand Down Expand Up @@ -569,22 +568,24 @@ public function lastUpdate(): ?DateTimeImmutable {
FROM ' . $this->configuration->dbPrefix . 'items;');
$lastUpdate = $res[0]['last_update_time'];

return $lastUpdate !== null ? new DateTimeImmutable($lastUpdate) : null;
// MySQL and SQLite do not support timezones, load it as UTC.
// PostgreSQL will include the timezone offset as the part of the returned value and it will take precedence.
return $lastUpdate !== null ? new DateTimeImmutable($lastUpdate, new \DateTimeZone('UTC')) : null;
}

/**
* returns the statuses of items last update
*
* @param DateTime $since minimal date of returned items
* @param DateTimeImmutable $since minimal date of returned items
*
* @return array<array{id: int, unread: bool, starred: bool}> of unread, starred, etc. status of specified items
*/
public function statuses(DateTime $since): array {
public function statuses(DateTimeImmutable $since): array {
$res = $this->database->exec(
'SELECT id, unread, starred
FROM ' . $this->configuration->dbPrefix . 'items
WHERE ' . $this->configuration->dbPrefix . 'items.updatetime > :since;',
[':since' => [$since->format('Y-m-d H:i:s'), \PDO::PARAM_STR]]
[':since' => [static::$stmt::datetime($since), \PDO::PARAM_STR]]
);
$res = static::$stmt::ensureRowTypes($res, [
'id' => DatabaseInterface::PARAM_INT,
Expand Down Expand Up @@ -627,7 +628,8 @@ public function bulkStatusUpdate(array $statuses): void {

// sanitize update time
if (array_key_exists('datetime', $status)) {
$updateDate = new \DateTime($status['datetime']);
// The client should include a timezone offset but let’s default to UTC in case it does not.
$updateDate = new \DateTimeImmutable($status['datetime'], new \DateTimeZone('UTC'));
} else {
$updateDate = null;
}
Expand All @@ -649,7 +651,7 @@ public function bulkStatusUpdate(array $statuses): void {
// create new status update
$sql[$id] = [
'updates' => [$sk => $statusUpdate['sql']],
'datetime' => $updateDate->format('Y-m-d H:i:s'),
'datetime' => static::$stmt::datetime($updateDate),
];
}
}
Expand Down
17 changes: 9 additions & 8 deletions src/daos/mysql/Statements.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,16 @@ public static function bool(bool $bool): string {
}

/**
* Convert a date into a representation suitable for comparison by
* the database engine.
*
* @param \DateTime $date datetime
* Convert a date into a representation suitable for storage or comparison.
*
* @return string representation of datetime
*/
public static function datetime(\DateTime $date): string {
// mysql supports ISO8601 datetime comparisons
return $date->format(\DateTime::ATOM);
public static function datetime(\DateTimeImmutable $date): string {
// SQLite does not support timezones so we store all dates in UTC.
// MySQL supports them for comparison but not for storage.
$utcDate = $date->setTimeZone(new \DateTimeZone('UTC'));

return $utcDate->format('Y-m-d H:i:s');
}

/**
Expand Down Expand Up @@ -179,7 +179,8 @@ public static function ensureRowTypes(array $rows, array $expectedRowTypes): arr
}
break;
case DatabaseInterface::PARAM_DATETIME:
$value = new \DateTime($row[$columnIndex]);
// MySQL and SQLite do not support timezones, load it as UTC.
$value = new \DateTimeImmutable($row[$columnIndex], new \DateTimeZone('UTC'));
break;
default:
$value = null;
Expand Down
Loading