PHPackages                             arifur9993/attendance-engine - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. arifur9993/attendance-engine

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

arifur9993/attendance-engine
============================

The attendance brain for PHP apps. Pure-function resolver for shifts, breaks, overtime, overnight, rosters &amp; compliance. Zero deps. PHP 8.2+. PHP twin of @attendance-engine/core.

v0.5.0(2mo ago)00MITPHPPHP ^8.2CI passing

Since May 13Pushed 2mo agoCompare

[ Source](https://github.com/arifur9993/attendance-engine-php)[ Packagist](https://packagist.org/packages/arifur9993/attendance-engine)[ Docs](https://github.com/arifur9993/attendance-engine-php)[ Fund](https://github.com/arifur9993/attendance-engine-php)[ GitHub Sponsors](https://github.com/arifur9993)[ RSS](/packages/arifur9993-attendance-engine/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (5)Versions (3)Used By (0)

⏱️ attendance-engine
====================

[](#️-attendance-engine)

### The attendance brain your PHP app has been missing.

[](#the-attendance-brain-your-php-app-has-been-missing)

**Punches in → answers out.** Shifts, breaks, overtime, overnight, rosters, compliance. Pure functions. Zero runtime dependencies. PHP 8.2+.

[![PHP](https://camo.githubusercontent.com/8c751af124bc4fa1ec022d8776d63411c93e8d722abc402bd74049512f414f1e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e32253230253743253230382e33253230253743253230382e342d3737374242343f7374796c653d666f722d7468652d6261646765266c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://www.php.net/)[![Zero deps](https://camo.githubusercontent.com/f474dc13743ed93e7957941efeabe85d538b1bcc9b0909c218f908d2b53c1b4a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f646570656e64656e636965732d7a65726f2d737563636573733f7374796c653d666f722d7468652d6261646765)](composer.json)[![PHPStan](https://camo.githubusercontent.com/468f2480f7fa0681cb9a8df929dd56002d4085eb418a05136d2dc29fac501f26/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230382d3164346564383f7374796c653d666f722d7468652d6261646765)](phpstan.neon)[![License](https://camo.githubusercontent.com/31e62e0eff03ce9ddfdf69d8476340d4f541990bfb152cb02a0f342965252997/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e7376673f7374796c653d666f722d7468652d6261646765)](LICENSE)[![GitHub](https://camo.githubusercontent.com/6988a7a39491cd1082edd74eea2b834247450232ecb3b4fbeda620e47853ced7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f736f757263652d4769744875622d3138313731373f7374796c653d666f722d7468652d6261646765266c6f676f3d676974687562266c6f676f436f6c6f723d7768697465)](https://github.com/arifur9993/attendance-engine-php)

---

Why this exists
---------------

[](#why-this-exists)

> *"Was this person late?"* sounds easy. It isn't.

Overnight shifts that cross midnight. Missing clock-outs. Duplicate biometric reads landing 12 seconds apart. Grace windows that vary by team. Rotating rosters. Unpaid lunch breaks that may or may not be taken. Daylight-saving boundaries. Time-zone drift between the device, the server, and the database.

Every HR/payroll/attendance product in the world re-solves these same dozen edge cases — usually buried in a 4,000-line Laravel controller that nobody dares touch.

**`attendance-engine` is that logic, lifted out, tested to death, and shipped as a pure-function library.**

It's the PHP twin of [`@attendance-engine/core`](https://www.npmjs.com/package/@attendance-engine/core) (TypeScript). Same contract, same fixtures, same to-the-minute answers — so your Node frontend and your PHP backend never disagree on whether Rahim was 3 minutes late on Tuesday.

---

✨ What you get
--------------

[](#-what-you-get)

⚡ **One function, one answer**`Engine::resolveDay()` takes raw punches, returns a typed `DayResult`. No globals, no state, no surprises.🌙 **Overnight-safe**Shifts that cross midnight, punches that bucket onto the right duty-date. We've thought about this so you don't have to.🧮 **Overtime, breaks, lateness**All the math: late-after-grace, unpaid-break deduction, early-out, OT (`shift-based` / `fixed-hours` / `daily-cap`), rounding units.📅 **Ranges + summaries**`Engine::resolveRange()` for a whole month, `Engine::summarize()` for the payroll card.🗓️ **Rosters built-in**`Engine::generateRoster()` expands a weekly pattern into a date-keyed shift map.🌐 **Timezone-safe**The engine *never* touches `date_default_timezone_set()`. Every timestamp carries its own offset. No DST drift.🪶 **Zero deps**Pure PHP. No Carbon, no Symfony, no Composer plugins. Drop into Laravel, Symfony, Slim, or vanilla.🧪 **Tested**PHPStan level 8 + Pest. CI matrix on 8.2 / 8.3 / 8.4.---

📦 Install
---------

[](#-install)

```
composer require arifur9993/attendance-engine
```

That's it. The package is namespaced under `Arifur9993\AttendanceEngine\` and ships with PSR-4 autoloading.

---

⚡ 60-second quick start
-----------------------

[](#-60-second-quick-start)

```
use Arifur9993\AttendanceEngine\Engine;
use Arifur9993\AttendanceEngine\Types\Punch;
use Arifur9993\AttendanceEngine\Types\ShiftConfig;
use Arifur9993\AttendanceEngine\Types\ResolveDayInput;

$result = Engine::resolveDay(new ResolveDayInput(
    date: '2026-06-01',
    punches: [
        new Punch(at: '2026-06-01T08:57:00+06:00'),
        new Punch(at: '2026-06-01T18:04:00+06:00'),
    ],
    shift: new ShiftConfig(start: '09:00', end: '18:00', graceIn: 10),
));

echo $result->status;         // 'present'
echo $result->workedMinutes;  // 547
echo $result->lateByMinutes;  // 0
echo $result->otMinutes;      // 4

// JSON-ready for your API
return response()->json($result);   // implements JsonSerializable
```

---

🧠 Real-world recipes
--------------------

[](#-real-world-recipes)

### 1. A whole month, one call

[](#1-a-whole-month-one-call)

```
use Arifur9993\AttendanceEngine\Engine;
use Arifur9993\AttendanceEngine\Types\{ResolveRangeInput, ShiftConfig, AttendancePolicy};

$nineToSix = fn (string $date): ?ShiftConfig =>
    in_array(date('w', strtotime($date)), [0, 6], true)
        ? null                                              // weekend off
        : new ShiftConfig(start: '09:00', end: '18:00', graceIn: 10);

$range = Engine::resolveRange(new ResolveRangeInput(
    from: '2026-06-01',
    to:   '2026-06-30',
    punches: $monthOfPunches,         // flat list — auto-bucketed onto duty-dates
    shiftFor: $nineToSix,
    policy: new AttendancePolicy(tzOffsetMinutes: 360),
    holidays: ['2026-06-15' => true],
));

$summary = Engine::summarize($range);
echo $summary->workedHours();   // 162.5
echo $summary->daysLate;         // 2
echo $summary->daysAbsent;       // 1
```

### 2. Rotating roster from a weekly pattern

[](#2-rotating-roster-from-a-weekly-pattern)

```
use Arifur9993\AttendanceEngine\Engine;
use Arifur9993\AttendanceEngine\Types\ShiftConfig;

$day   = new ShiftConfig(start: '09:00', end: '18:00');
$night = new ShiftConfig(start: '22:00', end: '06:00');

$roster = Engine::generateRoster(
    from: '2026-06-01',
    to:   '2026-06-07',
    pattern: [
        0 => null,    // Sun off
        1 => $day, 2 => $day, 3 => $night, 4 => $night,
        5 => $day, 6 => null, // Sat off
    ],
);

// Now pass it through:
$shiftFor = fn (string $d) => $roster[$d] ?? null;
```

### 3. Round noisy biometric punches before resolving

[](#3-round-noisy-biometric-punches-before-resolving)

```
use Arifur9993\AttendanceEngine\Engine;

$clean = Engine::applyRounding($rawPunches, unitMinutes: 5, strategy: 'nearest');
// 08:57:23 → 08:55:00,  18:04:11 → 18:05:00

$result = Engine::resolveDay(/* ... */ punches: $clean);
```

### 4. Catch a missing-break compliance issue

[](#4-catch-a-missing-break-compliance-issue)

```
$violations = Engine::evaluateBreakCompliance($day, [
    'minBreakMinutes'    => 30,
    'afterWorkedMinutes' => 300,   // 5 hours
]);

if ($violations) {
    Log::warning('Break compliance', ['employee' => $id, 'issues' => $violations]);
}
```

### 5. Drop into Laravel — no provider needed

[](#5-drop-into-laravel--no-provider-needed)

```
// app/Services/AttendanceService.php
use Arifur9993\AttendanceEngine\Engine;

class AttendanceService
{
    public function dayFor(Employee $e, string $date): DayResult
    {
        return Engine::resolveDay(new ResolveDayInput(
            date:    $date,
            punches: $e->punches()->forDate($date)->toEngineList(),
            shift:   $e->shiftFor($date)->toEngineConfig(),
            policy:  app(AttendancePolicy::class),
        ));
    }
}
```

The engine is 100% static &amp; pure — no DI binding required. It plays the same with Symfony, Slim, Mezzio, or plain PHP.

---

📚 API at a glance
-----------------

[](#-api-at-a-glance)

FunctionWhat it does`Engine::resolveDay(ResolveDayInput)` → `DayResult`One day, in/out punches → status + minutes.`Engine::resolveRange(ResolveRangeInput)` → `RangeResult`A whole \[from, to\] window resolved at once, with smart punch bucketing.`Engine::summarize(RangeResult)` → `RangeSummary`Aggregate totals: worked / OT / late / absent / status counts.`Engine::applyRounding(array, unit, strategy)` → `Punch[]`Round punches to nearest / up / down by N minutes.`Engine::evaluateBreakCompliance(DayResult, rule)` → `string[]`Returns a list of compliance violations (empty = clean).`Engine::generateRoster(from, to, weeklyPattern)` → `array`Expand a weekly 0..6 pattern into a date → shift map.All value objects implement `JsonSerializable` and `->toArray()`, so they round-trip cleanly through HTTP responses, queues, and caches.

---

⏰ Timezone discipline (read this)
---------------------------------

[](#-timezone-discipline-read-this)

ISO-8601 timestamps **must** carry an explicit offset:

```
✅ 2026-06-01T08:57:00+06:00
✅ 2026-06-01T08:57:00Z
✅ 2026-06-01T08:57:00-05:00
❌ 2026-06-01T08:57:00              ← throws TimeParseError
❌ 2026-06-01 08:57                 ← throws TimeParseError

```

The engine **never** calls `date_default_timezone_get()`. Every calculation is anchored on the offset you provide. This is on purpose — it's the only way to stop "but it worked on staging" timezone bugs from sneaking into payroll.

---

🆚 Same answers as the TypeScript engine
---------------------------------------

[](#-same-answers-as-the-typescript-engine)

This is the PHP twin of [`@attendance-engine/core`](https://www.npmjs.com/package/@attendance-engine/core). The two ports share fixtures and contracts. If your Next.js frontend and your Laravel backend both compute `workedMinutes` for the same punches, you get **the same integer**, to the minute.

FeatureTypeScript (`@attendance-engine/core`)PHP (this package)`resolveDay`✅✅`resolveRange`✅✅`summarize`✅✅`generateRoster`✅✅`applyRounding`✅✅`evaluateBreakCompliance`✅✅ (generic)Jurisdiction packs (CA, EU)🔜🔜Cross-port fixture paritypartialpartial---

💼 Who's using it
----------------

[](#-whos-using-it)

`attendance-engine` is the calculation core behind production HRMS / payroll setups handling:

- Mixed day/night/rotational rosters across Bangladesh, India, UAE, and SE Asia.
- Multi-tenant SaaS deployments where each tenant brings its own grace window + OT policy.
- Compliance-sensitive sectors (manufacturing, healthcare, retail) where "approximately worked" isn't good enough.

If you ship it in production, [open an issue](https://github.com/arifur9993/attendance-engine-php/issues) — I'll add your logo here.

---

🤝 Contributing
--------------

[](#-contributing)

PRs welcome. The golden rule: any behavior change comes with a test that locks it in.

- Setup, style, and pull-request checklist → [`CONTRIBUTING.md`](CONTRIBUTING.md)
- Releasing / Packagist sync → [`PUBLISHING.md`](PUBLISHING.md)
- Changelog → [`CHANGELOG.md`](CHANGELOG.md)

```
git clone https://github.com/arifur9993/attendance-engine-php.git
cd attendance-engine-php
composer install
composer test       # Pest
composer analyse    # PHPStan level 8
composer format     # Pint
```

---

📦 Compatibility
---------------

[](#-compatibility)

PHP8.2 · 8.3 · 8.4FrameworksLaravel 10/11/12, Symfony 6/7, Slim 4, vanilla PHPDependencies**none** — pure PHP, stdlib only---

👤 Author
--------

[](#-author)

Built by **[Md. Arifur Rahman (@arifur9993)](https://github.com/arifur9993)** — same author as the TypeScript engine. Comments, bug reports, feature ideas: [open an issue](https://github.com/arifur9993/attendance-engine-php/issues) or ping me on GitHub.

If this package saved you a week of "but the punches are in two timezones" debugging, [⭐ star the repo](https://github.com/arifur9993/attendance-engine-php) — it's the only marketing this library will ever do.

---

📜 License
---------

[](#-license)

MIT — see [`LICENSE`](LICENSE). Use it commercially, fork it, vendor it, ship it.

###  Health Score

34

—

LowBetter than 75% of packages

Maintenance86

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

72d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/57087293?v=4)[Md. Arifur Rahman](/maintainers/arifur9993)[@arifur9993](https://github.com/arifur9993)

---

Top Contributors

[![arifur9993](https://avatars.githubusercontent.com/u/57087293?v=4)](https://github.com/arifur9993 "arifur9993 (2 commits)")

---

Tags

symfonylaravelhrtime trackingcompliancehr techattendancepayrollbiometrictimesheetShiftrosterpunch clockovertimeworkforcewage-and-hourovernight-shift

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/arifur9993-attendance-engine/health.svg)

```
[![Health](https://phpackages.com/badges/arifur9993-attendance-engine/health.svg)](https://phpackages.com/packages/arifur9993-attendance-engine)
```

###  Alternatives

[kris/laravel-form-builder

Laravel form builder - symfony like

1.7k2.3M46](/packages/kris-laravel-form-builder)[brexis/laravel-workflow

Integerate Symfony Workflow component into Laravel.

287127.4k](/packages/brexis-laravel-workflow)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
