isNullable(), $fieldInformation->getFormat(), $fieldInformation->getPersistenceType(), ); } public static function createFromDatabaseValueAndTCAConfig(int|string|null $value, array $fieldConfig): ?\DateTimeImmutable { $persistenceType = in_array($fieldConfig['dbType'] ?? null, QueryHelper::getDateTimeTypes(), true) ? $fieldConfig['dbType'] : null; $isNative = $persistenceType !== null; $isNullable = (bool)($fieldConfig['nullable'] ?? $isNative); $format = self::getFormatFromTCAConfig($fieldConfig); return self::fromDatabase( $value, $isNullable, $format, $persistenceType ); } public static function getFormatFromTCAConfig(array $fieldConfig): string { $format = $fieldConfig['format'] ?? null; $persistenceType = in_array($fieldConfig['dbType'] ?? null, QueryHelper::getDateTimeTypes(), true) ? $fieldConfig['dbType'] : null; // A native time field must not be formatted as date if (($format === 'datetime' || $format === 'datetimesec' || $format === 'date') && $persistenceType === 'time') { return 'timesec'; } // A native date field must not be formatted as time if (($format === 'time' || $format === 'timesec' || $format === 'datetime' || $format === 'datetimesec') && $persistenceType === 'date') { return 'date'; } if (in_array($format, ['datetime', 'date', 'time', 'timesec', 'datetimesec'], true)) { return $format; } if ($persistenceType !== null) { return $persistenceType === 'time' ? 'timesec' : $persistenceType; } return 'datetime'; } /** * Create a DateTimeImmutable object from a unix timestamp in server localtime * * Alternative to \DateTimeImmutable('@…') which forces UTC timezone */ public static function createFromTimestamp(int $timestamp): \DateTimeImmutable { // Create a new DateTime object in current timezone // // Note: As documented by PHP, `\DateTime` or `\DateTimeImmutable` // objects created from timestamps (e.g., '@12345678') as the // first constructor argument will use UTC as timezone instead of localtime, // therefore we must not initialize with a timestamp directly. $datetime = new \DateTimeImmutable(); // Apply timestamp (which will not change the objects timezone) return $datetime->setTimestamp($timestamp); } private static function fromDatabase( int|string|null $value, bool $isNullable, string $format, ?string $persistenceType ): ?\DateTimeImmutable { if ($value === null || $value === '') { return null; } $emptyFormat = QueryHelper::getDateTimeFormats()[$persistenceType ?? '']['empty'] ?? null; // A regular empty value is null for nullable fields $emptyValue = $isNullable ? null : ($emptyFormat ?? 0); // A legacy empty value is "0000-00-00" or "0000-00-00 00:00:00" stored // in a nullable native DATE or DATETIME field (which should already use // a proper `null` value, but still has a legacy empty value set). $legacyEmptyValue = $persistenceType === 'date' || $persistenceType === 'datetime' ? $emptyFormat : null; if (MathUtility::canBeInterpretedAsInteger($value)) { $value = (int)$value; } if ($value === $emptyValue || $value === $legacyEmptyValue) { return null; } try { $datetime = match (true) { is_int($value) && ($format === 'time' || $format === 'timesec') => new \DateTimeImmutable( // time(sec) is stored as elapsed seconds in DB and has no defined date associated. // Per convention we map to 1970-01-01 for the sake of a reliable date. // We still want a PHP localtime timezone in the DateTime object set, // therefore we interpret the second as UTC time on 1970-01-01T00:00:00 // and map the resulting value to PHP localtime gmdate(DateTimeFormat::ISO8601_LOCALTIME, $value) ), // Unix timestamp is_int($value) => self::createFromTimestamp($value), // The database always contains server localtime in native fields. // The field value is something like "2016-01-01" or "2016-01-01 10:11:12. default => new \DateTimeImmutable($value), }; } catch (\DateMalformedStringException $e) { throw new \InvalidArgumentException('Invalid date provided', 1743159490, $e); } return match ($format) { // time(sec) is stored as elapsed seconds in DB, hence we normalize it as time on 1970-01-01 for consistency 'time' => $datetime->setDate(1970, 1, 1)->setTime((int)$datetime->format('H'), (int)$datetime->format('i'), 0), 'timesec' => $datetime->setDate(1970, 1, 1), 'date' => $datetime->setTime(0, 0, 0), // default case also for 'datetimesec' default => $datetime, }; } }