From 806bb97e60aa4d890f7a0259e68ae615296a5df6 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Wed, 24 Dec 2025 16:33:28 +0100 Subject: [PATCH] query modified_after in UTC (#5743) --- phpstan.neon | 5 ----- src/API/TimesheetController.php | 13 ++++++++----- src/Repository/Query/TimesheetQuery.php | 8 +++----- tests/Repository/Query/TimesheetQueryTest.php | 2 +- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index cc2e74f3..d0fb259a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -124,11 +124,6 @@ parameters: count: 1 path: src/API/TagController.php - - - message: "#^Parameter \\#1 \\$datetime of method App\\\\Timesheet\\\\DateTimeFactory\\:\\:createDateTime\\(\\) expects string, mixed given\\.$#" - count: 3 - path: src/API/TimesheetController.php - - message: "#^Parameter \\#2 \\$statistic of class App\\\\Event\\\\ActivityStatisticEvent constructor expects App\\\\Model\\\\ActivityStatistic, App\\\\Model\\\\ActivityStatistic\\|null given\\.$#" count: 1 diff --git a/src/API/TimesheetController.php b/src/API/TimesheetController.php index 7676a715..514aeb96 100644 --- a/src/API/TimesheetController.php +++ b/src/API/TimesheetController.php @@ -93,7 +93,7 @@ final class TimesheetController extends BaseApiController #[Rest\QueryParam(name: 'billable', requirements: '0|1', strict: true, nullable: true, description: 'Filter for non-/billable records. Allowed values: 0=non-billable, 1=billable (default: all)')] #[Rest\QueryParam(name: 'full', requirements: '0|1|true|false', strict: true, nullable: true, description: 'Allows to fetch full objects including subresources. Allowed values: 0|1|false|true (default: false)')] #[Rest\QueryParam(name: 'term', description: 'Free search term', nullable: true)] - #[Rest\QueryParam(name: 'modified_after', requirements: [new Constraints\DateTime(format: 'Y-m-d\TH:i:s')], strict: true, nullable: true, description: 'Only records changed after this date will be included (format: HTML5 datetime-local, e.g. YYYY-MM-DDThh:mm:ss)')] + #[Rest\QueryParam(name: 'modified_after', requirements: [new Constraints\DateTime(format: 'Y-m-d\TH:i:s')], strict: true, nullable: true, description: 'Only records changed after this date will be included. You need to pass in a UTC date-time, as this field is stored in UTC (format: HTML5 datetime-local, e.g. YYYY-MM-DDThh:mm:ss)')] public function cgetAction(ParamFetcherInterface $paramFetcher, CustomerRepository $customerRepository, ProjectRepository $projectRepository, ActivityRepository $activityRepository, UserRepository $userRepository): Response { $query = new TimesheetQuery(false); @@ -228,8 +228,9 @@ final class TimesheetController extends BaseApiController $query->setSearchTerm(new SearchTerm($term)); } - if (!empty($modifiedAfter = $paramFetcher->get('modified_after'))) { - $query->setModifiedAfter($factory->createDateTime($modifiedAfter)); + $modifiedAfter = $paramFetcher->get('modified_after'); + if (\is_string($modifiedAfter)) { + $query->setModifiedAfter(new \DateTimeImmutable($modifiedAfter, new \DateTimeZone('UTC'))); } $data = $this->repository->getPagerfantaForQuery($query); @@ -392,7 +393,8 @@ final class TimesheetController extends BaseApiController $limit = (int) $reqLimit; } - if (null !== ($reqBegin = $paramFetcher->get('begin'))) { + $reqBegin = $paramFetcher->get('begin'); + if (\is_string($reqBegin)) { $begin = $this->getDateTimeFactory($user)->createDateTime($reqBegin); } @@ -471,7 +473,8 @@ final class TimesheetController extends BaseApiController $factory = $this->getDateTimeFactory(); $begin = $factory->createDateTime(); - if (null !== ($beginTmp = $paramFetcher->get('begin'))) { + $beginTmp = $paramFetcher->get('begin'); + if (\is_string($beginTmp)) { $begin = $factory->createDateTime($beginTmp); } diff --git a/src/Repository/Query/TimesheetQuery.php b/src/Repository/Query/TimesheetQuery.php index b7885cd7..b138e045 100644 --- a/src/Repository/Query/TimesheetQuery.php +++ b/src/Repository/Query/TimesheetQuery.php @@ -36,7 +36,7 @@ class TimesheetQuery extends ActivityQuery implements BillableInterface, DateRan private int $state = self::STATE_ALL; private int $exported = self::STATE_ALL; private ?int $maxResults = null; - private ?\DateTime $modifiedAfter = null; + private ?\DateTimeInterface $modifiedAfter = null; /** * @var array */ @@ -244,15 +244,13 @@ class TimesheetQuery extends ActivityQuery implements BillableInterface, DateRan $this->tags[$tag->getId()] = $tag; } - public function getModifiedAfter(): ?\DateTime + public function getModifiedAfter(): ?\DateTimeInterface { return $this->modifiedAfter; } - public function setModifiedAfter(\DateTime $modifiedAfter): TimesheetQuery + public function setModifiedAfter(\DateTimeInterface $modifiedAfter): void { $this->modifiedAfter = $modifiedAfter; - - return $this; } } diff --git a/tests/Repository/Query/TimesheetQueryTest.php b/tests/Repository/Query/TimesheetQueryTest.php index f64983fa..2563855d 100644 --- a/tests/Repository/Query/TimesheetQueryTest.php +++ b/tests/Repository/Query/TimesheetQueryTest.php @@ -169,7 +169,7 @@ class TimesheetQueryTest extends BaseQueryTest self::assertNull($sut->getModifiedAfter()); $date = new \DateTime('-3 hours'); - self::assertInstanceOf(TimesheetQuery::class, $sut->setModifiedAfter($date)); + $sut->setModifiedAfter($date); self::assertNotNull($sut->getModifiedAfter()); // just here to fix a PHPStan issue self::assertSame($date, $sut->getModifiedAfter()); }