
本文详解如何在 laravel 中准确判断当前时间是否落在数据库中两个时间字段(如 `start_time` 和 `end_time`)定义的时间范围内,并指出使用 `time` 类型的严重缺陷,推荐采用 `datetime` 或 `timestamp` 字段配合原生时间函数进行可靠查询。
在 Laravel 应用中,常需筛选“当前时刻处于某配置时间段内”的用户(例如:仅在 09:00–17:30 启用短信告警)。但如原始代码所示,若数据库字段定义为 time('start_time') 和 time('end_time'),将面临根本性限制:
- ✅ time 类型仅存储时分秒(HH:MM:SS),不包含日期、时区或上下文;
- ❌ whereTime() 方法无法表达“时间在区间内”逻辑——它只做点对点比较(如 $q->whereTime('start_time', '>=', '14:00:00')),而 start_time 区间判定,需组合条件且必须确保逻辑严谨。
更关键的是:**whereTime('start_time', '>=', $now) + whereTime('end_time', '= start AND = ? AND end_time
✅ 正确方案:改用 datetime 类型 + 区间查询
首先,在迁移中重构字段类型(推荐 datetime,语义清晰且支持时区):
Schema::create('settings', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id');
$table->datetime('start_at'); // 替换为 datetime
$table->datetime('end_at'); // 替换为 datetime
$table->foreign('user_id')->references('id')->on('users');
$table->timestamps();
});然后,在查询中使用 Laravel 的 whereBetween() 或原生 whereRaw 确保时间区间逻辑正确:
// ✅ 推荐:使用 whereBetween(需确保 start_at/end_at 是 datetime 且含当日日期)
$now = now('Asia/Karachi'); // Carbon instance, timezone-aware
$users_with_crypto_only_sms_alert = User::whereNotNull('device_key')
->where('subscription_package', 'LIKE', '%Morpheus%')
->where('role', 'user')
->where('sms_alerts', 1)
->where('is_email', 0)
->where('is_blocked', 0)
->where('mobile_no', '!=', '')
->whereHas('setting', function ($q) use ($now) {
// 正确逻辑:当前时间介于 start_at 和 end_at 之间(含端点)
$q->whereBetween('start_at', [$now, $now]) // ❌ 错误!这是无效写法
->whereBetween('end_at', [$now, $now]); // ❌ 不要这样用
})
->get(); // ⚠️ 注意:以上 whereBetween 用法错误 —— 见下方修正⚠️ 注意:whereBetween('column', [$from, $to]) 是用于判断 column 值是否在 [$from, $to] 区间内;而我们需要的是 $now 是否在 [start_at, end_at] 区间内,因此应使用:
->whereHas('setting', function ($q) use ($now) {
$q->where('start_at', '<=', $now)
->where('end_at', '>=', $now);
})完整可运行示例:
use Illuminate\Support\Carbon;
$karachiTime = Carbon::now('Asia/Karachi');
$deviceKeys = User::whereNotNull('device_key')
->where('subscription_package', 'LIKE', '%Morpheus%')
->where('role', 'user')
->where('sms_alerts', 1)
->where('is_email', 0)
->where('is_blocked', 0)
->where('mobile_no', '!=', '')
->whereHas('setting', function ($q) use ($karachiTime) {
$q->where('start_at', '<=', $karachiTime)
->where('end_at', '>=', $karachiTime);
})
->pluck('device_key')
->all();
dd($deviceKeys);? 补充说明与最佳实践
- 时区一致性:务必确保数据库服务器、PHP 运行环境、Laravel 配置(app.timezone)及 Carbon::now($tz) 中的时区统一为 'Asia/Karachi',否则跨时区查询结果不可靠。
-
索引优化:为 start_at 和 end_at 字段添加复合索引可显著提升查询性能:
$table->index(['start_at', 'end_at']);
- 边界处理:若业务要求“严格开区间”(如 start = 改为 >。
-
历史兼容性:若无法修改表结构,可临时用 whereRaw 模拟(不推荐长期使用):
->whereRaw('? BETWEEN TIME(start_time) AND TIME(end_time)', [$karachiTime->format('H:i:s')])但此方式丧失索引能力、不可移植,且无法处理跨日场景(如 23:00 → 02:00)。
总之,时间范围判定的本质是数学区间运算,而非字符串或时间片段比较。选用 datetime 类型、配合清晰的 WHERE start = now 逻辑,才是 Laravel 中健壮、可维护、可扩展的解决方案。










