Skip to content

Commit 558ae1c

Browse files
committed
[11.x] Introduce whereDateDiff methods in query builder
1 parent d2895c7 commit 558ae1c

File tree

6 files changed

+180
-0
lines changed

6 files changed

+180
-0
lines changed

src/Illuminate/Database/Query/Builder.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
use Illuminate\Database\Query\Processors\Processor;
2020
use Illuminate\Pagination\Paginator;
2121
use Illuminate\Support\Arr;
22+
use Illuminate\Support\Carbon;
2223
use Illuminate\Support\Collection;
24+
use Illuminate\Support\Facades\Validator;
2325
use Illuminate\Support\LazyCollection;
2426
use Illuminate\Support\Str;
2527
use Illuminate\Support\Traits\ForwardsCalls;
@@ -1773,6 +1775,65 @@ protected function addDateBasedWhere($type, $column, $operator, $value, $boolean
17731775
return $this;
17741776
}
17751777

1778+
/**
1779+
* Add an "where DateDiff" statement to the query.
1780+
*
1781+
* @param \DateTimeInterface|string $column1
1782+
* @param \DateTimeInterface|string $column2
1783+
* @param string $operator
1784+
* @param string $value
1785+
* @param string $unit One of 'year', 'month', 'week', 'day', 'hour', 'minute', or 'day'
1786+
* @return $this
1787+
*/
1788+
public function whereDateDiff($column1, $column2, $operator, $value, $unit = 'day', $boolean = 'and')
1789+
{
1790+
$type = 'DateDiff';
1791+
1792+
$column1 = $this->normalizeDateDiffArgument($column1);
1793+
$column2 = $this->normalizeDateDiffArgument($column2);
1794+
1795+
$this->wheres[] = compact('type', 'column1', 'column2', 'operator', 'value', 'boolean', 'unit');
1796+
1797+
$this->addBinding($value, 'where');
1798+
return $this;
1799+
}
1800+
1801+
/**
1802+
* Add an "or where DateDiff" statement to the query.
1803+
*
1804+
* @param \DateTimeInterface|string $column1
1805+
* @param \DateTimeInterface|string $column2
1806+
* @param string $operator
1807+
* @param string $value
1808+
* @param string $unit One of 'year', 'month', 'week', 'day', 'hour', 'minute', or 'day'
1809+
* @return $this
1810+
*/
1811+
public function orWhereDateDiff($column1, $column2, $operator, $value, $unit = 'day')
1812+
{
1813+
return $this->whereDateDiff($column1, $column2, $operator, $value, $unit, 'or');
1814+
}
1815+
1816+
/**
1817+
* Normalize the argument for a DateDiff query.
1818+
*
1819+
* @param \DateTimeInterface|string $column
1820+
* @return \Illuminate\Database\Query\Expression|string
1821+
*/
1822+
protected function normalizeDateDiffArgument($column)
1823+
{
1824+
if ($column instanceof DateTimeInterface) {
1825+
return new Expression("'".$column->toDateTimeString()."'");
1826+
}
1827+
1828+
if (is_string($column)) {
1829+
if (Validator::make(['date' => $column], ['date' => 'date'])->passes()) {
1830+
return new Expression("'".Carbon::parse($column)->toDateTimeString()."'");
1831+
}
1832+
}
1833+
1834+
return $column;
1835+
}
1836+
17761837
/**
17771838
* Add a nested where statement to the query.
17781839
*

src/Illuminate/Database/Query/Grammars/Grammar.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Illuminate\Database\Query\JoinClause;
1010
use Illuminate\Database\Query\JoinLateralClause;
1111
use Illuminate\Support\Arr;
12+
use InvalidArgumentException;
1213
use RuntimeException;
1314

1415
class Grammar extends BaseGrammar
@@ -541,6 +542,34 @@ protected function whereColumn(Builder $query, $where)
541542
return $this->wrap($where['first']).' '.$where['operator'].' '.$this->wrap($where['second']);
542543
}
543544

545+
/**
546+
* Compile a "where DateDiff" clause.
547+
*
548+
* @param \Illuminate\Database\Query\Builder $query
549+
* @param array $where
550+
* @return string
551+
*/
552+
protected function whereDateDiff(Builder $query, $where)
553+
{
554+
555+
$column1 = $this->wrap($where['column1']);
556+
$column2 = $this->wrap($where['column2']);
557+
$unit = strtolower($where['unit']);
558+
559+
$sql = match ($unit) {
560+
'year' => "TIMESTAMPDIFF(YEAR, {$column2}, {$column1})",
561+
'month' => "TIMESTAMPDIFF(MONTH, {$column2}, {$column1})",
562+
'week' => "FLOOR(DATEDIFF({$column1}, {$column2}) / 7)",
563+
'day' => "DATEDIFF({$column1}, {$column2})",
564+
'hour' => "TIMESTAMPDIFF(HOUR, {$column2}, {$column1})",
565+
'minute' => "TIMESTAMPDIFF(MINUTE, {$column2}, {$column1})",
566+
'second' => "TIMESTAMPDIFF(SECOND, {$column2}, {$column1})",
567+
default => throw new InvalidArgumentException("Unsupported date difference unit: {$unit}")
568+
};
569+
570+
return $sql . ' ' . $where['operator'] . ' ?';
571+
}
572+
544573
/**
545574
* Compile a nested where clause.
546575
*

src/Illuminate/Database/Query/Grammars/PostgresGrammar.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Database\Query\JoinLateralClause;
77
use Illuminate\Support\Arr;
88
use Illuminate\Support\Str;
9+
use InvalidArgumentException;
910

1011
class PostgresGrammar extends Grammar
1112
{
@@ -127,6 +128,33 @@ protected function dateBasedWhere($type, Builder $query, $where)
127128
return 'extract('.$type.' from '.$this->wrap($where['column']).') '.$where['operator'].' '.$value;
128129
}
129130

131+
po /**
132+
* Compile a "where DateDiff" clause.
133+
*
134+
* @param \Illuminate\Database\Query\Builder $query
135+
* @param array $where
136+
* @return string
137+
*/
138+
protected function whereDateDiff(Builder $query, $where)
139+
{
140+
$column1 = $this->wrap($where['column1']);
141+
$column2 = $this->wrap($where['column2']);
142+
$unit = strtolower($where['unit']);
143+
144+
$sql = match ($unit) {
145+
'year' => "DATE_PART('year', AGE({$column1}, {$column2}))",
146+
'month' => "(DATE_PART('year', AGE({$column1}, {$column2})) * 12 + DATE_PART('month', AGE({$column1}, {$column2})))",
147+
'week' => "FLOOR(DATE_PART('day', {$column1}::timestamp - {$column2}::timestamp) / 7)",
148+
'day' => "DATE_PART('day', {$column1}::timestamp - {$column2}::timestamp)",
149+
'hour' => "FLOOR(EXTRACT(EPOCH FROM ({$column1}::timestamp - {$column2}::timestamp)) / 3600)",
150+
'minute' => "FLOOR(EXTRACT(EPOCH FROM ({$column1}::timestamp - {$column2}::timestamp)) / 60)",
151+
'second' => "EXTRACT(EPOCH FROM ({$column1}::timestamp - {$column2}::timestamp))",
152+
default => throw new InvalidArgumentException("Unsupported date difference unit: {$unit}")
153+
};
154+
155+
return $sql . ' ' . $where['operator'] . ' ?';
156+
}
157+
130158
/**
131159
* Compile a "where fulltext" clause.
132160
*

src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Database\Query\Builder;
66
use Illuminate\Support\Arr;
77
use Illuminate\Support\Str;
8+
use InvalidArgumentException;
89

910
class SQLiteGrammar extends Grammar
1011
{
@@ -150,6 +151,34 @@ protected function dateBasedWhere($type, Builder $query, $where)
150151
return "strftime('{$type}', {$this->wrap($where['column'])}) {$where['operator']} cast({$value} as text)";
151152
}
152153

154+
/**
155+
* Compile a "where DateDiff" clause.
156+
*
157+
* @param \Illuminate\Database\Query\Builder $query
158+
* @param array $where
159+
* @return string
160+
*/
161+
protected function whereDateDiff(Builder $query, $where)
162+
{
163+
$column1 = $this->wrap($where['column1']);
164+
$column2 = $this->wrap($where['column2']);
165+
$unit = strtolower($where['unit']);
166+
167+
$sql = match ($unit) {
168+
'year' => "(CAST(strftime('%Y', {$column1}) AS INTEGER) - CAST(strftime('%Y', {$column2}) AS INTEGER))",
169+
'month' => "((CAST(strftime('%Y', {$column1}) AS INTEGER) - CAST(strftime('%Y', {$column2}) AS INTEGER)) * 12 +
170+
(CAST(strftime('%m', {$column1}) AS INTEGER) - CAST(strftime('%m', {$column2}) AS INTEGER)))",
171+
'week' => "CAST((julianday({$column1}) - julianday({$column2})) / 7 AS INTEGER)",
172+
'day' => "CAST(julianday({$column1}) - julianday({$column2}) AS INTEGER)",
173+
'hour' => "CAST((julianday({$column1}) - julianday({$column2})) * 24 AS INTEGER)",
174+
'minute' => "CAST((julianday({$column1}) - julianday({$column2})) * 1440 AS INTEGER)",
175+
'second' => "CAST((julianday({$column1}) - julianday({$column2})) * 86400 AS INTEGER)",
176+
default => throw new InvalidArgumentException("Unsupported date difference unit: {$unit}")
177+
};
178+
179+
return $sql . ' ' . $where['operator'] . ' ?';
180+
}
181+
153182
/**
154183
* Compile the index hints for the query.
155184
*

src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,20 @@ protected function whereTime(Builder $query, $where)
161161
return 'cast('.$this->wrap($where['column']).' as time) '.$where['operator'].' '.$value;
162162
}
163163

164+
/**
165+
* Compile a "where DateDiff" clause.
166+
*
167+
* @param \Illuminate\Database\Query\Builder $query
168+
* @param array $where
169+
* @return string
170+
*/
171+
protected function whereDateDiff(Builder $query, $where)
172+
{
173+
$unit = strtolower($where['unit']);
174+
175+
return "DATEDIFF({$unit}, {$this->wrap($where['column2'])}, {$this->wrap($where['column1'])}) {$where['operator']} ?";
176+
}
177+
164178
/**
165179
* Compile a "JSON contains" statement into SQL.
166180
*

tests/Integration/Database/QueryBuilderTest.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,25 @@ public function testWhereDayWithInvalidOperator()
377377
$this->assertSame(0, $sql->count());
378378
}
379379

380+
public function testWhereDateDiff()
381+
{
382+
$this->assertSame(1, DB::table('posts')->whereId(1)->whereDateDiff('posts.created_at', '2017-11-12 13:14:15', '=', 0)->count());
383+
$this->assertSame(1, DB::table('posts')->whereId(1)->whereDateDiff('created_at', '2017-11-12 13:14:15', '=', 0)->count());
384+
$this->assertSame(1, DB::table('posts')->whereId(1)->whereDateDiff('Dec 31 2023', 'Dec 31 2023', '=', 0)->count());
385+
$this->assertSame(1, DB::table('posts')->whereId(1)->whereDateDiff('2017-11-12 13:14:15', 'created_at', '=', 0)->count());
386+
$this->assertSame(1, DB::table('posts')->whereId(1)->whereDateDiff('2017-11-12 13:14:15', '2017-11-12 13:14:15', '=', 0)->count());
387+
$this->assertSame(1, DB::table('posts')->whereId(1)->whereDateDiff('Dec 01 2025', '2022-11-12 13:14:15', '=', 3, 'year')->count());
388+
}
389+
390+
391+
public function testOrWhereDateDiff()
392+
{
393+
$this->assertSame(1, DB::table('posts')->whereId(1)->orWhereDateDiff('created_at', '2017-11-12 13:14:15', '=', 0)->count());
394+
$this->assertSame(1, DB::table('posts')->whereId(1)->orWhereDateDiff('2017-11-12 13:14:15', 'created_at', '=', 0)->count());
395+
$this->assertSame(2, DB::table('posts')->whereId(1)->orWhereDateDiff('2017-11-12 13:14:15', '2017-11-12 13:14:15', '=', 0)->count());
396+
$this->assertSame(2, DB::table('posts')->whereId(1)->orWhereDateDiff('Dec 01 2025', '2022-11-12 13:14:15', '=', 3, 'year')->count());
397+
}
398+
380399
public function testOrWhereDay()
381400
{
382401
$this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereDay('created_at', '02')->count());

0 commit comments

Comments
 (0)