Skip to content

Commit 899a235

Browse files
hans-thomasGromNaN
andauthored
Datetime casting with custom format (#2658)
As mentioned in the #2655 issue, casting date types with a custom format did not work properly. So I decided to cover all date types and fix this issue. I tested these date types: - immutable_date - immutable_date with custom formatting - immutable_datetime - immutable_datetime with custom formatting To achieve this goal, I override one of the cases in castAttribute method to handle immutable_date date type. Also, I override transformModelValue method to check if the result is a DateTimeInterface or not for applying defined custom formatting on the Carbon instance and resetting time in a custom date type. In the end, I should say that all date values will be stored in DB as Date type. (not as an object or string anymore) --------- Co-authored-by: Jérôme Tamarelle <jerome@tamarelle.net>
1 parent bfbe055 commit 899a235

File tree

4 files changed

+175
-3
lines changed

4 files changed

+175
-3
lines changed

src/Eloquent/Model.php

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Brick\Math\BigDecimal;
88
use Brick\Math\Exception\MathException as BrickMathException;
99
use Brick\Math\RoundingMode;
10+
use Carbon\CarbonInterface;
1011
use DateTimeInterface;
1112
use Illuminate\Contracts\Queue\QueueableCollection;
1213
use Illuminate\Contracts\Queue\QueueableEntity;
@@ -27,6 +28,7 @@
2728
use function abs;
2829
use function array_key_exists;
2930
use function array_keys;
31+
use function array_merge;
3032
use function array_unique;
3133
use function array_values;
3234
use function class_basename;
@@ -41,6 +43,7 @@
4143
use function method_exists;
4244
use function sprintf;
4345
use function str_contains;
46+
use function str_starts_with;
4447
use function strcmp;
4548
use function uniqid;
4649

@@ -199,6 +202,36 @@ public function getAttribute($key)
199202
return parent::getAttribute($key);
200203
}
201204

205+
/** @inheritdoc */
206+
protected function transformModelValue($key, $value)
207+
{
208+
$value = parent::transformModelValue($key, $value);
209+
// Casting attributes to any of date types, will convert that attribute
210+
// to a Carbon or CarbonImmutable instance.
211+
// @see Model::setAttribute()
212+
if ($this->hasCast($key) && $value instanceof CarbonInterface) {
213+
$value->settings(array_merge($value->getSettings(), ['toStringFormat' => $this->getDateFormat()]));
214+
215+
$castType = $this->getCasts()[$key];
216+
if ($this->isCustomDateTimeCast($castType) && str_starts_with($castType, 'date:')) {
217+
$value->startOfDay();
218+
}
219+
}
220+
221+
return $value;
222+
}
223+
224+
/** @inheritdoc */
225+
protected function getCastType($key)
226+
{
227+
$castType = $this->getCasts()[$key];
228+
if ($this->isCustomDateTimeCast($castType) || $this->isImmutableCustomDateTimeCast($castType)) {
229+
$this->setDateFormat(Str::after($castType, ':'));
230+
}
231+
232+
return parent::getCastType($key);
233+
}
234+
202235
/** @inheritdoc */
203236
protected function getAttributeFromArray($key)
204237
{
@@ -217,7 +250,7 @@ public function setAttribute($key, $value)
217250
{
218251
$key = (string) $key;
219252

220-
//Add casts
253+
// Add casts
221254
if ($this->hasCast($key)) {
222255
$value = $this->castAttribute($key, $value);
223256
}
@@ -270,6 +303,19 @@ public function fromJson($value, $asObject = false)
270303
return Json::decode($value ?? '', ! $asObject);
271304
}
272305

306+
/** @inheritdoc */
307+
protected function castAttribute($key, $value)
308+
{
309+
$castType = $this->getCastType($key);
310+
311+
return match ($castType) {
312+
'immutable_custom_datetime','immutable_datetime' => str_starts_with($this->getCasts()[$key], 'immutable_date:') ?
313+
$this->asDate($value)->toImmutable() :
314+
$this->asDateTime($value)->toImmutable(),
315+
default => parent::castAttribute($key, $value)
316+
};
317+
}
318+
273319
/** @inheritdoc */
274320
public function attributesToArray()
275321
{

tests/Casts/DateTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace MongoDB\Laravel\Tests\Casts;
66

7+
use Carbon\CarbonImmutable;
78
use DateTime;
89
use Illuminate\Support\Carbon;
910
use MongoDB\Laravel\Tests\Models\Casting;
@@ -61,4 +62,59 @@ public function testDateAsString(): void
6162
(string) $model->dateField,
6263
);
6364
}
65+
66+
public function testDateWithCustomFormat(): void
67+
{
68+
$model = Casting::query()->create(['dateWithFormatField' => new DateTime()]);
69+
70+
self::assertInstanceOf(Carbon::class, $model->dateWithFormatField);
71+
self::assertEquals(now()->startOfDay()->format('j.n.Y H:i'), (string) $model->dateWithFormatField);
72+
73+
$model->update(['dateWithFormatField' => now()->subDay()]);
74+
75+
self::assertInstanceOf(Carbon::class, $model->dateWithFormatField);
76+
self::assertEquals(now()->startOfDay()->subDay()->format('j.n.Y H:i'), (string) $model->dateWithFormatField);
77+
}
78+
79+
public function testImmutableDate(): void
80+
{
81+
$model = Casting::query()->create(['immutableDateField' => new DateTime()]);
82+
83+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateField);
84+
self::assertEquals(now()->startOfDay()->format('Y-m-d H:i:s'), (string) $model->immutableDateField);
85+
86+
$model->update(['immutableDateField' => now()->subDay()]);
87+
88+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateField);
89+
self::assertEquals(now()->startOfDay()->subDay()->format('Y-m-d H:i:s'), (string) $model->immutableDateField);
90+
91+
$model->update(['immutableDateField' => '2023-10-28']);
92+
93+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateField);
94+
self::assertEquals(
95+
Carbon::createFromTimestamp(1698577443)->subDay()->startOfDay()->format('Y-m-d H:i:s'),
96+
(string) $model->immutableDateField,
97+
);
98+
}
99+
100+
public function testImmutableDateWithCustomFormat(): void
101+
{
102+
$model = Casting::query()->create(['immutableDateWithFormatField' => new DateTime()]);
103+
104+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateWithFormatField);
105+
self::assertEquals(now()->startOfDay()->format('j.n.Y H:i'), (string) $model->immutableDateWithFormatField);
106+
107+
$model->update(['immutableDateWithFormatField' => now()->startOfDay()->subDay()]);
108+
109+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateWithFormatField);
110+
self::assertEquals(now()->startOfDay()->subDay()->format('j.n.Y H:i'), (string) $model->immutableDateWithFormatField);
111+
112+
$model->update(['immutableDateWithFormatField' => '2023-10-28']);
113+
114+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDateWithFormatField);
115+
self::assertEquals(
116+
Carbon::createFromTimestamp(1698577443)->subDay()->startOfDay()->format('j.n.Y H:i'),
117+
(string) $model->immutableDateWithFormatField,
118+
);
119+
}
64120
}

tests/Casts/DatetimeTest.php

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace MongoDB\Laravel\Tests\Casts;
66

7+
use Carbon\CarbonImmutable;
8+
use DateTime;
79
use Illuminate\Support\Carbon;
810
use MongoDB\Laravel\Tests\Models\Casting;
911
use MongoDB\Laravel\Tests\TestCase;
@@ -19,7 +21,7 @@ protected function setUp(): void
1921
Casting::truncate();
2022
}
2123

22-
public function testDate(): void
24+
public function testDatetime(): void
2325
{
2426
$model = Casting::query()->create(['datetimeField' => now()]);
2527

@@ -32,7 +34,7 @@ public function testDate(): void
3234
self::assertEquals(now()->subDay()->format('Y-m-d H:i:s'), (string) $model->datetimeField);
3335
}
3436

35-
public function testDateAsString(): void
37+
public function testDatetimeAsString(): void
3638
{
3739
$model = Casting::query()->create(['datetimeField' => '2023-10-29']);
3840

@@ -50,4 +52,59 @@ public function testDateAsString(): void
5052
(string) $model->datetimeField,
5153
);
5254
}
55+
56+
public function testDatetimeWithCustomFormat(): void
57+
{
58+
$model = Casting::query()->create(['datetimeWithFormatField' => new DateTime()]);
59+
60+
self::assertInstanceOf(Carbon::class, $model->datetimeWithFormatField);
61+
self::assertEquals(now()->format('j.n.Y H:i'), (string) $model->datetimeWithFormatField);
62+
63+
$model->update(['datetimeWithFormatField' => now()->subDay()]);
64+
65+
self::assertInstanceOf(Carbon::class, $model->datetimeWithFormatField);
66+
self::assertEquals(now()->subDay()->format('j.n.Y H:i'), (string) $model->datetimeWithFormatField);
67+
}
68+
69+
public function testImmutableDatetime(): void
70+
{
71+
$model = Casting::query()->create(['immutableDatetimeField' => new DateTime()]);
72+
73+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeField);
74+
self::assertEquals(now()->format('Y-m-d H:i:s'), (string) $model->immutableDatetimeField);
75+
76+
$model->update(['immutableDatetimeField' => now()->subDay()]);
77+
78+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeField);
79+
self::assertEquals(now()->subDay()->format('Y-m-d H:i:s'), (string) $model->immutableDatetimeField);
80+
81+
$model->update(['immutableDatetimeField' => '2023-10-28 11:04:03']);
82+
83+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeField);
84+
self::assertEquals(
85+
Carbon::createFromTimestamp(1698577443)->subDay()->format('Y-m-d H:i:s'),
86+
(string) $model->immutableDatetimeField,
87+
);
88+
}
89+
90+
public function testImmutableDatetimeWithCustomFormat(): void
91+
{
92+
$model = Casting::query()->create(['immutableDatetimeWithFormatField' => new DateTime()]);
93+
94+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeWithFormatField);
95+
self::assertEquals(now()->format('j.n.Y H:i'), (string) $model->immutableDatetimeWithFormatField);
96+
97+
$model->update(['immutableDatetimeWithFormatField' => now()->subDay()]);
98+
99+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeWithFormatField);
100+
self::assertEquals(now()->subDay()->format('j.n.Y H:i'), (string) $model->immutableDatetimeWithFormatField);
101+
102+
$model->update(['immutableDatetimeWithFormatField' => '2023-10-28 11:04:03']);
103+
104+
self::assertInstanceOf(CarbonImmutable::class, $model->immutableDatetimeWithFormatField);
105+
self::assertEquals(
106+
Carbon::createFromTimestamp(1698577443)->subDay()->format('j.n.Y H:i'),
107+
(string) $model->immutableDatetimeWithFormatField,
108+
);
109+
}
53110
}

tests/Models/Casting.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@ class Casting extends Eloquent
2424
'jsonValue',
2525
'collectionValue',
2626
'dateField',
27+
'dateWithFormatField',
28+
'immutableDateField',
29+
'immutableDateWithFormatField',
2730
'datetimeField',
31+
'dateWithFormatField',
32+
'datetimeWithFormatField',
33+
'immutableDatetimeField',
34+
'immutableDatetimeWithFormatField',
2835
];
2936

3037
protected $casts = [
@@ -38,6 +45,12 @@ class Casting extends Eloquent
3845
'jsonValue' => 'json',
3946
'collectionValue' => 'collection',
4047
'dateField' => 'date',
48+
'dateWithFormatField' => 'date:j.n.Y H:i',
49+
'immutableDateField' => 'immutable_date',
50+
'immutableDateWithFormatField' => 'immutable_date:j.n.Y H:i',
4151
'datetimeField' => 'datetime',
52+
'datetimeWithFormatField' => 'datetime:j.n.Y H:i',
53+
'immutableDatetimeField' => 'immutable_datetime',
54+
'immutableDatetimeWithFormatField' => 'immutable_datetime:j.n.Y H:i',
4255
];
4356
}

0 commit comments

Comments
 (0)