|
31 | 31 | use Psr\Container\ContainerExceptionInterface;
|
32 | 32 | use Psr\Container\NotFoundExceptionInterface;
|
33 | 33 | use Psr\Log\LoggerInterface;
|
| 34 | +use function is_string; |
| 35 | +use function mb_strlen; |
| 36 | +use function preg_match; |
34 | 37 |
|
35 | 38 | class ImportService extends SuperService {
|
36 | 39 |
|
@@ -160,25 +163,38 @@ private function getPreviewData(Worksheet $worksheet): array {
|
160 | 163 | $colIndex = $cellIterator->getCurrentColumnIndex() - 1;
|
161 | 164 | $column = $this->columns[$colIndex];
|
162 | 165 |
|
163 |
| - if (($column && $column->getType() === 'datetime') || (is_array($columns[$colIndex]) && $columns[$colIndex]['type'] === 'datetime')) { |
164 |
| - if (isset($columns[$colIndex]['subtype']) && $columns[$colIndex]['subtype'] === 'date') { |
| 166 | + if ( |
| 167 | + ($column && $column->getType() === Column::TYPE_DATETIME) |
| 168 | + || (is_array($columns[$colIndex]) |
| 169 | + && $columns[$colIndex]['type'] === Column::TYPE_DATETIME) |
| 170 | + ) { |
| 171 | + if (isset($columns[$colIndex]['subtype']) |
| 172 | + && $columns[$colIndex]['subtype'] === Column::SUBTYPE_DATETIME_DATE |
| 173 | + ) { |
165 | 174 | $format = 'Y-m-d';
|
166 |
| - } elseif (isset($columns[$colIndex]['subtype']) && $columns[$colIndex]['subtype'] === 'time') { |
| 175 | + } elseif (isset($columns[$colIndex]['subtype']) |
| 176 | + && $columns[$colIndex]['subtype'] === Column::SUBTYPE_DATETIME_TIME |
| 177 | + ) { |
167 | 178 | $format = 'H:i';
|
168 | 179 | } else {
|
169 | 180 | $format = 'Y-m-d H:i';
|
170 | 181 | }
|
171 |
| - |
172 |
| - try { |
173 |
| - $value = Date::excelToDateTimeObject($value)->format($format); |
174 |
| - } catch (\TypeError) { |
175 |
| - $value = (new \DateTimeImmutable($value))->format($format); |
176 |
| - } |
177 |
| - } elseif (($column && $column->getType() === 'number' && $column->getNumberSuffix() === '%') |
178 |
| - || (is_array($columns[$colIndex]) && $columns[$colIndex]['type'] === 'number' && $columns[$colIndex]['numberSuffix'] === '%')) { |
| 182 | + $value = $this->parseAndFormatDateTimeString($value, $format); |
| 183 | + } elseif ( |
| 184 | + ($column && $column->getType() === Column::TYPE_NUMBER |
| 185 | + && $column->getNumberSuffix() === '%') |
| 186 | + || (is_array($columns[$colIndex]) |
| 187 | + && $columns[$colIndex]['type'] === Column::TYPE_NUMBER |
| 188 | + && $columns[$colIndex]['numberSuffix'] === '%') |
| 189 | + ) { |
179 | 190 | $value = $value * 100;
|
180 |
| - } elseif (($column && $column->getType() === 'selection' && $column->getSubtype() === 'check') |
181 |
| - || (is_array($columns[$colIndex]) && $columns[$colIndex]['type'] === 'selection' && $columns[$colIndex]['subtype'] === 'check')) { |
| 191 | + } elseif ( |
| 192 | + ($column && $column->getType() === Column::TYPE_SELECTION |
| 193 | + && $column->getSubtype() === Column::SUBTYPE_SELECTION_CHECK) |
| 194 | + || (is_array($columns[$colIndex]) |
| 195 | + && $columns[$colIndex]['type'] === Column::TYPE_SELECTION |
| 196 | + && $columns[$colIndex]['subtype'] === Column::SUBTYPE_SELECTION_CHECK) |
| 197 | + ) { |
182 | 198 | $value = $cell->getFormattedValue() === 'TRUE' ? 'true' : 'false';
|
183 | 199 | }
|
184 | 200 |
|
@@ -381,34 +397,22 @@ private function createRow(Row $row): void {
|
381 | 397 | $value = $cell->getValue();
|
382 | 398 | $hasData = $hasData || !empty($value);
|
383 | 399 |
|
384 |
| - if ($column->getType() === 'datetime') { |
385 |
| - if ($column->getType() === 'datetime' && $column->getSubtype() === 'date') { |
| 400 | + if ($column->getType() === Column::TYPE_DATETIME) { |
| 401 | + if ($column->getSubtype() === Column::SUBTYPE_DATETIME_DATE) { |
386 | 402 | $format = 'Y-m-d';
|
387 |
| - } elseif ($column->getType() === 'datetime' && $column->getSubtype() === 'time') { |
| 403 | + } elseif ($column->getSubtype() === Column::SUBTYPE_DATETIME_TIME) { |
388 | 404 | $format = 'H:i';
|
389 | 405 | } else {
|
390 | 406 | $format = 'Y-m-d H:i';
|
391 | 407 | }
|
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 |
| - } |
409 |
| - } elseif ($column->getType() === 'number' && $column->getNumberSuffix() === '%') { |
| 408 | + $value = $this->parseAndFormatDateTimeString($value, $format); |
| 409 | + } elseif ($column->getType() === Column::TYPE_NUMBER |
| 410 | + && $column->getNumberSuffix() === '%' |
| 411 | + ) { |
410 | 412 | $value = $value * 100;
|
411 |
| - } elseif ($column->getType() === 'selection' && $column->getSubtype() === 'check') { |
| 413 | + } elseif ($column->getType() === Column::TYPE_SELECTION |
| 414 | + && $column->getSubtype() === Column::SUBTYPE_SELECTION_CHECK |
| 415 | + ) { |
412 | 416 | $value = $cell->getFormattedValue() === 'TRUE' ? 'true' : 'false';
|
413 | 417 | }
|
414 | 418 |
|
@@ -440,6 +444,37 @@ private function createRow(Row $row): void {
|
440 | 444 |
|
441 | 445 | }
|
442 | 446 |
|
| 447 | + private function valueToDateTimeImmutable(mixed $value): ?\DateTimeImmutable { |
| 448 | + if ( |
| 449 | + $value === false |
| 450 | + || $value === null |
| 451 | + || (is_string($value) |
| 452 | + && mb_strlen($value) < 3 // Let pass potential 3-letter month names |
| 453 | + && preg_match('/\d/', $value) !== 1) // or anything containing a digit |
| 454 | + ) { |
| 455 | + return null; |
| 456 | + } |
| 457 | + try { |
| 458 | + $dt = Date::excelToDateTimeObject($value); |
| 459 | + return \DateTimeImmutable::createFromMutable($dt); |
| 460 | + } catch (\TypeError) { |
| 461 | + try { |
| 462 | + return (new \DateTimeImmutable($value)); |
| 463 | + } catch (\Exception $e) { |
| 464 | + $this->logger->debug('Could not parse string {value} as date time.', [ |
| 465 | + 'exception' => $e, |
| 466 | + 'value' => $value, |
| 467 | + ]); |
| 468 | + return null; |
| 469 | + } |
| 470 | + } |
| 471 | + } |
| 472 | + |
| 473 | + private function parseAndFormatDateTimeString(?string $value, string $format): string { |
| 474 | + $dateTime = $this->valueToDateTimeImmutable($value); |
| 475 | + return $dateTime?->format($format) ?: ''; |
| 476 | + } |
| 477 | + |
443 | 478 | /**
|
444 | 479 | * @param Row $firstRow
|
445 | 480 | * @param Row $secondRow
|
@@ -524,87 +559,81 @@ private function parseColumnDataType(Cell $cell): array {
|
524 | 559 | $value = $cell->getValue();
|
525 | 560 | $formattedValue = $cell->getFormattedValue();
|
526 | 561 | $dataType = [
|
527 |
| - 'type' => 'text', |
528 |
| - 'subtype' => 'line', |
| 562 | + 'type' => Column::TYPE_TEXT, |
| 563 | + 'subtype' => Column::SUBTYPE_TEXT_LINE, |
529 | 564 | ];
|
530 | 565 |
|
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) |
| 566 | + if (!is_numeric($formattedValue) |
| 567 | + && ($this->valueToDateTimeImmutable($value) instanceof \DateTimeImmutable |
540 | 568 | || Date::isDateTime($cell)
|
541 |
| - || $originDataType === DataType::TYPE_ISO_DATE) { |
| 569 | + || $originDataType === DataType::TYPE_ISO_DATE) |
| 570 | + ) { |
542 | 571 | // the formatted value stems from the office document and shows the original user intent
|
543 | 572 | $dateAnalysis = date_parse($formattedValue);
|
544 | 573 | $containsDate = $dateAnalysis['year'] !== false || $dateAnalysis['month'] !== false || $dateAnalysis['day'] !== false;
|
545 | 574 | $containsTime = $dateAnalysis['hour'] !== false || $dateAnalysis['minute'] !== false || $dateAnalysis['second'] !== false;
|
546 | 575 |
|
547 | 576 | if ($containsDate && !$containsTime) {
|
548 |
| - $subType = 'date'; |
| 577 | + $subType = Column::SUBTYPE_DATETIME_DATE; |
549 | 578 | } elseif (!$containsDate && $containsTime) {
|
550 |
| - $subType = 'time'; |
| 579 | + $subType = Column::SUBTYPE_DATETIME_TIME; |
551 | 580 | } else {
|
552 | 581 | $subType = '';
|
553 | 582 | }
|
554 | 583 |
|
555 | 584 | $dataType = [
|
556 |
| - 'type' => 'datetime', |
| 585 | + 'type' => Column::TYPE_DATETIME, |
557 | 586 | 'subtype' => $subType,
|
558 | 587 | ];
|
559 | 588 | } elseif ($originDataType === DataType::TYPE_NUMERIC) {
|
560 | 589 | if (str_contains($formattedValue, '%')) {
|
561 | 590 | $dataType = [
|
562 |
| - 'type' => 'number', |
| 591 | + 'type' => Column::TYPE_NUMBER, |
563 | 592 | 'number_decimals' => 2,
|
564 | 593 | 'number_suffix' => '%',
|
565 | 594 | ];
|
566 | 595 | } elseif (str_contains($formattedValue, '€')) {
|
567 | 596 | $dataType = [
|
568 |
| - 'type' => 'number', |
| 597 | + 'type' => Column::TYPE_NUMBER, |
569 | 598 | 'number_decimals' => 2,
|
570 | 599 | 'number_suffix' => '€',
|
571 | 600 | ];
|
572 | 601 | } elseif (str_contains($formattedValue, 'EUR')) {
|
573 | 602 | $dataType = [
|
574 |
| - 'type' => 'number', |
| 603 | + 'type' => Column::TYPE_NUMBER, |
575 | 604 | 'number_decimals' => 2,
|
576 | 605 | 'number_suffix' => 'EUR',
|
577 | 606 | ];
|
578 | 607 | } elseif (str_contains($formattedValue, '$')) {
|
579 | 608 | $dataType = [
|
580 |
| - 'type' => 'number', |
| 609 | + 'type' => Column::TYPE_NUMBER, |
581 | 610 | 'number_decimals' => 2,
|
582 | 611 | 'number_prefix' => '$',
|
583 | 612 | ];
|
584 | 613 | } elseif (str_contains($formattedValue, 'USD')) {
|
585 | 614 | $dataType = [
|
586 |
| - 'type' => 'number', |
| 615 | + 'type' => Column::TYPE_NUMBER, |
587 | 616 | 'number_decimals' => 2,
|
588 | 617 | 'number_suffix' => 'USD',
|
589 | 618 | ];
|
590 | 619 | } elseif (is_float($value)) {
|
591 | 620 | $decimals = strlen(substr(strrchr((string)$value, '.'), 1));
|
592 | 621 | $dataType = [
|
593 |
| - 'type' => 'number', |
| 622 | + 'type' => Column::TYPE_NUMBER, |
594 | 623 | 'number_decimals' => $decimals,
|
595 | 624 | ];
|
596 | 625 | } else {
|
597 | 626 | $dataType = [
|
598 |
| - 'type' => 'number', |
| 627 | + 'type' => Column::TYPE_NUMBER, |
599 | 628 | ];
|
600 | 629 | }
|
601 | 630 | } elseif ($originDataType === DataType::TYPE_BOOL
|
602 | 631 | || ($originDataType === DataType::TYPE_FORMULA
|
603 | 632 | && in_array($formattedValue, ['FALSE', 'TRUE'], true))
|
604 | 633 | ) {
|
605 | 634 | $dataType = [
|
606 |
| - 'type' => 'selection', |
607 |
| - 'subtype' => 'check', |
| 635 | + 'type' => Column::TYPE_SELECTION, |
| 636 | + 'subtype' => Column::SUBTYPE_SELECTION_CHECK, |
608 | 637 | 'selection_default' => 'false',
|
609 | 638 | ];
|
610 | 639 | }
|
|
0 commit comments