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

Nepali Date Laravel Package

rohanadhikari/nepali-date

rohanadhikari/nepali-date is a Laravel/PHP package for working with Nepali (Bikram Sambat) dates. It helps convert between AD and BS, format and parse Nepali dates, and integrate date handling into Laravel apps.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and let Laravel's package discovery register the service provider:

composer require rohanadhikari/nepali-date
php artisan vendor:publish --tag=nepali-date-config

A plain composer require resolves to the latest stable line (1.x). A v2.0-beta exists upstream, but Composer will not select a pre-release unless you ask for it explicitly (^2.0@beta or a lowered minimum-stability), so a fresh install today is still on 1.x. Don't opt into the beta on a project that has a deadline; do read the upstream changelog diff before you do.

The config file is the first thing to open — it controls the default output format and the timezone used when "now" is resolved to a BS date. Set the timezone explicitly to Asia/Kathmandu rather than relying on app.timezone, because date boundaries in Nepal do not line up with UTC or with your app's default.

Where to look first: the package exposes two value objects — a BS date (NepaliDate) and an AD date (AdDate). Almost all day-to-day work is one of two conversions:

use RohanAdhikari\NepaliDate\NepaliDate;

// BS -> AD (e.g. parsing a form submission before saving)
$ad = NepaliDate::from('2081-04-01')->toAd();   // AdDate

// AD -> BS (e.g. rendering a stored timestamp)
$bs = NepaliDate::fromAd(now())->toBs();        // NepaliDate
echo $bs->format('Y-m-d');                      // "2081-04-01"

First use case: an invoice or receipt that must show a Bikram Sambat date while the database keeps Gregorian timestamps. Add an accessor that converts created_at at render time — do not add a bs_date column on day one.

Implementation Patterns

Store AD, render BS. Keep every date, datetime, and timestamp column in Gregorian. Convert at the edges: API resources, Blade views, PDF exports, and CSV downloads. This preserves whereBetween, orderBy, and index behaviour, and means a bad timezone assumption only breaks display, not data.

Convert once per request, not per row. In a report of 5,000 invoices, converting each row's created_at in a loop is measurable overhead. Convert the query bounds to AD instead:

// Nepali fiscal year 2081/82: Shrawan 1 to Ashadh end
$from = NepaliDate::from('2081-04-01')->toAd()->toDateTime();
$to   = NepaliDate::from('2082-03-32')->toAd()->toDateTime();

$invoices = Invoice::whereBetween('issued_at', [$from, $to])->get();

Validate as BS, persist as AD. Use a form request that first checks the shape, then the calendar:

public function rules(): array
{
    return ['issued_on_bs' => ['required', 'regex:/^\d{4}-\d{2}-\d{2}$/']];
}

public function toAd(): array
{
    $bs = NepaliDate::from($this->input('issued_on_bs'));

    // Round-trip guard: catches 2081-01-32 style input that BS month
    // length rules reject.
    abort_unless($bs->toAd()->toBs()->format('Y-m-d') === $this->input('issued_on_bs'), 422);

    return ['issued_at' => $bs->toAd()->toDateTime()];
}

Expose both calendars in API resources. Clients differ: internal tools want ISO BS strings they can sort, public pages want preformatted Nepali names.

return [
    'issued_at'       => $this->issued_at->toIso8601String(),      // AD, canonical
    'issued_on_bs'    => NepaliDate::fromAd($this->issued_at)->format('Y-m-d'),
    'issued_on_bs_l'  => NepaliDate::fromAd($this->issued_at)->format('j F Y'), // Nepali label
];

Add a thin application wrapper. Rather than importing the vendor namespace in 40 files, create App\Support\BsDate with fromAd(), fromBs(), today(), and label(). When the package's API shifts — or you swap libraries — you edit one file. This also gives you a single place to pin the numeral script (Devanagari vs Latin digits) and the format presets your product actually uses. The wrapper is what makes a future 1.x → 2.x move a one-file diff instead of a grep-and-patch exercise.

Blade and view helpers. Register a directive or a small component once in a service provider and use it everywhere:

Blade::directive('bs', fn ($expr) => "<?php echo e(\\App\\Support\\BsDate::label($expr)); ?>");
{{-- renders "१५ श्रावण २०८१" or "15 Shrawan 2081" --}}
@bs($invoice->issued_at)

Freeze "today" for scheduled jobs. In queued jobs, exports, and PDF generation, resolve BsDate::today() once at the top and pass the value down. A job that runs at 18:20 UTC is already on the next Nepali day, and a "yesterday vs today" comparison computed inside a loop can straddle the boundary mid-run.

Gotchas and Tips

  • BS months are not fixed-length. A month has 29–32 days depending on the year, and the package resolves this from a bundled lookup table. Anything outside the supported year range throws or returns nonsense. Seeders, factories, and demo data must stay inside that range; check the README for the exact bounds and add a test that asserts them.
  • strtotime() and checkdate() are useless here. checkdate(4, 1, 2081) is not a Gregorian date and PHP will happily accept or reject it wrongly. The only reliable validation is a parse plus a round-trip comparison.
  • The +5:45 offset breaks whole-hour assumptions. Nepal is UTC+05:45. Code that formats offsets, truncates to the hour, or compares diffInHours() against a naive expectation will drift. Always convert via an explicit timezone, never date('Y-m-d') on a raw timestamp.
  • "Today" in UTC is often tomorrow in Kathmandu. Any code doing now()->toDateString() then converting is one day off for roughly 18:15–24:00 UTC. Use now('Asia/Kathmandu').
  • Format tokens are not Carbon's. BS formatting has its own token set (Nepali day and month names, ordinal handling, numeral script). Passing a Carbon-format string and assuming it renders identically will silently produce wrong labels. Verify each token you use against the package docs.
  • The returned AD object may not be a Carbon instance. If you need diffForHumans(), isoFormat(), or ->startOfMonth(), wrap it explicitly: Carbon::instance($ad->toDateTime()) or Carbon::parse($ad).
  • Value objects are likely immutable. format() returns a string; conversions return new instances. Don't write $bs->addDays(3) and assume $bs changed — use the return value.
  • Don't sort by a formatted BS label. If you must sort in SQL, sort by the underlying AD column. A BS string is only lexicographically sortable in strict YYYY-MM-DD form with Latin digits.
  • Pin the version and own an integration test. With zero dependents and a young release history, treat the package as a dependency you are responsible for: "rohanadhikari/nepali-date": "^1.0" in composer.json (or ^2.0 only after you have read the changelog and run the test below against it), plus a feature test that round-trips a handful of boundary dates (first of each month across two BS years, and the last day of a 32-day month). That test is what tells you a composer update broke something.
  • Treat the v2.0-beta as unproven until the changelog says otherwise. The release announcement is a bare compare link, so the only safe assumption is that a major bump may rename methods, change default formats, or alter the supported year range. Before any upgrade: read the diff, branch, bump the constraint with an explicit pre-release flag, run your round-trip test suite, and only then touch the wrapper class. Ship on 1.x until there is a stable 2.x.
  • Method names track the README, not this document. Confirm from(), fromAd(), toAd(), and toBs() against the version you installed before spreading them across the wrapper class — the wrapper exists precisely so a naming change stays contained.
  • Extension points worth using: the published config for default format and timezone, a container binding for your own formatter if you need multiple locales, and a Blade directive for views. Avoid patching the vendor directory for fiscal-year logic — that belongs in your app layer where the Shrawan–Ashadh boundaries are business rules, not date-library concerns.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
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