diff --git a/app/Service/Import/Importers/ClockifyTimeEntriesImporter.php b/app/Service/Import/Importers/ClockifyTimeEntriesImporter.php index 96ed4a8c..19d30af3 100644 --- a/app/Service/Import/Importers/ClockifyTimeEntriesImporter.php +++ b/app/Service/Import/Importers/ClockifyTimeEntriesImporter.php @@ -124,34 +124,59 @@ class ClockifyTimeEntriesImporter extends DefaultImporter $timeEntry->is_imported = true; // Start + $start = null; try { - if (preg_match('/^[0-9]{1,2}:[0-9]{1,2} (AM|PM)$/', $record['Start Time']) === 1) { - $start = Carbon::createFromFormat('m/d/Y h:i A', $record['Start Date'].' '.$record['Start Time'], $timezone); - } else { - $start = Carbon::createFromFormat('m/d/Y H:i:s A', $record['Start Date'].' '.$record['Start Time'], $timezone); + $startDateStr = $record['Start Date']; + $startTimeStr = $record['Start Time']; + $startStr = $startDateStr.' '.$startTimeStr; + $matches = []; + $checkResult = preg_match('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4}) ([0-9]{1,2}):([0-9]{1,2})(:[0-9]{1,2})? (AM|PM)$/', $startStr, $matches); + + if ($checkResult === 1) { + if ((int) $matches[1] > 12) { + throw new ImportException('Start date ("'.$startDateStr.'") is invalid, please select the correct date format before exporting from Clockify'); + } + if ($matches[6] === '') { + $start = Carbon::createFromFormat('m/d/Y h:i A', $startStr, $timezone); + } else { + $start = Carbon::createFromFormat('m/d/Y H:i:s A', $startStr, $timezone); + } } } catch (InvalidFormatException) { - throw new ImportException('Start date ("'.$record['Start Date'].'") or time ("'.$record['Start Time'].'") are invalid'); + throw new ImportException('Start date ("'.$startDateStr.'") or time ("'.$startTimeStr.'") are invalid'); } if ($start === null) { - throw new ImportException('Start date ("'.$record['Start Date'].'") or time ("'.$record['Start Time'].'") are invalid'); + throw new ImportException('Start date ("'.$startDateStr.'") or time ("'.$startTimeStr.'") are invalid'); } $timeEntry->start = $start->utc(); // End + $end = null; try { - if (preg_match('/^[0-9]{1,2}:[0-9]{1,2} (AM|PM)$/', $record['End Time']) === 1) { - $end = Carbon::createFromFormat('m/d/Y h:i A', $record['End Date'].' '.$record['End Time'], $timezone); - } else { - $end = Carbon::createFromFormat('m/d/Y H:i:s A', $record['End Date'].' '.$record['End Time'], $timezone); + $endDateStr = $record['End Date']; + $endTimeStr = $record['End Time']; + $endStr = $endDateStr.' '.$endTimeStr; + $matches = []; + $checkResult = preg_match('/^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{4}) ([0-9]{1,2}):([0-9]{1,2})(:[0-9]{1,2})? (AM|PM)$/', $endStr, $matches); + + if ($checkResult === 1) { + if ((int) $matches[1] > 12) { + throw new ImportException('Start date ("'.$endDateStr.'") is invalid, please select the correct date format before exporting from Clockify'); + } + if ($matches[6] === '') { + $end = Carbon::createFromFormat('m/d/Y h:i A', $endStr, $timezone); + } else { + $end = Carbon::createFromFormat('m/d/Y H:i:s A', $endStr, $timezone); + } } } catch (InvalidFormatException) { - throw new ImportException('End date ("'.$record['End Date'].'") or time ("'.$record['End Time'].'") are invalid'); + throw new ImportException('End date ("'.$endDateStr.'") or time ("'.$endTimeStr.'") are invalid'); } if ($end === null) { - throw new ImportException('End date ("'.$record['End Date'].'") or time ("'.$record['End Time'].'") are invalid'); + throw new ImportException('End date ("'.$endDateStr.'") or time ("'.$endTimeStr.'") are invalid'); } $timeEntry->end = $end->utc(); + $timeEntry->billable_rate = $this->billableRateService->getBillableRateForTimeEntryWithGivenRelations( $timeEntry, $projectMember, diff --git a/resources/testfiles/clockify_time_entries_import_test_3.csv b/resources/testfiles/clockify_time_entries_import_test_3.csv new file mode 100644 index 00000000..92b64e3d --- /dev/null +++ b/resources/testfiles/clockify_time_entries_import_test_3.csv @@ -0,0 +1,2 @@ +"Project","Client","Description","Task","User","Group","Email","Tags","Type","Billable","Invoiced","Invoice ID","Start Date","Start Time","End Date","End Time","Duration (h)","Duration (decimal)","Billable Rate (EUR)","Billable Amount (EUR)","Date of creation" +"Real World Project","Real World Client","\\ 🔥 Special characters ''''''`!@#$%^&*()_+\-=\[\]{};':''\\|,.''<>\/?~ \\\","A giant task","Peter Tester","Group1, Group2","peter.test@email.test","","Regular","Yes","Yes","Invoice100","13/15/2024","11:00:00 AM","10/15/2024","11:30:00 AM","00:30:00","0.50","1000.00","500.00","10/15/2024" diff --git a/tests/Unit/Service/Import/Importers/ClockifyTimeEntriesImporterTest.php b/tests/Unit/Service/Import/Importers/ClockifyTimeEntriesImporterTest.php index 91adadd3..eb6ca5e0 100644 --- a/tests/Unit/Service/Import/Importers/ClockifyTimeEntriesImporterTest.php +++ b/tests/Unit/Service/Import/Importers/ClockifyTimeEntriesImporterTest.php @@ -94,4 +94,25 @@ class ClockifyTimeEntriesImporterTest extends ImporterTestAbstract $this->assertSame(0, $report->projectsCreated); $this->assertSame(0, $report->clientsCreated); } + + public function test_import_fails_if_month_in_date_is_bigger_than_12(): void + { + // Arrange + $organization = Organization::factory()->create(); + $timezone = 'Europe/Vienna'; + $importer = new ClockifyTimeEntriesImporter; + $importer->init($organization); + $data = Storage::disk('testfiles')->get('clockify_time_entries_import_test_3.csv'); + + // Act + try { + $importer->importData($data, $timezone); + } catch (ImportException $e) { + // Assert + $this->assertSame('Start date ("13/15/2024") is invalid, please select the correct date format before exporting from Clockify', $e->getMessage()); + + return; + } + $this->fail(); + } }