Due to the complexity of the rules, I'd probably go for a closure-based approach. You can get pretty fine grained with the error messages for each step of the way.
$validator = Validator::make($request->all(), [
'pickup_date' => [
'required',
'date_format:"Y-m-d H:i:s"',
function($attribute, $value, $fail) {
$pickupDate = Carbon::parse($value);
// is date < now?
if ($pickupDate->lt(now()) {
return $fail($attribute.' must be on or after ' . now()->toDateTimeString());
}
// is time between 5pm and 9:30pm? (Can you pickup 7 days/week?)
$pickupTime = Carbon::createFromTime($pickupDate->hour, $pickupDate->minute, $pickupDate->second);
$earliestTime = Carbon::createFromTimeString('17:00:00');
$latestTime = Carbon::createFromTimeString('21:30:00');
if ( ! $pickupTime->between($earliestTime, $latestTime)) {
return $fail($attribute.' must be between 5pm and 9:30pm');
}
},
],
]);
I'm not saying that's the exact code to use, or the most efficient, but I'm just using carbon to compare dates/times and setting error messages for each. Anyway, hope that gives you something to go on.