From 3b9b19ed1717d95f17c23f49940e419e8f8003a5 Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Mon, 8 Dec 2025 00:11:56 +0100 Subject: [PATCH 1/2] Enhancement: Apply user's timezone to meta fields Signed-off-by: Kostiantyn Miakshyn --- lib/Db/Row2Mapper.php | 16 +++++-- lib/Helper/TimezoneHelper.php | 47 ++++++++++++++++++++ tests/unit/Db/Row2MapperTestDependencies.php | 4 +- 3 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 lib/Helper/TimezoneHelper.php diff --git a/lib/Db/Row2Mapper.php b/lib/Db/Row2Mapper.php index 931098a2c0..71642e085d 100644 --- a/lib/Db/Row2Mapper.php +++ b/lib/Db/Row2Mapper.php @@ -13,6 +13,7 @@ use OCA\Tables\Errors\InternalError; use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Helper\ColumnsHelper; +use OCA\Tables\Helper\TimezoneHelper; use OCA\Tables\Helper\UserHelper; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; @@ -39,7 +40,16 @@ class Row2Mapper { private ColumnsHelper $columnsHelper; - public function __construct(?string $userId, IDBConnection $db, LoggerInterface $logger, UserHelper $userHelper, RowSleeveMapper $rowSleeveMapper, ColumnsHelper $columnsHelper, ColumnMapper $columnMapper) { + public function __construct( + ?string $userId, + IDBConnection $db, + LoggerInterface $logger, + UserHelper $userHelper, + RowSleeveMapper $rowSleeveMapper, + ColumnsHelper $columnsHelper, + ColumnMapper $columnMapper, + private TimezoneHelper $timezoneHelper, + ) { $this->rowSleeveMapper = $rowSleeveMapper; $this->userId = $userId; $this->db = $db; @@ -631,9 +641,9 @@ private function parseEntities(IResult $result, array $sleeves): array { $rows[$sleeve->getId()] = new Row2(); $rows[$sleeve->getId()]->setId($sleeve->getId()); $rows[$sleeve->getId()]->setCreatedBy($sleeve->getCreatedBy()); - $rows[$sleeve->getId()]->setCreatedAt($sleeve->getCreatedAt()); + $rows[$sleeve->getId()]->setCreatedAt($this->timezoneHelper->applyUserTimezone($sleeve->getCreatedAt())); $rows[$sleeve->getId()]->setLastEditBy($sleeve->getLastEditBy()); - $rows[$sleeve->getId()]->setLastEditAt($sleeve->getLastEditAt()); + $rows[$sleeve->getId()]->setLastEditAt($this->timezoneHelper->applyUserTimezone($sleeve->getLastEditAt())); $rows[$sleeve->getId()]->setTableId($sleeve->getTableId()); } diff --git a/lib/Helper/TimezoneHelper.php b/lib/Helper/TimezoneHelper.php new file mode 100644 index 0000000000..2b588b3fcd --- /dev/null +++ b/lib/Helper/TimezoneHelper.php @@ -0,0 +1,47 @@ +getUserTimezone(); + + $dateTime = new \DateTimeImmutable($date, new DateTimeZone('UTC')); + + return $dateTime + ->setTimezone(new DateTimeZone($userTimezone)) + ->format('Y-m-d H:i:s'); + } + + public function getUserTimezone(): string { + if ($this->timezone) { + return $this->timezone; + } + + $userId = $this->userSession->getUser()?->getUID(); + $defaultTimeZone = $this->config->getSystemValueString('default_timezone', 'UTC'); + + if ($userId) { + $this->timezone = $this->config->getUserValue($userId, 'core', 'timezone', $defaultTimeZone); + } + + return $this->timezone ?? $defaultTimeZone; + } +} diff --git a/tests/unit/Db/Row2MapperTestDependencies.php b/tests/unit/Db/Row2MapperTestDependencies.php index 297eaf1674..e7e9aba811 100644 --- a/tests/unit/Db/Row2MapperTestDependencies.php +++ b/tests/unit/Db/Row2MapperTestDependencies.php @@ -15,6 +15,7 @@ use OCA\Tables\Db\RowSleeveMapper; use OCA\Tables\Helper\CircleHelper; use OCA\Tables\Helper\ColumnsHelper; +use OCA\Tables\Helper\TimezoneHelper; use OCA\Tables\Helper\UserHelper; use OCP\AppFramework\Db\DoesNotExistException; use PHPUnit\Framework\MockObject\MockObject; @@ -65,7 +66,8 @@ protected function setupDependencies(): void { $this->userHelper, $this->rowSleeveMapper, $this->columnsHelper, - $this->columnMapper + $this->columnMapper, + $this->createMock(TimezoneHelper::class) ); if (!self::$testDataInitialized) { From 098edb3e66dd537739829908ff455e644e85127f Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Thu, 18 Dec 2025 17:13:39 +0100 Subject: [PATCH 2/2] Enhancement: Apply user's timezone to meta fields (alternative approach) Signed-off-by: Kostiantyn Miakshyn --- lib/Db/Row2Mapper.php | 16 ++----- lib/Helper/TimezoneHelper.php | 47 ------------------- .../main/partials/ColumnInfoPopover.vue | 4 +- .../ncTable/mixins/columnsTypes/datetime.js | 2 +- .../mixins/columnsTypes/datetimeDate.js | 2 +- .../mixins/columnsTypes/datetimeTime.js | 2 +- .../ncTable/partials/TableCellDateTime.vue | 2 +- .../partials/rowTypePartials/DatetimeForm.vue | 2 +- .../rowTypePartials/DatetimeTimeForm.vue | 2 +- tests/unit/Db/Row2MapperTestDependencies.php | 4 +- 10 files changed, 13 insertions(+), 70 deletions(-) delete mode 100644 lib/Helper/TimezoneHelper.php diff --git a/lib/Db/Row2Mapper.php b/lib/Db/Row2Mapper.php index 71642e085d..931098a2c0 100644 --- a/lib/Db/Row2Mapper.php +++ b/lib/Db/Row2Mapper.php @@ -13,7 +13,6 @@ use OCA\Tables\Errors\InternalError; use OCA\Tables\Errors\NotFoundError; use OCA\Tables\Helper\ColumnsHelper; -use OCA\Tables\Helper\TimezoneHelper; use OCA\Tables\Helper\UserHelper; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; @@ -40,16 +39,7 @@ class Row2Mapper { private ColumnsHelper $columnsHelper; - public function __construct( - ?string $userId, - IDBConnection $db, - LoggerInterface $logger, - UserHelper $userHelper, - RowSleeveMapper $rowSleeveMapper, - ColumnsHelper $columnsHelper, - ColumnMapper $columnMapper, - private TimezoneHelper $timezoneHelper, - ) { + public function __construct(?string $userId, IDBConnection $db, LoggerInterface $logger, UserHelper $userHelper, RowSleeveMapper $rowSleeveMapper, ColumnsHelper $columnsHelper, ColumnMapper $columnMapper) { $this->rowSleeveMapper = $rowSleeveMapper; $this->userId = $userId; $this->db = $db; @@ -641,9 +631,9 @@ private function parseEntities(IResult $result, array $sleeves): array { $rows[$sleeve->getId()] = new Row2(); $rows[$sleeve->getId()]->setId($sleeve->getId()); $rows[$sleeve->getId()]->setCreatedBy($sleeve->getCreatedBy()); - $rows[$sleeve->getId()]->setCreatedAt($this->timezoneHelper->applyUserTimezone($sleeve->getCreatedAt())); + $rows[$sleeve->getId()]->setCreatedAt($sleeve->getCreatedAt()); $rows[$sleeve->getId()]->setLastEditBy($sleeve->getLastEditBy()); - $rows[$sleeve->getId()]->setLastEditAt($this->timezoneHelper->applyUserTimezone($sleeve->getLastEditAt())); + $rows[$sleeve->getId()]->setLastEditAt($sleeve->getLastEditAt()); $rows[$sleeve->getId()]->setTableId($sleeve->getTableId()); } diff --git a/lib/Helper/TimezoneHelper.php b/lib/Helper/TimezoneHelper.php deleted file mode 100644 index 2b588b3fcd..0000000000 --- a/lib/Helper/TimezoneHelper.php +++ /dev/null @@ -1,47 +0,0 @@ -getUserTimezone(); - - $dateTime = new \DateTimeImmutable($date, new DateTimeZone('UTC')); - - return $dateTime - ->setTimezone(new DateTimeZone($userTimezone)) - ->format('Y-m-d H:i:s'); - } - - public function getUserTimezone(): string { - if ($this->timezone) { - return $this->timezone; - } - - $userId = $this->userSession->getUser()?->getUID(); - $defaultTimeZone = $this->config->getSystemValueString('default_timezone', 'UTC'); - - if ($userId) { - $this->timezone = $this->config->getUserValue($userId, 'core', 'timezone', $defaultTimeZone); - } - - return $this->timezone ?? $defaultTimeZone; - } -} diff --git a/src/modules/main/partials/ColumnInfoPopover.vue b/src/modules/main/partials/ColumnInfoPopover.vue index 9e3bd229ec..2d04c6e6f4 100644 --- a/src/modules/main/partials/ColumnInfoPopover.vue +++ b/src/modules/main/partials/ColumnInfoPopover.vue @@ -72,7 +72,9 @@ export default { }, methods: { relativeDateTime(v) { - return moment(v).format('L') === moment().format('L') ? t('tables', 'Today') + ' ' + moment(v).format('LT') : moment(v).format('LLLL') + const value = moment.utc(v).local() + + return value.format('L') === moment().format('L') ? t('tables', 'Today') + ' ' + value.format('LT') : value.format('LLLL') }, }, } diff --git a/src/shared/components/ncTable/mixins/columnsTypes/datetime.js b/src/shared/components/ncTable/mixins/columnsTypes/datetime.js index e4d32af108..781fa5f54a 100644 --- a/src/shared/components/ncTable/mixins/columnsTypes/datetime.js +++ b/src/shared/components/ncTable/mixins/columnsTypes/datetime.js @@ -19,7 +19,7 @@ export default class DatetimeColumn extends AbstractDatetimeColumn { } formatValue(value) { - return Moment(value, 'YYYY-MM-DD HH:mm:ss').format('lll') + return Moment.utc(value, 'YYYY-MM-DD HH:mm:ss').local().format('lll') } sort(mode, nextSorts) { diff --git a/src/shared/components/ncTable/mixins/columnsTypes/datetimeDate.js b/src/shared/components/ncTable/mixins/columnsTypes/datetimeDate.js index db3d9cf6d6..45660a0293 100644 --- a/src/shared/components/ncTable/mixins/columnsTypes/datetimeDate.js +++ b/src/shared/components/ncTable/mixins/columnsTypes/datetimeDate.js @@ -15,7 +15,7 @@ export default class DatetimeDateColumn extends AbstractDatetimeColumn { } formatValue(value) { - return Moment(value, 'YYYY-MM-DD HH:mm:ss').format('ll') + return Moment.utc(value, 'YYYY-MM-DD HH:mm:ss').local().format('ll') } sort(mode, nextSorts) { diff --git a/src/shared/components/ncTable/mixins/columnsTypes/datetimeTime.js b/src/shared/components/ncTable/mixins/columnsTypes/datetimeTime.js index 04dc8a3a97..f04b261ec7 100644 --- a/src/shared/components/ncTable/mixins/columnsTypes/datetimeTime.js +++ b/src/shared/components/ncTable/mixins/columnsTypes/datetimeTime.js @@ -15,7 +15,7 @@ export default class DatetimeTimeColumn extends AbstractDatetimeColumn { } formatValue(value) { - return Moment(value, 'HH:mm:ss').format('LT') + return Moment.utc(value, 'HH:mm:ss').local().format('LT') } sort(mode, nextSorts) { diff --git a/src/shared/components/ncTable/partials/TableCellDateTime.vue b/src/shared/components/ncTable/partials/TableCellDateTime.vue index 3c69a8bd67..fb3fb7466b 100644 --- a/src/shared/components/ncTable/partials/TableCellDateTime.vue +++ b/src/shared/components/ncTable/partials/TableCellDateTime.vue @@ -171,7 +171,7 @@ export default { newValue = 'none' } else { const format = this.getDateFormat() - newValue = Moment(this.editDateTimeValue).format(format) + newValue = Moment(this.editDateTimeValue).utc().format(format) } if (newValue === this.value) { diff --git a/src/shared/components/ncTable/partials/rowTypePartials/DatetimeForm.vue b/src/shared/components/ncTable/partials/rowTypePartials/DatetimeForm.vue index 0e0fe07a3f..0bdd0735f7 100644 --- a/src/shared/components/ncTable/partials/rowTypePartials/DatetimeForm.vue +++ b/src/shared/components/ncTable/partials/rowTypePartials/DatetimeForm.vue @@ -59,7 +59,7 @@ export default { } else if (!v) { this.$emit('update:value', this.isMandatoryField ? null : 'none') } else { - this.$emit('update:value', Moment(v).format('YYYY-MM-DD HH:mm')) + this.$emit('update:value', Moment(v).utc().format('YYYY-MM-DD HH:mm')) } }, }, diff --git a/src/shared/components/ncTable/partials/rowTypePartials/DatetimeTimeForm.vue b/src/shared/components/ncTable/partials/rowTypePartials/DatetimeTimeForm.vue index 64face9d79..941b485b11 100644 --- a/src/shared/components/ncTable/partials/rowTypePartials/DatetimeTimeForm.vue +++ b/src/shared/components/ncTable/partials/rowTypePartials/DatetimeTimeForm.vue @@ -63,7 +63,7 @@ export default { if (v === 'none') { this.$emit('update:value', v) } else if (v) { - this.$emit('update:value', Moment(v).format('HH:mm')) + this.$emit('update:value', Moment(v).utc().format('HH:mm')) } }, }, diff --git a/tests/unit/Db/Row2MapperTestDependencies.php b/tests/unit/Db/Row2MapperTestDependencies.php index e7e9aba811..297eaf1674 100644 --- a/tests/unit/Db/Row2MapperTestDependencies.php +++ b/tests/unit/Db/Row2MapperTestDependencies.php @@ -15,7 +15,6 @@ use OCA\Tables\Db\RowSleeveMapper; use OCA\Tables\Helper\CircleHelper; use OCA\Tables\Helper\ColumnsHelper; -use OCA\Tables\Helper\TimezoneHelper; use OCA\Tables\Helper\UserHelper; use OCP\AppFramework\Db\DoesNotExistException; use PHPUnit\Framework\MockObject\MockObject; @@ -66,8 +65,7 @@ protected function setupDependencies(): void { $this->userHelper, $this->rowSleeveMapper, $this->columnsHelper, - $this->columnMapper, - $this->createMock(TimezoneHelper::class) + $this->columnMapper ); if (!self::$testDataInitialized) {