Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Yasumi Laravel Package

azuyalabs/yasumi

Yasumi is a PHP library for retrieving holiday dates and names across many countries and regions. Calculation- and rule-driven, it needs no database and offers a simple API to query, list, and check holidays for any year.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer — there is nothing to publish, no migrations, no service provider:

composer require azuyalabs/yasumi

Sanity-check the runtime first: Yasumi requires PHP 8.2+, so on an older Laravel app composer require will fail or silently resolve an old major. Run composer why-not azuyalabs/yasumi if you're unsure.

Where to look first, in order:

  1. https://www.yasumi.dev — the real documentation (the README is an advert; the docs site has the provider list, filters, and custom-provider recipes).
  2. Yasumi::getProviders() — introspect at runtime rather than hardcoding provider names in your head.
  3. The examples/ directory in the repo for copy-paste starting points.

First use case: "is this a working day?" This is 90% of what Laravel apps actually want Yasumi for — delivery estimates, SLA clocks, payout dates, cron skipping.

use Yasumi\Yasumi;

$holidays = Yasumi::create('USA', 2026);

$holidays->isHoliday(new DateTimeImmutable('2026-07-03')); // bool
$holidays->isWorkingDay(new DateTimeImmutable('2026-07-03')); // false if weekend or holiday
$holidays->isWeekend(new DateTimeImmutable('2026-07-04'));

// Enumerate the year
foreach ($holidays as $holiday) {
    echo $holiday->getName().' — '.$holiday->format('Y-m-d').PHP_EOL;
}

// Look up a specific one by its internal key
$holiday = $holidays->getHoliday('independenceDay');

Then map your app's locale/tenant country to a provider with Yasumi::createByISO3166_2('NL', 2026) instead of matching provider strings by hand. Provider objects are ArrayIterator-based, so foreach, count(), and iterator_to_array() all just work.

Implementation Patterns

1. Bind it once, inject everywhere

Don't scatter Yasumi::create() calls through controllers. Create one small service and resolve it from the container:

// app/Services/HolidayCalendar.php
namespace App\Services;

use Carbon\CarbonInterface;
use Yasumi\Yasumi;
use Yasumi\Provider\AbstractProvider;

class HolidayCalendar
{
    /** @var array<string, AbstractProvider> */
    private array $memo = [];

    public function __construct(
        private readonly string $provider,   // e.g. 'Netherlands'
        private readonly string $locale = 'en_US',
    ) {}

    public function for(int $year): AbstractProvider
    {
        return $this->memo[$year] ??= Yasumi::create($this->provider, $year, $this->locale);
    }

    public function isHoliday(CarbonInterface $date): bool
    {
        return $this->for($date->year)->isHoliday($date->toDateTimeImmutable());
    }

    public function isWorkingDay(CarbonInterface $date): bool
    {
        return $this->for($date->year)->isWorkingDay($date->toDateTimeImmutable());
    }
}

Resolve it per-tenant/per-user, e.g. app(HolidayCalendar::class) for a single-country app, or a factory method taking the user's country. Keep the locale→provider mapping in config/holidays.php so ops can fix a country without a deploy.

2. Cache the computed year, not the object graph

A provider computes every holiday for the year in its constructor. Building one per request per tenant is wasteful; building one per isHoliday() call is pathological.

$names = Cache::remember(
    "holidays:{$provider}:{$year}:{$locale}",
    now()->addDay(),
    fn () => Yasumi::create($provider, $year, $locale)->getHolidayNames() // key => name
);

$dates = Cache::remember(
    "holidays-dates:{$provider}:{$year}",
    now()->addDay(),
    fn () => collect(Yasumi::create($provider, $year)->getHolidays())
        ->map(fn ($h) => $h->format('Y-m-d'))
        ->flip()
        ->all()
);

Cache plain scalars (date strings, name maps), not Holiday objects — serialization survives, but the underlying rules change between releases and stale objects hide that. Holiday definitions do get retro-fixed upstream, so a 24h TTL (or a scheduled nightly warm) is a reasonable compromise.

3. Business-day arithmetic

public function addBusinessDays(CarbonInterface $start, int $days): CarbonInterface
{
    $date = $start->copy();
    while ($days > 0) {
        $date->addDay();
        if ($this->isWorkingDay($date)) {
            $days--;
        }
    }
    return $date;
}

Looping years at the boundary is the only subtlety: a date near 31 December needs next year's provider. Either keep a two-year memo or guard with if ($date->month === 12 && $date->day > 20) and pre-warm the next year.

4. Filter by holiday type, not by name

Payroll, invoicing, and UI display have different rules. Yasumi classifies holidays as official, observed, bank, season, or other:

use Yasumi\Holiday;

$official = Yasumi::create('UnitedKingdom', 2026)->filter('type', Holiday::TYPE_OFFICIAL);
$bank     = Yasumi::create('UnitedKingdom', 2026)->filter('type', Holiday::TYPE_BANK);

Use official for legal/contractual logic (interest accrual, statutory pay), bank for settlement/payment rails, and show everything to end users. Note filter() returns a plain array — you lose the isHoliday()/isWorkingDay() helpers on the result, so filter late.

5. Validation rule and API resource

A working_day rule pays for itself immediately in booking/scheduling forms:

Validator::extend('working_day', fn ($attr, $value, $params, $validator) => app(HolidayCalendar::class)
    ->isWorkingDay(Carbon::parse($value)));

For a JSON feed, project deliberately rather than json_encode($holiday):

return collect($provider->getHolidays())->map(fn ($h) => [
    'key'    => $h->getName(),        // or build a key map from getHolidayNames()
    'name'   => $h->getName(),
    'date'   => $h->format('Y-m-d'),
    'type'   => $h->getType(),
])->values();

6. Scheduled jobs and notifications

  • Skip jobs on non-working days: if (! app(HolidayCalendar::class)->isWorkingDay(today())) return;
  • Add a "next holiday" banner with Yasumi::next($provider, $locale) — cheaper than iterating years yourself.
  • For recurring payroll runs, snapshot the year's holiday list at the start of the year so mid-year upstream fixes don't silently move a payout date.

7. Testing

Freeze time as usual; Yasumi is deterministic per (provider, year, locale), so snapshot tests are ideal:

public function test_2026_list_is_stable(): void
{
    $this->assertSame(
        ['2026-01-01', '2026-04-03', /* ... */],
        array_values(array_map(
            fn ($h) => $h->format('Y-m-d'),
            iterator_to_array(Yasumi::create('Germany', 2026))
        ))
    );
}

A snapshot test is your canary when bumping the package — treat changes as contract changes, not as noise to re-bless.

8. Extension point: your own provider

For company-specific days or an unsupported region, extend AbstractProvider (or Christian, which gives you calculateEaster() and friends), implement initialize(), and addHoliday():

$this->addHoliday(new Holiday(
    'foundersDay',
    ['en_US' => "Founders' Day", 'nl_NL' => 'Oprichtersdag'],
    new DateTime('2026-05-14', $this->timezone),
    $this->locale,
    Holiday::TYPE_OTHER,
));

Also override $timezone and the weekend definition if the country isn't Sat/Sun.

Gotchas and Tips

  • getHoliday() throws for unknown keys on current versions. The README's if ($x !== null) snippet is out of date — recent changelogs add a HolidayNotFoundException. Guard with array_key_exists($key, $provider->getHolidayNames()) or wrap in try/catch.
  • Providers are year-scoped. There is no "all holidays between 2024 and 2027" API. Loop years and merge; remember to dedupe if you merge across providers (e.g. a company holiday that coincides with a national one).
  • Timezone mismatch is the classic bug. Holidays are constructed in the provider's timezone. A Carbon timestamp in America/New_York compared against a Europe/Amsterdam provider can land on the wrong calendar day near midnight. Normalise with ->toDateString() or ->setTimezone($provider->timezone) before comparing.
  • Holiday extends DateTime, it is not Carbon. You get format(), getName(), getType() — not isSameDay() or addWeekdays(). Use Carbon::instance($holiday) when you need the fluent API, and avoid mutating the object (it's mutable DateTime; mutating a provider's holiday corrupts the instance for the rest of the request).
  • Translations come from gettext. Pass a locale as the third argument to get localised names, but verify the environment has locale data (locale -a). Containers frequently ship without generated locales and you'll silently get English. Fallback is en_US.
  • Duplicate dates are normal. Substitute/observed holidays often share a date with the original (especially UK/Japan). Don't assume date => single holiday; iterate.
  • Historical accuracy is bounded by reality. Each provider only emits a holiday for years it actually existed (Germany's Unity Day, Japan's Emperor's Birthday shifting years, etc.). Backfilling 1995 reports will quietly produce different sets. For historic reporting, snapshot the lists at the time of the report.
  • Keys and provider names matter and are inconsistent. Keys are camelCase (newYearsDay, goodFriday, christmasDay) and not perfectly uniform across providers, and provider identifiers are specific (Czechia vs Czech Republic is an explicit alias, per the changelog). Always discover via getHolidayNames() / getProviders() and pin the strings in tests.
  • Type classifications get corrected upstream. Changelogs regularly re-label things ("Easter Sunday is not an official holiday", "Pentecost is not an official holiday except in Brandenburg"). If your payroll logic depends on TYPE_OFFICIAL, pin the version and review the changelog before upgrading.
  • Region granularity exists. Several countries expose sub-region behaviour (Spain, UK, German Länder, Australian states). Pick the most specific provider you can; falling back to a national provider gives you a superset that may over-count holidays for the tenant.
  • Weekends aren't always Sat/Sun. Providers define their own weekend; isWeekend() follows that, so don't re-implement $date->isWeekend() from Carbon alongside it — you'll disagree with yourself.
  • Don't compute in an accessor. A model accessor calling Yasumi::create() per row is a classic N+1-in-CPU-time. Resolve the calendar once per request job and memoize per year.
  • Upgrading is the real risk surface. The package is actively developed with frequent year-bound fixes and renames. Read changelog/unreleased.md before bumping, keep the snapshot test, and don't allow ^2 carets on a repo where a holiday name change hits customer-facing invoices.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core