Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion src/Migration/Destinations/Appwrite.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Appwrite\Services\Users;
use Override;
use Utopia\Database\Database as UtopiaDatabase;
use Utopia\Database\DateTime;
use Utopia\Database\Document as UtopiaDocument;
use Utopia\Database\Exception as DatabaseException;
use Utopia\Database\Exception\Authorization as AuthorizationException;
Expand Down Expand Up @@ -942,10 +943,37 @@ protected function createRow(Row $resource, bool $isLast): bool
return false;
}

$data = $resource->getData();

$hasCreatedAt = !empty($data['$createdAt']);
$hasUpdatedAt = !empty($data['$updatedAt']);

if (! $hasCreatedAt) {
$createdAt = $resource->getCreatedAt();
if (empty($createdAt)) {
$createdAt = $data['created_at'] ?? $data['createdAt'] ?? null;
}

$data['$createdAt'] = $this->normalizeDateTime($createdAt);
}

if (! $hasUpdatedAt) {
$updatedAt = $resource->getUpdatedAt();
if (empty($updatedAt)) {
$updatedAt = $data['updated_at'] ?? $data['updatedAt'] ?? null;
}

if (empty($updatedAt)) {
$data['$updatedAt'] = (string) $data['$createdAt'];
} else {
$data['$updatedAt'] = $this->normalizeDateTime($updatedAt, $data['$createdAt']);
}
}

$this->rowBuffer[] = new UtopiaDocument(\array_merge([
'$id' => $resource->getId(),
'$permissions' => $resource->getPermissions(),
], $resource->getData()));
], $data));

if ($isLast) {
try {
Expand Down Expand Up @@ -1001,6 +1029,37 @@ protected function createRow(Row $resource, bool $isLast): bool
return true;
}

/**
* @throws \Exception
*/
private function normalizeDateTime(mixed $value, mixed $fallback = null): string
{
$resolved = $this->stringifyDateValue($value)
?? $this->stringifyDateValue($fallback);

if (empty($resolved)) {
return DateTime::format(new \DateTime());
}

if (\is_numeric($resolved) && \strlen($resolved) === 10 && (int) $resolved > 0) { // Unix timestamp
$resolved = '@' . $resolved;
}

return DateTime::format(new \DateTime($resolved));
Comment on lines +1044 to +1048
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unix timestamp detection has edge case limitations.

The 10-character length check (\strlen($resolved) === 10) only handles timestamps from ~Sep 2001 onwards. Timestamps for dates before Sep 9, 2001 (e.g., 946684800 for Jan 1, 2000) are 9 digits and would be passed to new \DateTime() without the @ prefix, likely causing parse failures.

If migrating data from older systems is a concern, consider a more robust check:

Suggested improvement
-        if (\is_numeric($resolved) && \strlen($resolved) === 10 && (int) $resolved > 0) { // Unix timestamp
+        // Unix timestamps: 9-10 digits for years 1970-2286
+        if (\is_numeric($resolved) && \preg_match('/^\d{9,10}$/', $resolved)) {
             $resolved = '@' . $resolved;
         }

Additionally, some source systems (e.g., JavaScript) may provide millisecond timestamps (13 digits). If that's a potential source, you might also want to handle those by dividing by 1000.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (\is_numeric($resolved) && \strlen($resolved) === 10 && (int) $resolved > 0) { // Unix timestamp
$resolved = '@' . $resolved;
}
return DateTime::format(new \DateTime($resolved));
if (\is_numeric($resolved) && \preg_match('/^\d{9,10}$/', $resolved)) {
$resolved = '@' . $resolved;
}
return DateTime::format(new \DateTime($resolved));
🤖 Prompt for AI Agents
In @src/Migration/Destinations/Appwrite.php around lines 1044 - 1048, The
current Unix timestamp detection around $resolved uses a strict 10-character
check and misses 9-digit (pre-2001) timestamps and 13-digit millisecond
timestamps; update the logic in the method that returns DateTime::format(new
\DateTime($resolved)) to: detect numeric values (ctype_digit or is_numeric),
normalize 13-digit millisecond timestamps by dividing by 1000, treat any numeric
value within a reasonable Unix seconds range (e.g., between -2147483648 and
~4102444800) as a seconds timestamp and prefix with '@' before calling new
\DateTime($resolved), and otherwise fall back to the existing parse path so
older 9-digit timestamps and millisecond inputs are handled correctly.

}

private function stringifyDateValue(mixed $value): ?string
{
if (\is_string($value)) {
return $value;
}
if (\is_int($value) || \is_float($value)) {
return (string) $value;
}

return null;
}

/**
* @throws AppwriteException
*/
Expand Down