TL;DR: How to calculate business deadlines in Laravel that automatically skip weekends and holidays — using a database-driven holidays table and Carbon. Code examples included.
The Problem
When I was building VMMS — a voucher management system for government offices — I needed to calculate processing deadlines for each department.
Simple enough, right? Just add X days to the current date.
Wrong.
Government offices don't work on weekends. They don't work on holidays. A voucher submitted on Friday shouldn't have a Monday deadline if Monday is a holiday.
I needed a deadline calculator that was aware of all of this.
The Approach
Instead of hardcoding holidays, I stored them in a database table. This means adding new holidays requires no code changes — just a new database record.
Schema::create('holidays', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->date('date');
$table->timestamps();
});
Building the DateHelpers Service
I created a dedicated service class to handle all date calculations:
<?php
namespace App\Services;
use Carbon\Carbon;
use App\Models\Holiday;
class DateHelpers
{
protected array $holidays;
public function __construct()
{
$this->holidays = Holiday::pluck('date')
->map(fn($d) => Carbon::parse($d)->toDateString())
->toArray();
}
public function isNonWorkingDay(Carbon $date): bool
{
return $date->isWeekend()
|| in_array($date->toDateString(), $this->holidays);
}
public function addBusinessDays(Carbon $date, int $days): Carbon
{
$result = $date->copy();
$added = 0;
while ($added < $days) {
$result->addDay();
if (!$this->isNonWorkingDay($result)) {
$added++;
}
}
return $result;
}
}
How It Works
The addBusinessDays method loops day by day — skipping weekends and holidays — until it has counted the required number of business days.
For example — if today is Friday and you need 3 business days:
Saturday → skip (weekend)
Sunday → skip (weekend)
Monday → count (1)
Tuesday → count (2)
Wednesday → count (3) ← deadline
If Monday is a holiday:
Saturday → skip (weekend)
Sunday → skip (weekend)
Monday → skip (holiday)
Tuesday → count (1)
Wednesday → count (2)
Thursday → count (3) ← deadline
Using It in the Application
php$dateHelpers = new DateHelpers();
$deadline = $dateHelpers->addBusinessDays(Carbon::now(), 3);
When a department receives a voucher — the deadline is calculated automatically and stored in the audit trail:
AuditTrail::create([
'transaction_id' => $transaction->id,
'processing_offices' => $department->name,
'process_initiate' => now(),
'deadline' => $dateHelpers->addBusinessDays(Carbon::now(), 3),
]);
Caching for Performance
Since holidays don't change often — I cached the holidays list to avoid hitting the database on every request:
public function __construct()
{
$this->holidays = cache()->remember('holidays', now()->addDay(), function () {
return Holiday::pluck('date')
->map(fn($d) => Carbon::parse($d)->toDateString())
->toArray();
});
}
This caches the holidays list for 24 hours — refreshing automatically every day.
Checking for Overdue Deadlines
With Carbon's diffInDays you can easily check if a deadline has passed:
$deadline = Carbon::parse($auditTrail->deadline)->startOfDay();
$daysLeft = (int) Carbon::now()->startOfDay()->diffInDays($deadline, false);
// Negative = overdue, Positive = days remaining, Zero = due today
if ($daysLeft < 0) {
// Flag as overdue
}
The false parameter makes diffInDays return negative numbers for past dates — useful for overdue detection.
Managing Holidays
Since holidays are stored in the database — admins can manage them through a simple CRUD interface:
// Add a holiday
Holiday::create([
'name' => 'Independence Day',
'date' => '2026-06-12',
]);
// Get all holidays for the year
Holiday::whereYear('date', now()->year)->get();
No code deployments needed when a new holiday is declared — just add it to the database.
What I Learned
Keep business logic out of the database — date calculations belong in PHP, not SQL
Cache holiday lookups — they rarely change but get queried often
Use Carbon consistently — mixing Carbon with raw date strings causes subtle bugs
Test edge cases — Friday submissions, holiday Mondays, and long weekends all need testing
Conclusion
Holiday-aware deadline calculations sound simple but have more edge cases than you'd expect. A dedicated service class with a database-driven holidays table keeps the logic clean, flexible, and easy to maintain.
This pattern works for any system that needs business day calculations — leave requests, SLA tracking, invoice due dates, and more.
Want to see this in action?
I built VMMS — a complete Voucher Management & Monitoring System using everything covered in this article.
🔴 Live demo: https://vmms-app-production.up.railway.app/login
💰 Get the full source code: https://getvmms.gumroad.com/l/zeroqz
Happy to answer any questions in the comments! 🚀
Top comments (0)