diff --git a/CHANGELOG.md b/CHANGELOG.md index 297959410..162bdd010 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/Eloquent/DocumentModel.php b/src/Eloquent/DocumentModel.php index fbbc69e49..930ed6286 100644 --- a/src/Eloquent/DocumentModel.php +++ b/src/Eloquent/DocumentModel.php @@ -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; @@ -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) { @@ -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) { diff --git a/src/Query/Builder.php b/src/Query/Builder.php index 486325029..f4f31b58f 100644 --- a/src/Query/Builder.php +++ b/src/Query/Builder.php @@ -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; @@ -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; @@ -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())); + } elseif (is_array($value) || is_object($value)) { $values[$key] = $this->aliasIdForResult($value); } } @@ -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); } } diff --git a/tests/AuthTest.php b/tests/AuthTest.php index ffe3d46e9..98d42832e 100644 --- a/tests/AuthTest.php +++ b/tests/AuthTest.php @@ -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; @@ -63,7 +63,7 @@ function ($actualUser, $actualToken) use ($user, &$token) { $reminder = DB::table('password_reset_tokens')->first(); $this->assertEquals('john.doe@example.com', $reminder->email); $this->assertNotNull($reminder->token); - $this->assertInstanceOf(UTCDateTime::class, $reminder->created_at); + $this->assertInstanceOf(Carbon::class, $reminder->created_at); $credentials = [ 'email' => 'john.doe@example.com', diff --git a/tests/QueryBuilderTest.php b/tests/QueryBuilderTest.php index d34bb5241..846f48514 100644 --- a/tests/QueryBuilderTest.php +++ b/tests/QueryBuilderTest.php @@ -4,6 +4,7 @@ namespace MongoDB\Laravel\Tests; +use Carbon\Carbon; use DateTime; use DateTimeImmutable; use Illuminate\Support\Facades\Date; @@ -33,7 +34,6 @@ use function md5; use function sort; use function strlen; -use function strtotime; class QueryBuilderTest extends TestCase { @@ -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')], ]); $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')) ->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); diff --git a/tests/QueueTest.php b/tests/QueueTest.php index e149b9ef4..efc8f07ff 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -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; @@ -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); } }