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

Malaysiaholiday Laravel Package

afiqiqmal/malaysiaholiday

A Laravel package for retrieving Malaysian public holidays. Look up holiday dates by state and year, with a simple API for querying and integrating holiday data into your PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require <vendor>/<package>
php artisan vendor:publish --tag=<package>-config

The first thing to check after upgrading to 4.2.1 is the shape of the response, not the endpoint. Every holiday record should now carry a source key (publicholidays.com.my or calendarmalaysia.com). Dump a single year before wiring anything up:

$holidays = Holidays::school(2025, 'Selangor');

dump($holidays->first());
// ['date' => '2025-01-01', 'name' => '...', 'states' => [...], 'source' => 'calendarmalaysia.com']

Your first real use case is almost always one of two things: rendering a holiday calendar for a single state, or deciding whether a given date is a working day (for SLA calculation, delivery estimates, payroll cut-offs). Start with the single-state calendar — it exercises the state-note logic in 4.2.1 without the complexity of multi-state aggregation.

If you only need public holidays, you can stay on the original source for all years. The fallback only kicks in for school holidays from 2025 onwards, so a 2023 public-holiday lookup behaves exactly as before.

Implementation Patterns

Cache on year + state, and store the source alongside. Holiday data is effectively immutable once published, but not perfectly immutable — upstream occasionally corrects a date. A 30–90 day TTL keyed on school:{year}:{state} is the right trade-off. Persist source in the cache payload, not just the dates, so an audit or a support ticket can answer "where did this date come from" without a re-fetch.

$holidays = Cache::remember(
    "holidays:school:{$year}:{$state}",
    now()->addDays(30),
    fn () => Holidays::school($year, $state)
);

Treat source as a first-class field, not a debug artifact. Once 2025+ school holidays come from a different upstream, a single UI or export can mix data from two providers in one list. Surface it in admin views and CSV exports even if end users never see it. When a parent disputes a term date, the source is what resolves the ticket.

Normalise sahaja / kecuali into a resolved state list at the boundary. Don't leak the Malay qualifier into your domain model. The package resolves the note for you, but confirm whether the resolved states array means "applies to these states" or still contains a qualifier string — assert this in a test on a known case (Johor sahaja and Semua negeri kecuali Sarawak are the two shapes worth pinning down). Then downstream code only deals with in_array($state, $record['states']).

$isHoliday = $holidays
    ->filter(fn ($h) => $h['date']->is($date))
    ->contains(fn ($h) => in_array($state, $h['states'], true));

Wrap the package behind your own service. Scrapers change shape without warning; the 4.2.1 fallback exists precisely because one upstream stopped being sufficient. A thin HolidayService with isWorkingDay(), schoolHolidays(), and publicHolidays() methods means a future source change is one file, not a grep across the app.

Use it for scheduling, not for stored truth. If you have recurring jobs that must skip holidays, resolve the holiday calendar at dispatch time rather than persisting derived "working day" flags into a table. Otherwise a corrected upstream date leaves stale booleans in your database.

Gotchas and Tips

The source key can break strict JSON assertions. If you use assertExactJson(), snapshot tests, or a JSON Schema with additionalProperties: false, 4.2.1 will fail them. This is technically additive, but it is a response-shape change — check your test suite before deploying the patch.

Two sources means two shapes. calendarmalaysia.com data may differ in date formatting, name casing, or how state scope is expressed compared to publicholidays.com.my. Do not assume a field is present just because it was present for 2024 data. Validate defensively and log the raw record when a required key is missing.

Test the boundary at 2024/2025 explicitly. This is the seam where the fallback activates. A test that only covers 2024 and 2026 will silently miss a broken switchover. Also test a 2025 public holiday (should still be the original source) against a 2025 school holiday (should be the fallback) to confirm the scope of the change is what you expect.

sahaja and kecuali are not the only note forms. Malay state notes may list several states, use commas, or appear as a suffix on the holiday name rather than a dedicated field. Don't hand-roll a regex over the name string — use whatever the package exposes, and add a regression test per note shape you encounter in the wild.

Scraper failure modes are silent, not loud. Upstream markup changes typically yield an empty collection or a truncated year rather than an exception. Assert count($holidays) > 0 and sanity-check that you got at least, say, 10 public holidays for a full year. An empty holiday calendar quietly turns every day into a working day.

Pin the timezone. Set app.timezone to Asia/Kuala_Lumpur or convert explicitly. Date comparisons against now() in a different timezone will be off by a day near midnight, which is exactly when holiday boundaries matter.

Extension point: if the fallback provider is configurable, keep the default and override per-environment only. Two providers returning different answers for the same date is a real possibility; decide up front which one wins for your use case, and document it.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
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