Skip to content

PHPORM-234 Convert dates in DB Query results #3119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
* Remove support for Laravel 10 by @GromNaN in [#3123](https://github.com/mongodb/laravel-mongodb/pull/3123)
* **BREAKING CHANGE** Use `id` as an alias for `_id` in commands and queries for compatibility with Eloquent packages by @GromNaN in [#3040](https://github.com/mongodb/laravel-mongodb/pull/3040)
* **BREAKING CHANGE** Make Query\Builder return objects instead of array to match Laravel behavior by @GromNaN in [#3107](https://github.com/mongodb/laravel-mongodb/pull/3107)
* **BREAKING CHANGE** In DB query results, convert BSON `UTCDateTime` objects into `Carbon` date with the default timezone by @GromNaN in [#3119](https://github.com/mongodb/laravel-mongodb/pull/3119)
* Remove `MongoFailedJobProvider`, replaced by Laravel `DatabaseFailedJobProvider` by @GromNaN in [#3122](https://github.com/mongodb/laravel-mongodb/pull/3122)

## [4.8.0] - 2024-08-27
Expand Down
22 changes: 18 additions & 4 deletions src/Eloquent/DocumentModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
namespace MongoDB\Laravel\Eloquent;

use BackedEnum;
use Carbon\Carbon;
use Carbon\CarbonInterface;
use DateTimeInterface;
use DateTimeZone;
use Illuminate\Contracts\Queue\QueueableCollection;
use Illuminate\Contracts\Queue\QueueableEntity;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Concerns\HasAttributes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Arr;
Expand Down Expand Up @@ -97,8 +99,14 @@ public function getQualifiedKeyName()
return $this->getKeyName();
}

/** @inheritdoc */
public function fromDateTime($value)
/**
* Convert a DateTimeInterface (including Carbon) to a storable UTCDateTime.
*
* @see HasAttributes::fromDateTime()
*
* @param mixed $value
*/
public function fromDateTime($value): UTCDateTime
{
// If the value is already a UTCDateTime instance, we don't need to parse it.
if ($value instanceof UTCDateTime) {
Expand All @@ -113,8 +121,14 @@ public function fromDateTime($value)
return new UTCDateTime($value);
}

/** @inheritdoc */
protected function asDateTime($value)
/**
* Return a timestamp as Carbon object.
*
* @see HasAttributes::asDateTime()
*
* @param mixed $value
*/
protected function asDateTime($value): Carbon
{
// Convert UTCDateTime instances to Carbon.
if ($value instanceof UTCDateTime) {
Expand Down
13 changes: 11 additions & 2 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
use Carbon\CarbonPeriod;
use Closure;
use DateTimeInterface;
use DateTimeZone;
use Illuminate\Database\Query\Builder as BaseBuilder;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\LazyCollection;
use InvalidArgumentException;
use LogicException;
Expand All @@ -39,6 +41,7 @@
use function call_user_func_array;
use function count;
use function ctype_xdigit;
use function date_default_timezone_get;
use function dd;
use function dump;
use function end;
Expand Down Expand Up @@ -1660,7 +1663,10 @@ private function aliasIdForResult(array|object $values): array|object
}

foreach ($values as $key => $value) {
if (is_array($value) || is_object($value)) {
if ($value instanceof UTCDateTime) {
$values[$key] = Date::instance($value->toDateTime())
->setTimezone(new DateTimeZone(date_default_timezone_get()));
Copy link
Member

Choose a reason for hiding this comment

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

I'll note that ODM also applies the default timezone in its DateType class.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good to know, thanks for the investigation.

} elseif (is_array($value) || is_object($value)) {
$values[$key] = $this->aliasIdForResult($value);
}
}
Expand All @@ -1673,7 +1679,10 @@ private function aliasIdForResult(array|object $values): array|object
}

foreach (get_object_vars($values) as $key => $value) {
if (is_array($value) || is_object($value)) {
if ($value instanceof UTCDateTime) {
$values->{$key} = Date::instance($value->toDateTime())
->setTimezone(new DateTimeZone(date_default_timezone_get()));
} elseif (is_array($value) || is_object($value)) {
$values->{$key} = $this->aliasIdForResult($value);
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/AuthTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

namespace MongoDB\Laravel\Tests;

use Carbon\Carbon;
use Illuminate\Auth\Passwords\PasswordBroker;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Laravel\Tests\Models\User;

use function bcrypt;
Expand Down Expand Up @@ -63,7 +63,7 @@ function ($actualUser, $actualToken) use ($user, &$token) {
$reminder = DB::table('password_reset_tokens')->first();
$this->assertEquals('[email protected]', $reminder->email);
$this->assertNotNull($reminder->token);
$this->assertInstanceOf(UTCDateTime::class, $reminder->created_at);
$this->assertInstanceOf(Carbon::class, $reminder->created_at);

$credentials = [
'email' => '[email protected]',
Expand Down
23 changes: 14 additions & 9 deletions tests/QueryBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace MongoDB\Laravel\Tests;

use Carbon\Carbon;
use DateTime;
use DateTimeImmutable;
use Illuminate\Support\Facades\Date;
Expand Down Expand Up @@ -33,7 +34,6 @@
use function md5;
use function sort;
use function strlen;
use function strtotime;

class QueryBuilderTest extends TestCase
{
Expand Down Expand Up @@ -676,27 +676,32 @@ public function testUpdateSubdocument()
public function testDates()
{
DB::table('users')->insert([
['name' => 'John Doe', 'birthday' => new UTCDateTime(Date::parse('1980-01-01 00:00:00'))],
['name' => 'Robert Roe', 'birthday' => new UTCDateTime(Date::parse('1982-01-01 00:00:00'))],
['name' => 'Mark Moe', 'birthday' => new UTCDateTime(Date::parse('1983-01-01 00:00:00.1'))],
['name' => 'Frank White', 'birthday' => new UTCDateTime(Date::parse('1960-01-01 12:12:12.1'))],
['name' => 'John Doe', 'birthday' => Date::parse('1980-01-01 00:00:00')],
['name' => 'Robert Roe', 'birthday' => Date::parse('1982-01-01 00:00:00')],
['name' => 'Mark Moe', 'birthday' => Date::parse('1983-01-01 00:00:00.1')],
['name' => 'Frank White', 'birthday' => Date::parse('1975-01-01 12:12:12.1')],
Copy link
Member Author

Choose a reason for hiding this comment

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

Changed the date to avoid the issue reported in PHPC-2429

]);

$user = DB::table('users')
->where('birthday', new UTCDateTime(Date::parse('1980-01-01 00:00:00')))
->where('birthday', Date::parse('1980-01-01 00:00:00'))
Copy link
Member

Choose a reason for hiding this comment

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

Noted that Date::parse() returns a Carbon instance

->first();
$this->assertEquals('John Doe', $user->name);

$user = DB::table('users')
->where('birthday', new UTCDateTime(Date::parse('1960-01-01 12:12:12.1')))
->where('birthday', Date::parse('1975-01-01 12:12:12.1'))
->first();

$this->assertEquals('Frank White', $user->name);
$this->assertInstanceOf(Carbon::class, $user->birthday);
$this->assertSame('1975-01-01 12:12:12.100000', $user->birthday->format('Y-m-d H:i:s.u'));

$user = DB::table('users')->where('birthday', '=', new DateTime('1980-01-01 00:00:00'))->first();
$this->assertEquals('John Doe', $user->name);
$this->assertInstanceOf(Carbon::class, $user->birthday);
$this->assertSame('1980-01-01 00:00:00.000000', $user->birthday->format('Y-m-d H:i:s.u'));

$start = new UTCDateTime(1000 * strtotime('1950-01-01 00:00:00'));
$stop = new UTCDateTime(1000 * strtotime('1981-01-01 00:00:00'));
$start = new UTCDateTime(new DateTime('1950-01-01 00:00:00'));
$stop = new UTCDateTime(new DateTime('1981-01-01 00:00:00'));

$users = DB::table('users')->whereBetween('birthday', [$start, $stop])->get();
$this->assertCount(2, $users);
Expand Down
3 changes: 1 addition & 2 deletions tests/QueueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Str;
use Mockery;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Laravel\Queue\MongoJob;
use MongoDB\Laravel\Queue\MongoQueue;

Expand Down Expand Up @@ -197,7 +196,7 @@ public function testFailedJobLogging()
$this->assertSame('test_connection', $failedJob->connection);
$this->assertSame('test_queue', $failedJob->queue);
$this->assertSame('test_payload', $failedJob->payload);
$this->assertEquals(new UTCDateTime(Carbon::now()), $failedJob->failed_at);
$this->assertEquals(Carbon::now(), $failedJob->failed_at);
$this->assertStringStartsWith('Exception: test_exception in ', $failedJob->exception);
}
}
Loading