mubbashir786/prayer-times-laravel

Prayer times, Ramadan scheduling, and Adhan reminders for Laravel apps.

Maintainers

Package info

github.com/mubbashir786/prayer-times-laravel

pkg:composer/mubbashir786/prayer-times-laravel

Transparency log

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-09-01 20:35 UTC

This package is auto-updated.

Last update: 2026-09-01 20:57:30 UTC


README

Daily prayer times for any city on earth — cached in your own database, Hijri-aware in 12 languages, timezone-correct, with a drop-in Blade widget and reminder events.

Latest version Downloads PHP version Laravel version Tests License

PrayerTimes::today('Lahore')->maghrib;       // "18:47"
PrayerTimes::today('Lahore')->hijri_date;    // "30 Ramadan 1447"  (Makkah says 1 Shawwal)
PrayerTimes::setLocale('ur')->hijriMonth(9); // "رمضان"
PrayerTimes::minutesUntilIftar('Dubai');     // 137

Contents

Why this package

🌍 Any city Pass a name from the built-in map, a name the API resolves for you, or raw coordinates.
🗄️ Cached in your DB One row per city per day. The Aladhan API is hit once, not on every page load.
🕐 Timezone-correct Each row remembers its own timezone, so "next prayer" is right for Makkah even when your app runs in UTC.
🌙 Ramadan-aware Hijri date and an is_ramadan flag on every row, plus Suhoor cutoff and Iftar countdown helpers.
🌐 12 languages Hijri months and prayer names in English, Urdu, Arabic, Turkish, Indonesian, Malay, Bengali, Persian, Hindi, French, Russian and Spanish.
🌗 Moon-sighting offsets Saudi Arabia says 1 Shawwal while Pakistan is still on 30 Ramadan — the package models that per country.
🔔 Reminder events A scheduled command fires an event N minutes before each prayer — wire it to mail, database, WhatsApp, Reverb, anything.
🎨 Blade widget <x-prayer-times::widget city="Lahore" /> and you are done.

Install

composer require mubbashir786/prayer-times-laravel
php artisan vendor:publish --tag=prayer-times-config
php artisan migrate

Optional — publish the translations if you want to edit them or add a language:

php artisan vendor:publish --tag=prayer-times-lang

The service provider and the PrayerTimes facade are auto-discovered — no manual registration needed.

Configure

Everything has a sensible default. Override what you need in .env:

PRAYER_TIMES_CITY=Islamabad
PRAYER_TIMES_COUNTRY=Pakistan
PRAYER_TIMES_LAT=33.6844
PRAYER_TIMES_LNG=73.0479
PRAYER_TIMES_TZ=Asia/Karachi
PRAYER_TIMES_METHOD=1
PRAYER_TIMES_ASR_SCHOOL=1
PRAYER_TIMES_REMINDERS_ENABLED=true
PRAYER_TIMES_LOCALE=ur
PRAYER_TIMES_HIJRI_ADJUSTMENT=0
Key Meaning
default_location Used whenever no city is passed.
cities Your own name → coordinates map (see below).
fallback_country Country sent with the API lookup for a city that is not in the map.
calculation_method Aladhan method id — 1 Karachi, 2 ISNA, 3 MWL, 4 Umm al-Qura…
asr_school 0 Shafi/Maliki/Hanbali, 1 Hanafi.
cache_ttl_hours How long a cached day stays fresh. 0 = never expires.
locale Language for months, prayer names and widget labels. null follows the app locale.
rtl_locales Which locales the widget renders right-to-left.
hijri.adjustment Global day offset applied to the Hijri date.
hijri.adjustments Per-country day offsets, e.g. 'Pakistan' => -1.
reminders Enable/disable, lead time in minutes, notification channels.
ramadan.suhoor_buffer_minutes Minutes subtracted from Fajr for the Suhoor cutoff.

Quick start

use Mubbashir786\PrayerTimes\Facades\PrayerTimes;

$today = PrayerTimes::today();

$today->fajr;          // "05:12"
$today->maghrib;       // "18:47"
$today->hijri_date;    // "1 Ramadan 1447" - in the current locale
$today->hijri->day;    // 1
$today->is_ramadan;    // true
$today->nextPrayer();  // ['name' => 'Asr', 'time' => '16:20']
$today->toPrayerArray(); // ['Fajr' => '05:12', 'Dhuhr' => '12:10', ...]

Picking a city

Every method takes an optional city, and coordinates you can pass instead of — or on top of — it:

PrayerTimes::today('Lahore');                    // from the config city map
PrayerTimes::today('sahiwal');                   // not in the map — resolved by the API
PrayerTimes::today('My Village', 31.10, 72.40);  // exact coordinates; the name is just a label

A city is resolved in this order:

1. explicit $lat / $lng ─────────────► /timings          any point on earth
2. config('prayer-times.cities') ────► /timings          no lookup needed, timezone known
3. anything else ────────────────────► /timingsByCity    name + fallback_country
4. no city passed ───────────────────► default_location

With 2 and 3 you get the same result; the difference is that the map avoids a name lookup and pins the timezone yourself. Add your own entries to config/prayer-times.php:

'cities' => [
    'Sahiwal' => [
        'latitude' => 30.6682,
        'longitude' => 73.1114,
        'timezone' => 'Asia/Karachi',
        'country' => 'Pakistan',
    ],
],

Names are matched case-insensitively, and the map's spelling is what gets stored — so today('lahore') and today('LAHORE') share one cache row.

PrayerTimes::cities();                 // names currently in the map
PrayerTimes::resolveLocation('dubai'); // ['city' => 'Dubai', 'latitude' => 25.2048, ...]

Ships with Islamabad, Karachi, Lahore, Rawalpindi, Faisalabad, Multan, Peshawar, Quetta, Makkah, Madinah, Dubai, London, Toronto and New York.

Hijri calendar

Every row carries its Hijri date as a value object:

$hijri = PrayerTimes::today('Lahore')->hijri;

$hijri->day;              // 30
$hijri->month;            // 9
$hijri->year;             // 1447
$hijri->monthName();      // "Ramadan"
$hijri->monthName('ur');  // "رمضان"
$hijri->isRamadan();      // true
$hijri->format();         // "30 Ramadan 1447"
$hijri->formatWithEra();  // "30 Ramadan 1447 AH"
$hijri->toDateString();   // "30-09-1447"
$hijri->toArray();        // day, month, year, month_name, adjustment, is_ramadan, formatted

$row->hijri_date is the same thing as a string, and $row->is_ramadan is derived from the month — both respect the current locale and any regional adjustment. There is a query scope too:

PrayerTime::ramadan()->get();   // every cached row that falls in Ramadan

Or straight from the facade, without touching a row:

PrayerTimes::hijri(Carbon::parse('2026-03-20'), 'Karachi');  // HijriDate
PrayerTimes::hijriMonths();          // [1 => 'Muharram', ..., 12 => 'Dhu al-Hijjah']
PrayerTimes::hijriMonth(9, 'tr');    // "Ramazan"

Languages

Twelve languages ship with the package:

en English ur اردو ar العربية tr Türkçe
id Indonesia ms Melayu bn বাংলা fa فارسی
hi हिन्दी fr Français ru Русский es Español

Set one globally for the rest of the request:

PrayerTimes::setLocale('ur');

PrayerTimes::locale();               // "ur"
PrayerTimes::hijriMonth(9);          // "رمضان"
PrayerTimes::prayerName('Fajr');     // "فجر"
PrayerTimes::today()->hijri_date;    // "30 رمضان 1447"
PrayerTimes::locales();              // every locale available

Or pass one per call — an explicit locale always wins:

PrayerTimes::hijriMonth(9, 'ar');            // "رَمَضان"
PrayerTimes::prayerName('Isha', 'ms');       // "Isyak"
PrayerTimes::today()->hijri->format('tr');   // "30 Ramazan 1447"
PrayerTimes::today()->toLocalizedPrayerArray('bn');  // ['ফজর' => '05:12', ...]

With no locale configured the package follows your application's locale, and falls back to English for anything it cannot translate. toPrayerArray() and nextPrayer() always return the canonical English names, so your logic never breaks when the display language changes.

To add a language or reword one, publish the files and edit them:

php artisan vendor:publish --tag=prayer-times-lang

That writes lang/vendor/prayer-times/{locale}/hijri.php and prayers.php. A new directory there is picked up automatically — add it to rtl_locales in the config if it reads right-to-left.

The translations are a solid starting point, but prayer-name conventions vary by region (Turkish İmsak vs Sabah, Indonesian Subuh vs Fajar). Have a native speaker check the languages you actually ship.

Regional Hijri differences

The API reports the Saudi (HJCoSA) calendar. Because the moon is sighted locally, other countries are often a day behind: on 20 March 2026 Saudi Arabia reads 1 Shawwal — Eid — while Pakistan is still on 30 Ramadan. The package models that as a day offset per country:

'hijri' => [
    'adjustment' => 0,             // global default
    'adjustments' => [             // per country, matched case-insensitively
        'Pakistan' => -1,
        'India' => -1,
        'Bangladesh' => -1,
    ],
],
PrayerTimes::forDate($eid, 'Makkah')->hijri_date;  // "1 Shawwal 1447"
PrayerTimes::forDate($eid, 'Lahore')->hijri_date;  // "30 Ramadan 1447"
PrayerTimes::forDate($eid, 'Lahore')->is_ramadan;  // true - still fasting

A city in the cities map may override its own country:

'Gilgit' => [
    'latitude' => 35.9208, 'longitude' => 74.3082,
    'timezone' => 'Asia/Karachi', 'country' => 'Pakistan',
    'hijri_adjustment' => 0,
],

How it works: the Gregorian date is shifted by the offset and converted by the API, so 29- and 30-day months are always handled correctly — no local Hijri arithmetic to get wrong. Prayer times are never affected, only the Hijri date and the Ramadan flag. Each row remembers the offset it was stored with, so changing the config refetches instead of serving another country's calendar.

This is a fixed offset, not moon sighting. It matches the common case, but the real difference is decided month to month by each country's committee. If your app must be exact on Eid, drive the offset from your own data rather than leaving it at the default.

API reference

Method Returns
today(?string $city, ?float $lat, ?float $lng) PrayerTime for today
forDate(?Carbon $date, ?string $city, ?float $lat, ?float $lng) PrayerTime for any date
forRange(Carbon $from, Carbon $to, ?string $city, ?float $lat, ?float $lng) array<'Y-m-d', PrayerTime>
suhoorCutoff(?Carbon $date, ?string $city, ?float $lat, ?float $lng) "05:02" — Fajr minus the buffer
minutesUntilIftar(?string $city, ?float $lat, ?float $lng) int minutes, or null once Maghrib has passed
cities() array of mapped city names
resolveLocation(?string $city, ?float $lat, ?float $lng) the location a call would use, offset included
hijri(?Carbon $date, ?string $city, ?float $lat, ?float $lng) HijriDate for that day
setLocale(?string $locale) / locale() set or read the display language
locales() every locale the package can render
hijriMonths(?string $locale) / hijriMonth(int $month, ?string $locale) Hijri month names
prayerName(string $prayer, ?string $locale) a translated prayer name

On the PrayerTime model:

Member Returns
fajr sunrise dhuhr asr maghrib isha "HH:MM" strings
city latitude longitude timezone date where and when these times belong to
hijri a HijriDate — day, month, year, month name, offset
hijri_date / is_ramadan "1 Ramadan 1447" in the current locale / bool
hijri_day hijri_month hijri_year hijri_adjustment the stored, queryable parts
toPrayerArray() the five daily prayers as name => time, English keys
toLocalizedPrayerArray(?string $locale) the same, keyed in any language
nextPrayer() ['name' => …, 'time' => …], or null after Isha
timezoneName() the row's timezone, falling back to the configured default
scopeRamadan() PrayerTime::ramadan()->get()
use Illuminate\Support\Carbon;

PrayerTimes::forDate(Carbon::parse('2026-03-20'), 'Makkah');

// A whole Ramadan calendar for one city, keyed by Y-m-d:
PrayerTimes::forRange(Carbon::parse('2026-02-18'), Carbon::parse('2026-03-19'), 'Karachi');

Blade widget

<x-prayer-times::widget />
<x-prayer-times::widget city="Lahore" />
<x-prayer-times::widget city="My Village" lat="31.10" lng="72.40" />
<x-prayer-times::widget city="Karachi" date="2026-03-20" />
<x-prayer-times::widget city="Lahore" locale="ur" />

It shows the city, the Hijri date, all five prayers with the next one in bold, and a Ramadan banner during Ramadan. It renders in the current locale unless you pass one, and sets dir="rtl" for right-to-left languages. To restyle it, publish the view and edit away:

php artisan vendor:publish --tag=prayer-times-views

Ramadan helpers

PrayerTimes::suhoorCutoff();               // "05:02" — Fajr minus suhoor_buffer_minutes
PrayerTimes::suhoorCutoff(null, 'Makkah'); // same, for another city
PrayerTimes::minutesUntilIftar('Dubai');   // 137, or null once Maghrib has passed
PrayerTimes::today()->is_ramadan;          // straight from the API's Hijri calendar

Both helpers count in the city's own timezone, so an app running in UTC still gets the right answer for Makkah or Toronto.

Reminders

Turn them on in config, then listen for the event anywhere in your app:

use Mubbashir786\PrayerTimes\Events\PrayerTimeApproaching;

Event::listen(PrayerTimeApproaching::class, function (PrayerTimeApproaching $event) {
    // $event->prayerName, $event->prayerTime, $event->minutesRemaining
    Notification::send(User::all(), new PrayerReminder($event->prayerName, $event->prayerTime));
});

The package registers prayer-times:check-reminders on Laravel's scheduler (every minute), so all you need is a running scheduler — php artisan schedule:work locally, or the usual cron entry in production. You can also run it by hand for a specific place:

php artisan prayer-times:check-reminders --city=Lahore

A ready-made PrayerReminder notification is included, with mail and database representations.

How caching works

One row per (city, date) in the prayer_times table. A day's times are fetched from the API the first time they are asked for and served from the database after that. A row is refetched when:

  • it is older than cache_ttl_hours (default: a week; 0 disables expiry),
  • the coordinates it was stored against no longer match the ones the city resolves to — so a row written before you added a city to the map is corrected rather than served for the wrong place, or
  • it was resolved by name and the city has since gained real coordinates in the map, or
  • its Hijri offset no longer matches the one the country resolves to.

Note — a row resolved by city name keeps latitude and longitude as null. The /timingsByCity response reports the same placeholder coordinates for every city on earth, so the package stores nothing rather than something wrong. The times and the timezone are real. Add the city to the cities map if you want the coordinates on the row.

Upgrading from 1.0

1.1 stores the Hijri date as structured columns instead of a formatted string, so the prayer_times table changes shape. If you installed 1.0 and already migrated, run:

php artisan migrate

The upgrade migration swaps hijri_date and is_ramadan for hijri_day, hijri_month, hijri_year and hijri_adjustment. Your cached prayer times are kept; each row refetches its Hijri date the first time it is asked for. It is a no-op on a fresh install, and it rolls back.

Republish the config to pick up the new locale, rtl_locales and hijri keys:

php artisan vendor:publish --tag=prayer-times-config --force

What changed in the API:

1.0 1.1
$row->hijri_date (column) still there, now an accessor rendered in the current locale
$row->is_ramadan (column) still there, now derived from hijri_month
$row->hijri — a HijriDate with day, month, year and month names
where('is_ramadan', true) PrayerTime::ramadan() or where('hijri_month', 9)

Everything else — today(), forDate(), suhoorCutoff(), minutesUntilIftar(), the widget — works exactly as it did.

Testing

composer install
composer test

82 tests cover city resolution and the endpoint each path picks, caching and invalidation, timezone handling, the Hijri calendar and its per-country offsets, all twelve languages, the Ramadan helpers, the Blade widget, the reminder command and the upgrade path from 1.0 — all against a mocked HTTP client, so the suite never touches the network.

Six end-to-end tests run against the live Aladhan API, including the Saudi/Pakistan Eid split. They are excluded by default and opt-in:

vendor/bin/phpunit --group=integration

Roadmap

  • Qibla direction helper (bearing calculation from lat/lng)
  • Moon-sighting feeds, so the Hijri offset follows real announcements instead of a fixed number
  • WhatsApp/SMS channel presets for reminders
  • Ramadan calendar export (iCal) for Suhoor/Iftar times across the month

License

MIT © Mubbashir. Prayer time data from the free Aladhan API.