Skip to content

Commit d1cef81

Browse files
authanramtpetry
andauthored
feat: add ExtractDatePart expression (tpetry#33)
Co-authored-by: tpetry <github@tpetry.me>
1 parent b5b91bd commit d1cef81

4 files changed

Lines changed: 83 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
### Added
10+
* `ExtractYear` time function to extract the year from date/datetime columns
11+
812
## [1.5.0] - 2025-02-14
913
### Added
1014
* Laravel 12 support

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,9 +360,11 @@ Schema::table('users', function (Blueprint $table): void {
360360
361361
#### Time
362362
```php
363+
use Tpetry\QueryExpressions\Function\Time\ExtractDatePart;
363364
use Tpetry\QueryExpressions\Function\Time\Now;
364365
use Tpetry\QueryExpressions\Function\Time\TimestampBin;
365366

367+
new ExtractDatePart(string|Expression $column, string $part);
366368
new Now();
367369
new TimestampBin(string|Expression $expression, DateInterval $step, ?DateTimeInterface $origin = null);
368370

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tpetry\QueryExpressions\Function\Time;
6+
7+
use Illuminate\Contracts\Database\Query\Expression;
8+
use Illuminate\Database\Grammar;
9+
use InvalidArgumentException;
10+
use Tpetry\QueryExpressions\Concerns\IdentifiesDriver;
11+
use Tpetry\QueryExpressions\Concerns\StringizeExpression;
12+
13+
class ExtractDatePart implements Expression
14+
{
15+
use IdentifiesDriver;
16+
use StringizeExpression;
17+
18+
/**
19+
* @param 'year' $part
20+
*/
21+
public function __construct(
22+
private readonly string|Expression $column,
23+
private readonly string $part,
24+
) {}
25+
26+
public function getValue(Grammar $grammar): string
27+
{
28+
$column = $this->stringize($grammar, $this->column);
29+
30+
return match ($this->part) {
31+
'year' => $this->extractYear($grammar, $column), // @phpstan-ignore-line match.alwaysTrue
32+
default => throw new InvalidArgumentException("Invalid date part: '{$this->part}'."),
33+
};
34+
}
35+
36+
private function extractYear(Grammar $grammar, string $column): string
37+
{
38+
return match ($this->identify($grammar)) {
39+
'mariadb', 'mysql', 'sqlsrv' => "(year({$column}))",
40+
'pgsql' => "extract(year from {$column})::int",
41+
'sqlite' => "cast(strftime('%Y', {$column}) as integer)",
42+
};
43+
}
44+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Database\Query\Expression;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Tpetry\QueryExpressions\Function\Time\ExtractDatePart;
8+
9+
test('fails for unimplemented date parts', function () {
10+
expect(function () {
11+
(new ExtractDatePart('val', 'day'))->getValue(DB::getQueryGrammar());
12+
})->toThrow("Invalid date part: 'day'.");
13+
});
14+
15+
it('can extract the year from a column')
16+
->expect(new ExtractDatePart('val', 'year'))
17+
->toBeExecutable(function (Blueprint $table) {
18+
$table->date('val');
19+
})
20+
->toBeMysql('(year(`val`))')
21+
->toBePgsql('extract(year from "val")::int')
22+
->toBeSqlite('cast(strftime(\'%Y\', "val") as integer)')
23+
->toBeSqlsrv('year([val])');
24+
25+
it('can extract the year from an expression')
26+
->expect(new ExtractDatePart(new Expression('current_date'), 'year'))
27+
->toBeExecutable(function (Blueprint $table) {
28+
$table->date('val');
29+
})
30+
->toBeMysql('(year(current_date))')
31+
->toBePgsql('extract(year from current_date)::int')
32+
->toBeSqlite('cast(strftime(\'%Y\', current_date) as integer)')
33+
->toBeSqlsrv('year(current_date)');

0 commit comments

Comments
 (0)