DEV Community

Chris Lloyd Fallaria
Chris Lloyd Fallaria

Posted on

How I Built Holiday-Aware Deadline Calculations in Laravel

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();
});
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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),
]);
Enter fullscreen mode Exit fullscreen mode

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();
    });
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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)