@@ -136,7 +136,7 @@ private function getPreviewData(Worksheet $worksheet): array {
136136 $ columns [] = [
137137 'title ' => $ title ,
138138 'type ' => $ this ->rawColumnDataTypes [$ colIndex ]['type ' ],
139- 'subtype ' => $ this ->rawColumnDataTypes [$ colIndex ]['subtype ' ],
139+ 'subtype ' => $ this ->rawColumnDataTypes [$ colIndex ]['subtype ' ] ?? null ,
140140 'numberDecimals ' => $ this ->rawColumnDataTypes [$ colIndex ]['number_decimals ' ] ?? 0 ,
141141 'numberPrefix ' => $ this ->rawColumnDataTypes [$ colIndex ]['number_prefix ' ] ?? '' ,
142142 'numberSuffix ' => $ this ->rawColumnDataTypes [$ colIndex ]['number_suffix ' ] ?? '' ,
@@ -154,13 +154,26 @@ private function getPreviewData(Worksheet $worksheet): array {
154154 $ cellIterator = $ row ->getCellIterator ();
155155 $ cellIterator ->setIterateOnlyExistingCells (false );
156156
157- foreach ($ cellIterator as $ cellIndex => $ cell ) {
157+ foreach ($ cellIterator as $ cell ) {
158158 $ value = $ cell ->getValue ();
159- $ colIndex = (int ) $ cellIndex ;
159+ // $cellIterator`s index is based on 1, not 0.
160+ $ colIndex = $ cellIterator ->getCurrentColumnIndex () - 1 ;
160161 $ column = $ this ->columns [$ colIndex ];
161162
162163 if (($ column && $ column ->getType () === 'datetime ' ) || (is_array ($ columns [$ colIndex ]) && $ columns [$ colIndex ]['type ' ] === 'datetime ' )) {
163- $ value = Date::excelToDateTimeObject ($ value )->format ('Y-m-d H:i ' );
164+ if (isset ($ columns [$ colIndex ]['subtype ' ]) && $ columns [$ colIndex ]['subtype ' ] === 'date ' ) {
165+ $ format = 'Y-m-d ' ;
166+ } elseif (isset ($ columns [$ colIndex ]['subtype ' ]) && $ columns [$ colIndex ]['subtype ' ] === 'time ' ) {
167+ $ format = 'H:i ' ;
168+ } else {
169+ $ format = 'Y-m-d H:i ' ;
170+ }
171+
172+ try {
173+ $ value = Date::excelToDateTimeObject ($ value )->format ($ format );
174+ } catch (\TypeError ) {
175+ $ value = (new \DateTimeImmutable ($ value ))->format ($ format );
176+ }
164177 } elseif (($ column && $ column ->getType () === 'number ' && $ column ->getNumberSuffix () === '% ' )
165178 || (is_array ($ columns [$ colIndex ]) && $ columns [$ colIndex ]['type ' ] === 'number ' && $ columns [$ colIndex ]['numberSuffix ' ] === '% ' )) {
166179 $ value = $ value * 100 ;
@@ -285,8 +298,14 @@ public function import(?int $tableId, ?int $viewId, string $path, bool $createMi
285298 * @throws PermissionError
286299 */
287300 private function loop (Worksheet $ worksheet ): void {
288- $ firstRow = $ worksheet ->getRowIterator ()->current ();
289- $ secondRow = $ worksheet ->getRowIterator ()->seek (2 )->current ();
301+ $ rowIterator = $ worksheet ->getRowIterator ();
302+ $ firstRow = $ rowIterator ->current ();
303+ $ rowIterator ->next ();
304+ if (!$ rowIterator ->valid ()) {
305+ return ;
306+ }
307+ $ secondRow = $ rowIterator ->current ();
308+ unset($ rowIterator );
290309 $ this ->getColumns ($ firstRow , $ secondRow );
291310
292311 if (empty (array_filter ($ this ->columns ))) {
@@ -361,8 +380,32 @@ private function createRow(Row $row): void {
361380
362381 $ value = $ cell ->getValue ();
363382 $ hasData = $ hasData || !empty ($ value );
383+
364384 if ($ column ->getType () === 'datetime ' ) {
365- $ value = Date::excelToDateTimeObject ($ value )->format ('Y-m-d H:i ' );
385+ if ($ column ->getType () === 'datetime ' && $ column ->getSubtype () === 'date ' ) {
386+ $ format = 'Y-m-d ' ;
387+ } elseif ($ column ->getType () === 'datetime ' && $ column ->getSubtype () === 'time ' ) {
388+ $ format = 'H:i ' ;
389+ } else {
390+ $ format = 'Y-m-d H:i ' ;
391+ }
392+ try {
393+ $ value = Date::excelToDateTimeObject ($ value )->format ($ format );
394+ } catch (\TypeError ) {
395+ $ value = (new \DateTimeImmutable ($ value ))->format ($ format );
396+ }
397+ } elseif ($ column ->getType () === 'datetime ' && $ column ->getSubtype () === 'date ' ) {
398+ try {
399+ $ value = Date::excelToDateTimeObject ($ value )->format ('Y-m-d ' );
400+ } catch (\TypeError ) {
401+ $ value = (new \DateTimeImmutable ($ value ))->format ('Y-m-d ' );
402+ }
403+ } elseif ($ column ->getType () === 'datetime ' && $ column ->getSubtype () === 'time ' ) {
404+ try {
405+ $ value = Date::excelToDateTimeObject ($ value )->format ('H:i ' );
406+ } catch (\TypeError ) {
407+ $ value = (new \DateTimeImmutable ($ value ))->format ('H:i ' );
408+ }
366409 } elseif ($ column ->getType () === 'number ' && $ column ->getNumberSuffix () === '% ' ) {
367410 $ value = $ value * 100 ;
368411 } elseif ($ column ->getType () === 'selection ' && $ column ->getSubtype () === 'check ' ) {
@@ -414,6 +457,8 @@ private function getColumns(Row $firstRow, Row $secondRow): void {
414457 $ index = 0 ;
415458 $ countMatchingColumnsFromConfig = 0 ;
416459 $ countCreatedColumnsFromConfig = 0 ;
460+ $ lastCellWasEmpty = false ;
461+ $ hasGapInTitles = false ;
417462 foreach ($ cellIterator as $ cell ) {
418463 if ($ cell && $ cell ->getValue () !== null && $ cell ->getValue () !== '' ) {
419464 $ title = $ cell ->getValue ();
@@ -437,14 +482,29 @@ private function getColumns(Row $firstRow, Row $secondRow): void {
437482
438483 // Convert data type to our data type
439484 $ dataTypes [] = $ this ->parseColumnDataType ($ secondRowCellIterator ->current ());
485+ if ($ lastCellWasEmpty ) {
486+ $ hasGapInTitles = true ;
487+ }
488+ $ lastCellWasEmpty = false ;
440489 } else {
441490 $ this ->logger ->debug ('No cell given or cellValue is empty while loading columns for importing ' );
491+ if ($ cell ->getDataType () === 'null ' ) {
492+ // LibreOffice generated XLSX doc may have more empty columns in the first row.
493+ // Continue without increasing error count, but leave a marker to detect gaps in titles.
494+ $ lastCellWasEmpty = true ;
495+ continue ;
496+ }
442497 $ this ->countErrors ++;
443498 }
444499 $ secondRowCellIterator ->next ();
445500 $ index ++;
446501 }
447502
503+ if ($ hasGapInTitles ) {
504+ $ this ->logger ->info ('Imported table is having a gap in column titles ' );
505+ $ this ->countErrors ++;
506+ }
507+
448508 $ this ->rawColumnTitles = $ titles ;
449509 $ this ->rawColumnDataTypes = $ dataTypes ;
450510
@@ -468,9 +528,33 @@ private function parseColumnDataType(Cell $cell): array {
468528 'subtype ' => 'line ' ,
469529 ];
470530
471- if (Date::isDateTime ($ cell ) || $ originDataType === DataType::TYPE_ISO_DATE ) {
531+ try {
532+ if ($ value === false ) {
533+ throw new \Exception ('We do not accept `false` here ' );
534+ }
535+ $ dateValue = new \DateTimeImmutable ($ value );
536+ } catch (\Exception ) {
537+ }
538+
539+ if (isset ($ dateValue )
540+ || Date::isDateTime ($ cell )
541+ || $ originDataType === DataType::TYPE_ISO_DATE ) {
542+ // the formatted value stems from the office document and shows the original user intent
543+ $ dateAnalysis = date_parse ($ formattedValue );
544+ $ containsDate = $ dateAnalysis ['year ' ] !== false || $ dateAnalysis ['month ' ] !== false || $ dateAnalysis ['day ' ] !== false ;
545+ $ containsTime = $ dateAnalysis ['hour ' ] !== false || $ dateAnalysis ['minute ' ] !== false || $ dateAnalysis ['second ' ] !== false ;
546+
547+ if ($ containsDate && !$ containsTime ) {
548+ $ subType = 'date ' ;
549+ } elseif (!$ containsDate && $ containsTime ) {
550+ $ subType = 'time ' ;
551+ } else {
552+ $ subType = '' ;
553+ }
554+
472555 $ dataType = [
473556 'type ' => 'datetime ' ,
557+ 'subtype ' => $ subType ,
474558 ];
475559 } elseif ($ originDataType === DataType::TYPE_NUMERIC ) {
476560 if (str_contains ($ formattedValue , '% ' )) {
@@ -514,7 +598,10 @@ private function parseColumnDataType(Cell $cell): array {
514598 'type ' => 'number ' ,
515599 ];
516600 }
517- } elseif ($ originDataType === DataType::TYPE_BOOL ) {
601+ } elseif ($ originDataType === DataType::TYPE_BOOL
602+ || ($ originDataType === DataType::TYPE_FORMULA
603+ && in_array ($ formattedValue , ['FALSE ' , 'TRUE ' ], true ))
604+ ) {
518605 $ dataType = [
519606 'type ' => 'selection ' ,
520607 'subtype ' => 'check ' ,
0 commit comments