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

Duration Bundle Laravel Package

kachnitel/duration-bundle

A Laravel/PHP package for working with and formatting durations, helping you convert time values into readable strings and handle duration logic cleanly in your applications.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a v0.0.1 package, so treat the API as unstable and pin it ("vendor/duration": "0.0.1" exact, not ^0.0.1) until 1.0.

Install and orient yourself

composer require vendor/duration

There is no service provider or config file to publish in the core — that is the first thing worth knowing. The release is layered:

Layer Relevant to Laravel?
DurationUtil (parse/format, seconds, DateInterval) Yes — this is the package for you
DurationType (Symfony form type) No, unless you run Symfony Forms in Laravel
Twig extension (duration, toSeconds, hhmm) No, unless you use Twig via a bridge

So read DurationUtil and its unit tests first. The test suite is effectively the API documentation for edge cases (negative values, HH:MM vs HH:MM:SS, overflow past 24h) that the README examples won't cover.

First use case: user-entered durations on a time-tracking feature

The highest-value day-one win is normalising messy human input into a single storable representation — integer seconds — and formatting it back for display:

use Vendor\Duration\DurationUtil;

$seconds = DurationUtil::toSeconds($request->input('duration')); // "1h 30m", "01:30", "5400"
$display = DurationUtil::hhmm($seconds);                          // "01:30"

If all you need is toSeconds() on input and hhmm() on output, you are using the package correctly. Everything below is about wrapping it so the rest of the app never touches the package directly.

Implementation Patterns

1. Store integers, format at the edges

Persist seconds (unsignedInteger) and never store the raw string. This keeps aggregation, sorting, and arithmetic in SQL, and makes a future swap of the package a non-event.

$table->unsignedInteger('budget_seconds')->default(0);

2. Wrap DurationUtil in one app-owned service

Given 0.0.1 status, isolate the dependency. One class, one place to change:

final class Duration
{
    public static function parse(?string $input): int
    {
        return DurationUtil::toSeconds((string) $input); // throws on invalid
    }

    public static function parseOrNull(?string $input): ?int
    {
        try { return static::parse($input); } catch (\Throwable) { return null; }
    }

    public static function format(int $seconds): string
    {
        return DurationUtil::hhmm($seconds);
    }
}

3. Custom Eloquent cast for read/write symmetry

class DurationCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes): ?string
    {
        return $value === null ? null : Duration::format((int) $value);
    }

    public function set($model, $key, $value, $attributes): ?int
    {
        return $value === null ? null : Duration::parse($value);
    }
}

4. Normalise in the form request, not the controller

Use prepareForValidation() so validation rules run against a known shape, then validate the integer rather than the free-text:

protected function prepareForValidation(): void
{
    if ($this->filled('duration')) {
        $this->merge(['duration' => Duration::parseOrNull($this->input('duration'))]);
    }
}

public function rules(): array
{
    return ['duration' => ['required', 'integer', 'min:1', 'max:86400']];
}

5. Handle DateInterval for elapsed/derived time

The DateInterval conversion utilities shine when your source is timestamps rather than typed input — e.g. started_at/finished_at on a timesheet, or a queued job's runtime. Convert the diff, then hand it to the formatter instead of reimplementing %H:%I maths:

$elapsed = DurationUtil::fromDateInterval($startedAt->diff($finishedAt));
Duration::format($elapsed);

6. Expose both representations in API resources

Let clients pick; it costs nothing:

return [
    'duration_seconds' => $this->duration_seconds,
    'duration'         => Duration::format($this->duration_seconds),
];

7. Blade presentation

Skip the Twig extension — add a one-line directive or a component in AppServiceProvider:

Blade::directive('duration', fn ($expr) => "<?php echo e(\\App\\Support\\Duration::format((int) ($expr))); ?>");

8. Aggregation flows

For reports ("time by project this week"), sum seconds in SQL, then format once per row at render. Parsing per row in PHP is the common performance mistake here.

Gotchas and Tips

  • HH:MM is genuinely ambiguous. 01:30 reads equally as 1h30m or 1m30s depending on which parse branch wins. Find out from the tests which the package favours, document your choice, and — if the input is user-facing — disambiguate at the UI layer (separate hour/minute fields, or require a unit suffix). This is the single most likely source of silent bugs.
  • >= 24 hours wraps if formatting goes through DateInterval and uses %H. For durations like "36:00" (long projects, log windows), verify against a >24h case before trusting the output, and fall back to computing total hours yourself if needed.
  • Negative values. Whether -01:30 round-trips depends on parsing and formatting both supporting the sign. Test it; if unsupported, clamp at the boundary rather than letting it reach storage.
  • Invalid input semantics — exception or null? Confirm this early and wrap it once (see parseOrNull). Validation rules that assume null will blow up if the package throws, and vice versa.
  • Locale decimal separators. 1,5h from a European keyboard is a realistic input that a naive parser rejects or truncates. Normalise commas to dots in prepareForValidation().
  • Duration ≠ datetime. Don't do DST/timezone reasoning on a duration; only on the timestamps you derived it from. Mixing Carbon arithmetic that crosses a DST boundary into "elapsed duration" will produce off-by-an-hour surprises.
  • Don't install Symfony/Twig expectations. The DurationType and Twig filters are not Blade/Symfony-in-Laravel features. If you reach for DurationType, you're either on a hybrid stack or on the wrong tool.
  • Pin the version and read the diff on every 0.0.x. Pre-1.0 releases routinely rename methods. Your single wrapper class is the entire cost of such a change — keep it thin.
  • Debugging tip: when a format looks wrong, dump the intermediate — the parsed integer, then the DateInterval — rather than the final string. It immediately tells you whether the fault is in parsing or formatting.
  • Round-trip test everything you rely on. parse(format($n)) === $n over a table of boundary values (0, 59, 60, 3599, 3600, 86399, 86400, 90000) is ten lines of PHPUnit and catches most of the issues above.
  • Extension points are limited by design — no config, no driver. If you need locale-aware output ("1 hr 30 min", "1,5 Stunden"), plan to own the formatter rather than expect it from v0.0.1.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core