Skip to content

Commit 6303da6

Browse files
committed
[TASK] Extract DateTime to-database transformation from DataHandler
Add QueryHelper::transformDateTimeToDatabaseValue for later use in extbase persistence layer. Also make use of DateTimeFactory for UTC timestamp conversion to localtime \DateTimeImmutable objects. Resolves: #106461 Releases: main, 13.4 Change-Id: Ib981aaa972eb9cb91fe438ad4702ff45fa2c3a6c Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/88835 Reviewed-by: Benjamin Franzke <ben@bnf.dev> Reviewed-by: Stefan Bürk <stefan@buerk.tech> Reviewed-by: Benni Mack <benni@typo3.org> Tested-by: Benni Mack <benni@typo3.org> Tested-by: core-ci <typo3@b13.com> Tested-by: Stefan Bürk <stefan@buerk.tech> Tested-by: Benjamin Franzke <ben@bnf.dev>
1 parent 2aaf2a3 commit 6303da6

File tree

2 files changed

+65
-44
lines changed

2 files changed

+65
-44
lines changed

typo3/sysext/core/Classes/DataHandling/DataHandler.php

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
use TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor;
5050
use TYPO3\CMS\Core\DataHandling\Model\CorrelationId;
5151
use TYPO3\CMS\Core\DataHandling\Model\RecordStateFactory;
52+
use TYPO3\CMS\Core\Domain\DateTimeFactory;
5253
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
5354
use TYPO3\CMS\Core\Html\RteHtmlParser;
5455
use TYPO3\CMS\Core\LinkHandling\Exception\UnknownLinkHandlerException;
@@ -2002,19 +2003,16 @@ protected function checkValueForDatetime(int|string|\DateTimeInterface|null $val
20022003
// Handle native date/time fields
20032004
$isNativeDateTimeField = false;
20042005
$isNullable = $tcaFieldConf['nullable'] ?? false;
2005-
$nullValue = $isNullable ? null : 0;
2006-
$nativeDateTimeFieldFormat = null;
20072006
$nativeDateTimeType = $tcaFieldConf['dbType'] ?? null;
20082007
if (in_array($nativeDateTimeType, QueryHelper::getDateTimeTypes(), true)) {
2009-
$isNativeDateTimeField = true;
20102008
$isNullable = $tcaFieldConf['nullable'] ?? true;
20112009
$dateTimeFormats = QueryHelper::getDateTimeFormats();
2012-
$nativeDateTimeFieldFormat = $dateTimeFormats[$nativeDateTimeType]['format'];
20132010
$nativeDateTimeFieldEmptyValue = $dateTimeFormats[$nativeDateTimeType]['empty'];
2014-
$nullValue = $isNullable ? null : $nativeDateTimeFieldEmptyValue;
20152011
if ($value === $nativeDateTimeFieldEmptyValue && $nativeDateTimeType !== 'time') {
20162012
$value = null;
20172013
}
2014+
} else {
2015+
$nativeDateTimeType = null;
20182016
}
20192017

20202018
if (!$this->validateValueForRequired($tcaFieldConf, $value instanceof \DateTimeInterface ? $value->format(\DateTimeInterface::ATOM) : (string)$value)) {
@@ -2029,9 +2027,9 @@ protected function checkValueForDatetime(int|string|\DateTimeInterface|null $val
20292027
$datetime = match (true) {
20302028
$value === null => null,
20312029
$value instanceof \DateTimeImmutable => $value,
2032-
$value instanceof \DateTime => \DateTimeImmutable::createFromMutable($value),
2030+
$value instanceof \DateTimeInterface => \DateTimeImmutable::createFromInterface($value),
20332031
// Unix timestamp
2034-
is_int($value) || MathUtility::canBeInterpretedAsInteger($value) => new \DateTimeImmutable('@' . $value),
2032+
is_int($value) || MathUtility::canBeInterpretedAsInteger($value) => DateTimeFactory::createFromTimestamp($value),
20352033
// The value we receive from the backend form is an unqualified ISO 8601 date,
20362034
// for instance "1999-11-11T11:11:11".
20372035
// We can also accept an ISO8601 date with offsets,
@@ -2044,47 +2042,19 @@ protected function checkValueForDatetime(int|string|\DateTimeInterface|null $val
20442042
$datetime = null;
20452043
}
20462044

2047-
if ($datetime === null) {
2048-
return ['value' => $nullValue];
2049-
}
2050-
2051-
// Apply format-specific normalizations
2052-
if ($format === 'time') {
2053-
// time(sec) is stored as elapsed seconds in DB, hence we interpret it as time on 1970-01-01
2054-
$datetime = $datetime->setDate(1970, 01, 01)->setTime((int)$datetime->format('H'), (int)$datetime->format('i'), 0);
2055-
} elseif ($format === 'timesec' || $nativeDateTimeType === 'time') {
2056-
$datetime = $datetime->setDate(1970, 01, 01);
2057-
} elseif ($format === 'date' || $nativeDateTimeType === 'date') {
2058-
$datetime = $datetime->setTime(0, 0, 0);
2059-
}
2060-
2061-
$upper = isset($tcaFieldConf['range']['upper']) ? new \DateTimeImmutable('@' . $tcaFieldConf['range']['upper']) : null;
2062-
if ($upper !== null && $datetime > $upper) {
2063-
$datetime = $upper;
2064-
}
2065-
2066-
$lower = isset($tcaFieldConf['range']['lower']) ? new \DateTimeImmutable('@' . $tcaFieldConf['range']['lower']) : null;
2067-
if ($lower !== null && $datetime < $lower) {
2068-
$datetime = $lower;
2069-
}
2070-
2071-
// Handle native date/time fields
2072-
if ($isNativeDateTimeField) {
2073-
if ($nativeDateTimeType === 'datetime') {
2074-
// native DATETIME values are stored in server LOCALTIME. Force conversion to the servers current timezone.
2075-
$datetime = $datetime->setTimezone(new \DateTimeZone(date_default_timezone_get()));
2045+
if ($datetime !== null) {
2046+
$upper = isset($tcaFieldConf['range']['upper']) ? DateTimeFactory::createFromTimestamp((int)$tcaFieldConf['range']['upper']) : null;
2047+
if ($upper !== null && $datetime > $upper) {
2048+
$datetime = $upper;
20762049
}
2077-
// Format the value back to a date(time) string
2078-
return ['value' => $datetime->format($nativeDateTimeFieldFormat)];
2079-
}
20802050

2081-
if ($format === 'timesec' || $format === 'time') {
2082-
// Time is stored in seconds for integer fields
2083-
return ['value' => (int)$datetime->format('H') * 3600 + (int)$datetime->format('i') * 60 + (int)$datetime->format('s')];
2051+
$lower = isset($tcaFieldConf['range']['lower']) ? DateTimeFactory::createFromTimestamp((int)$tcaFieldConf['range']['lower']) : null;
2052+
if ($lower !== null && $datetime < $lower) {
2053+
$datetime = $lower;
2054+
}
20842055
}
20852056

2086-
// Encode as unix timestamp (int) if no native field is used
2087-
return ['value' => $datetime->getTimestamp()];
2057+
return ['value' => QueryHelper::transformDateTimeToDatabaseValue($datetime, $isNullable, $format, $nativeDateTimeType)];
20882058
}
20892059

20902060
/**

typo3/sysext/core/Classes/Database/Query/QueryHelper.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,57 @@ public static function getDateTimeTypes(): array
217217
];
218218
}
219219

220+
public static function transformDateTimeToDatabaseValue(
221+
?\DateTimeInterface $datetime,
222+
bool $isNullable,
223+
string $format,
224+
?string $persistenceType,
225+
): int|string|null {
226+
if ($datetime === null) {
227+
if ($isNullable) {
228+
return null;
229+
}
230+
if ($persistenceType === null) {
231+
return 0;
232+
}
233+
return self::getDateTimeFormats()[$persistenceType]['empty'] ?? null;
234+
}
235+
236+
if (!$datetime instanceof \DateTimeImmutable) {
237+
$datetime = \DateTimeImmutable::createFromInterface($datetime);
238+
}
239+
240+
// Apply format-specific normalizations
241+
if ($format === 'time') {
242+
// time(sec) is stored as elapsed seconds in DB, hence we base the time on 1970-01-01
243+
$datetime = $datetime->setDate(1970, 01, 01)->setTime((int)$datetime->format('H'), (int)$datetime->format('i'), 0);
244+
} elseif ($format === 'timesec' || $persistenceType === 'time') {
245+
$datetime = $datetime->setDate(1970, 01, 01);
246+
} elseif ($format === 'date' || $persistenceType === 'date') {
247+
$datetime = $datetime->setTime(0, 0, 0);
248+
}
249+
250+
// Native DATETIME, DATE or TIME field
251+
if (in_array($persistenceType, self::getDateTimeTypes(), true)) {
252+
$dateTimeFormats = self::getDateTimeFormats();
253+
$persistenceFormat = $dateTimeFormats[$persistenceType]['format'];
254+
if ($persistenceType === 'datetime') {
255+
// native DATETIME values are stored in server LOCALTIME. Force conversion to the servers current timezone.
256+
$datetime = $datetime->setTimezone(new \DateTimeZone(date_default_timezone_get()));
257+
}
258+
259+
return $datetime->format($persistenceFormat);
260+
}
261+
262+
// Time is stored in seconds for integer fields
263+
if ($format === 'timesec' || $format === 'time') {
264+
return (int)$datetime->format('H') * 3600 + (int)$datetime->format('i') * 60 + (int)$datetime->format('s');
265+
}
266+
267+
// Encode as unix timestamp (int) if no native field is used
268+
return $datetime->getTimestamp();
269+
}
270+
220271
/**
221272
* Quote database table/column names indicated by {#identifier} markup in a SQL fragment string.
222273
* This is an intermediate step to make SQL fragments in Typoscript and TCA database agnostic.

0 commit comments

Comments
 (0)