label84/laravel-hours-helper
Generate Laravel collections of time/date intervals for any period: build dropdown-ready schedules with custom formatting, exclusions, support for past-midnight ranges, and multi-day spans. Simple facade API to create evenly spaced slots like 08:00–09:30 every 30 minutes.
Install via Composer:
composer require label84/laravel-hours-helper
First Use Case: Generate a time dropdown for appointment scheduling.
use Label84\HoursHelper\Facades\HoursHelper;
// Basic 30-minute slots between 8 AM and 9:30 AM
$slots = HoursHelper::create('08:00', '09:30', 30);
// Pass to Blade view
return view('appointments.create', ['slots' => $slots]);
Key Starting Points:
Generate Slots:
$slots = HoursHelper::create(
start: '09:00',
end: '17:00',
interval: 60, // minutes
format: 'g:i A',
exclusions: [['12:00', '13:00']] // lunch break
);
Blade Integration:
<select name="appointment_time">
@foreach($slots as $slot)
<option>{{ $slot }}</option>
@endforeach
</select>
API Response:
return response()->json(['slots' => $slots]);
Dynamic Exclusions from Database:
$excludedRanges = DB::table('blocked_slots')
->where('date', $date)
->pluck('range');
$slots = HoursHelper::create('08:00', '18:00', 30, 'H:i', $excludedRanges);
Multi-Day Generation with Chunking:
$start = now()->startOfDay();
$end = now()->addDays(7)->endOfDay();
$slots = collect();
while ($start < $end) {
$dayEnd = $start->copy()->endOfDay();
$daySlots = HoursHelper::create($start, $dayEnd, 60, 'Y-m-d H:i');
$slots = $slots->merge($daySlots);
$start = $dayEnd->addMinute();
}
Validation Rule:
use Illuminate\Validation\Rule;
$validator->validate([
'time' => Rule::in($slots->pluck('value')->toArray())
]);
Background Job Processing:
foreach ($slots as $slot) {
GenerateReminderJob::dispatch($slot);
}
Carbon::setTimezone() before calling the helper if working with non-default timezones.$this->mock(HoursHelper::class)->shouldReceive('create')->andReturn(collect(['09:00', '10:00']));
Exclusion Overlap Handling:
[09:00,10:00] and [09:30,10:30] into [09:00,10:30]).Past-Midnight Edge Cases:
$slots->reject(fn($slot) => $slot > '01:00');
Performance with Large Ranges:
$slots->chunk(100)->each(function($chunk) { /* process */ });
Time Format Quirks:
g:i A vs. H:i).Carbon::parse()->format() first.Facade vs. Class:
new \Label84\HoursHelper\HoursHelper) is possible for non-Laravel contexts.if (!Carbon::parse($start)->isBefore(Carbon::parse($end))) {
throw new \InvalidArgumentException("Start must be before end.");
}
dd($slots->reject(fn($slot) => in_array($slot, $excludedSlots)));
Carbon::setTestNow(Carbon::now('America/New_York'));
Custom Interval Logic:
class CustomHoursHelper extends \Label84\HoursHelper\HoursHelper {
public function createExponential($start, $end, $multiplier) { ... }
}
Database Integration:
class SlotGenerator {
public function generate($start, $end, $interval) {
$exclusions = $this->fetchExcludedRanges($start, $end);
return HoursHelper::create($start, $end, $interval, null, $exclusions);
}
}
Event Dispatching:
$slots->each(fn($slot) => event(new SlotGenerated($slot)));
Localization:
HoursHelper::macro('createTranslated', function($start, $end, $interval, $locale) {
$translator = app('translator');
return $this->create($start, $end, $interval, fn($time) =>
$translator->get('time.' . $time->format('H_i'))
);
});
Carbon settings.HoursHelper::shouldReceive('create') in PHPUnit tests to mock responses.$exclusions = collect($rawExclusions)->unique()->sort()->values()->toArray();
$slots->lazy()->each(fn($slot) => /* process */);
How can I help you explore Laravel packages today?