Laravel 模型中使正確取得兩個日期之間資料的方法

假設有一個部落格,想檢索兩個日期之間的所有貼文。聽起來很熟悉?那具體該怎麼做呢?

image

一般的做法

這看起來確實是一個微不足道的問題,答案也很簡單(但完全錯誤):只需使用 BETWEEN(或在 Laravel 中使用 whereBetween),如下所示:

$startDate = '2025-06-01';
$endDate = '2025-06-30';

$posts = Post::whereBetween( $published_at, [$startDate, $endDate])->get();

大部份的人都這樣做過(至少我這樣做過),檢索 6 月發布的所有帖子。這裡的問題是,published_at 列通常是 Datetime 類型,所以它不僅僅是一個簡單的日期,還包含一個時間。這意味著實際上 2025-06-30 日發布的文章不會被檢索到,因為它們的發布日期總是大於 2025-06-30(SQL 會將其理解為“2025-06-30 00:00:00”)。

$startDate = Carbon::createFromFormat('Y-m-d', '2025-06-01');
$endDate = Carbon::createFromFormat('Y-m-d', '2025-06-30');

$posts = Post::whereBetween('published_at', [$startDate, $endDate])->get();

這實際上更糟糕,因為它變得完全不可預測。 Carbon 實例代表一個時刻,它也有一個時間,除非不指定時間,否則它會預設為運行時的當前時間。所以,如果在上午 9 點運行時,而文章是在 30 號上午 8 點發布的,你就能檢索到它……但是,如果是在上午 7 點運行完全相同的腳本,將無法再檢索到該文章,因為 $endDate 實際上是“2025-06-30 07:00:00”。

我們可以使用 $endDate->toDateString() 來去除時間,但最終還是會回到上面的情況。

較好的 Carbon 利用方法

一種解決方案是確保在查詢中指定一個時間,並且該時間對於開始日期來說是一天的開始時間(00:00:00),對於結束日期來說是一天的結束時間(23:59:59.999999)。

幸運的是,Carbon 提供了 startOfDay() 和 endOfDay() 方法來實現這一點:

$startDate = Carbon::createFromFormat('Y-m-d', '2025-06-01')->startOfDay();
$endDate = Carbon::createFromFormat('Y-m-d', '2025-06-30')->endOfDay();

$posts = Post::whereBetween('published_at', [$startDate, $endDate])->get();

另一種方法: Filter

Laravel 提供很多方法(Available Methods),也可以先將資料以 where 條件篩選出來,透過 get() 取得資料後,再使用 filter() 作細部篩選。

$data = BlogPost::where([
	['status', '=', 'published'],
	['visible', '=', 'public']
])->get()
	->filter(function ($item) {
		if (isset($item->published_at) && isset($item->unpublished_at)) {
			if ( Carbon::now()->between($item->published_at, $item->unpublished_at) ) {
				return $item;
			}
		} else if (isset($item->published_at) && !isset($item->unpublished_at)) {
			if ( Carbon::now() > $item->published_at ) {
				return $item;
			}
		} else {
			return;
		}
	});

在程式中,使用 Carbon::now() 取得當時的時間,再與 published_at & unpublished_at 作比較,這也可以得到發佈與下架日期之間的文章。

Eloquent 其它種方法

Eloquent 提供了一個非常有用的 whereDate() 方法,它可以做兩件事:

  • 建立 SQL 查詢,使用 DATE() 的 SQL 函數將列內容格式化為 Y-m-d 格式。
  • 在比較 Carbon 或 Datetime 物件之前,將其正確地轉換為 Y-m-d 格式。

使用這個方法,我們可以放心地傳遞 Carbon 實例,並且知道其中包含的任何時間都會被丟棄,我們實際上是在兩個日期之間進行搜尋:

$startDate = Carbon::createFromFormat('Y-m-d', '2025-06-01');
$endDate = Carbon::createFromFormat('Y-m-d', '2025-06-30');

$posts = Post::query()
    ->whereDate('published_at', '>=', $startDate)
    ->whereDate('published_at', '<=', $endDate)
    ->get();

這將產生以下 SQL 查詢:

SELECT * from "posts"
WHERE DATE("published_at") >= '2025-06-01'
AND DATE("published_at") <= '2025-06-30';

參考:

How to properly retrieve Laravel models between two dates

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


內容索引