diff --git a/lib/Service/ColumnTypes/DatetimeTimeBusiness.php b/lib/Service/ColumnTypes/DatetimeTimeBusiness.php index 6d06cc39e..e0d41e4b5 100644 --- a/lib/Service/ColumnTypes/DatetimeTimeBusiness.php +++ b/lib/Service/ColumnTypes/DatetimeTimeBusiness.php @@ -17,7 +17,7 @@ class DatetimeTimeBusiness extends SuperBusiness implements IColumnTypeBusiness * @return string */ public function parseValue($value, ?Column $column = null): string { - return json_encode($this->isValidDate($value, 'H:i') ? $value : ''); + return json_encode($this->isValidDate((string)$value, 'H:i') ? $value : ''); } /** @@ -26,7 +26,7 @@ public function parseValue($value, ?Column $column = null): string { * @return bool */ public function canBeParsed($value, ?Column $column = null): bool { - return $this->isValidDate($value, 'H:i'); + return $this->isValidDate((string)$value, 'H:i'); } } diff --git a/lib/Service/ImportService.php b/lib/Service/ImportService.php index 6e9ddade2..d39638a59 100644 --- a/lib/Service/ImportService.php +++ b/lib/Service/ImportService.php @@ -161,10 +161,18 @@ private function getPreviewData(Worksheet $worksheet): array { $column = $this->columns[$colIndex]; if (($column && $column->getType() === 'datetime') || (is_array($columns[$colIndex]) && $columns[$colIndex]['type'] === 'datetime')) { + if (isset($columns[$colIndex]['subtype']) && $columns[$colIndex]['subtype'] === 'date') { + $format = 'Y-m-d'; + } elseif (isset($columns[$colIndex]['subtype']) && $columns[$colIndex]['subtype'] === 'time') { + $format = 'H:i'; + } else { + $format = 'Y-m-d H:i'; + } + try { - $value = Date::excelToDateTimeObject($value)->format('Y-m-d H:i'); + $value = Date::excelToDateTimeObject($value)->format($format); } catch (\TypeError) { - $value = (new \DateTimeImmutable($value))->format('Y-m-d H:i'); + $value = (new \DateTimeImmutable($value))->format($format); } } elseif (($column && $column->getType() === 'number' && $column->getNumberSuffix() === '%') || (is_array($columns[$colIndex]) && $columns[$colIndex]['type'] === 'number' && $columns[$colIndex]['numberSuffix'] === '%')) { @@ -372,11 +380,19 @@ private function createRow(Row $row): void { $value = $cell->getValue(); $hasData = $hasData || !empty($value); - if ($column->getType() === 'datetime' && $column->getSubtype() === '') { + + if ($column->getType() === 'datetime') { + if ($column->getType() === 'datetime' && $column->getSubtype() === 'date') { + $format = 'Y-m-d'; + } elseif ($column->getType() === 'datetime' && $column->getSubtype() === 'time') { + $format = 'H:i'; + } else { + $format = 'Y-m-d H:i'; + } try { - $value = Date::excelToDateTimeObject($value)->format('Y-m-d H:i'); + $value = Date::excelToDateTimeObject($value)->format($format); } catch (\TypeError) { - $value = (new \DateTimeImmutable($value))->format('Y-m-d H:i'); + $value = (new \DateTimeImmutable($value))->format($format); } } elseif ($column->getType() === 'datetime' && $column->getSubtype() === 'date') { try { @@ -384,6 +400,12 @@ private function createRow(Row $row): void { } catch (\TypeError) { $value = (new \DateTimeImmutable($value))->format('Y-m-d'); } + } elseif ($column->getType() === 'datetime' && $column->getSubtype() === 'time') { + try { + $value = Date::excelToDateTimeObject($value)->format('H:i'); + } catch (\TypeError) { + $value = (new \DateTimeImmutable($value))->format('H:i'); + } } elseif ($column->getType() === 'number' && $column->getNumberSuffix() === '%') { $value = $value * 100; } elseif ($column->getType() === 'selection' && $column->getSubtype() === 'check') { @@ -496,15 +518,33 @@ private function parseColumnDataType(Cell $cell): array { ]; try { + if ($value === false) { + // introduced in PHP 8.3, but provided by symfony-polyfill in server + throw new \DateMalformedStringException('We do not accept `false` here'); + } $dateValue = new \DateTimeImmutable($value); - } catch (\Exception $e) { + } catch (\Exception) { } - if ((isset($dateValue) && $originDataType === DataType::TYPE_STRING) + + if (isset($dateValue) || Date::isDateTime($cell) || $originDataType === DataType::TYPE_ISO_DATE) { + // the formatted value stems from the office document and shows the original user intent + $dateAnalysis = date_parse($formattedValue); + $containsDate = $dateAnalysis['year'] !== false || $dateAnalysis['month'] !== false || $dateAnalysis['day'] !== false; + $containsTime = $dateAnalysis['hour'] !== false || $dateAnalysis['minute'] !== false || $dateAnalysis['second'] !== false; + + if ($containsDate && !$containsTime) { + $subType = 'date'; + } elseif (!$containsDate && $containsTime) { + $subType = 'time'; + } else { + $subType = ''; + } + $dataType = [ 'type' => 'datetime', - 'subtype' => $cell->getCalculateDateTimeType() === Cell::CALCULATE_DATE_TIME_ASIS ? 'date' : '', + 'subtype' => $subType, ]; } elseif ($originDataType === DataType::TYPE_NUMERIC) { if (str_contains($formattedValue, '%')) { diff --git a/tests/integration/features/APIv1.feature b/tests/integration/features/APIv1.feature index 23b6ee828..a1e2985df 100644 --- a/tests/integration/features/APIv1.feature +++ b/tests/integration/features/APIv1.feature @@ -193,10 +193,10 @@ Feature: APIv1 And table "Import test" with emoji "👨🏻‍💻" exists for user "participant1" as "base1" When user imports file "/" into last created table Then import results have the following data - | found_columns_count | 8 | - | created_columns_count | 8 | - | inserted_rows_count | 2 | - | errors_count | 0 | + | found_columns_count | 10 | + | created_columns_count | 10 | + | inserted_rows_count | 2 | + | errors_count | 0 | Then table has at least following typed columns | Col1 | text | | Col2 | text | @@ -207,9 +207,9 @@ Feature: APIv1 | date | datetime | | truth | selection | Then table contains at least following rows - | Col1 | Col2 | Col3 | num | emoji | special | date | truth | - | Val1 | Val2 | Val3 | 1 | 💙 | Ä | 2024-02-24 | false | - | great | news | here | 99 | ⚠ | Ö | 2016-06-01 | true | + | Date and Time | Col1 | Col2 | Col3 | num | emoji | special | date | truth | time | + | 2022-02-20 08:42 | Val1 | Val2 | Val3 | 1 | 💙 | Ä | 2024-02-24 | false | 18:48 | + | 2016-06-01 13:37 | great | news | here | 99 | ⚠ | Ö | 2016-06-01 | true | 01:23 | Examples: | importfile | diff --git a/tests/integration/resources/import-from-libreoffice.csv b/tests/integration/resources/import-from-libreoffice.csv index d27e813d1..13d758cf7 100644 --- a/tests/integration/resources/import-from-libreoffice.csv +++ b/tests/integration/resources/import-from-libreoffice.csv @@ -1,3 +1,3 @@ -Col1,Col2,Col3,num,emoji,special,date,truth -Val1,Val2,Val3,1,💙,Ä,2024-02-24,false -great,news,here,99,⚠,Ö,2016-06-01,true +Date and Time,Col1,Col2,Col3,num,emoji,special,date,truth,time +2022-02-20 08:42,Val1,Val2,Val3,1,💙,Ä,2024-02-24,false,18:48 +2016-06-01 13:37,great,news,here,99,⚠,Ö,2016-06-01,true,01:23 diff --git a/tests/integration/resources/import-from-libreoffice.ods b/tests/integration/resources/import-from-libreoffice.ods index 5d539f6e6..617a94508 100644 Binary files a/tests/integration/resources/import-from-libreoffice.ods and b/tests/integration/resources/import-from-libreoffice.ods differ diff --git a/tests/integration/resources/import-from-libreoffice.xlsx b/tests/integration/resources/import-from-libreoffice.xlsx index 6cdff60f7..492d8706e 100644 Binary files a/tests/integration/resources/import-from-libreoffice.xlsx and b/tests/integration/resources/import-from-libreoffice.xlsx differ diff --git a/tests/integration/resources/import-from-ms365.xlsx b/tests/integration/resources/import-from-ms365.xlsx index 896b4276c..7e9ad13c4 100644 Binary files a/tests/integration/resources/import-from-ms365.xlsx and b/tests/integration/resources/import-from-ms365.xlsx differ