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

Php Duration Laravel Package

khill/php-duration

A PHP library for parsing and formatting durations. Turn strings like "1h 30m" or "2 days" into seconds, and convert seconds back into readable, human-friendly duration strings, with support for localization.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require khill/php-duration

There is no service provider, config file, or facade to register — it's a single plain PHP class you instantiate directly.

First look

  • README.md — the entire public API is documented in about 30 lines, which tells you the surface area is small.
  • src/Duration.php — read this file end to end before using it in production. It's short enough to audit in one sitting, and because the package has had no release since March 2021 you should treat the source, not the docs, as the source of truth.
  • tests/ — the fastest way to confirm edge-case behavior (negative values, zero, >24h inputs, malformed strings) without guessing.

First use case

The canonical win is normalizing a duration that arrives in one shape and needs to be displayed or stored in another:

use Khill\Duration\Duration;

$d = new Duration('7:32:12');

echo $d->toSeconds();   // 27132
echo $d->humanize();    // "7 hours 32 minutes 12 seconds"
echo $d->toMinutes();   // 452
echo $d->toHours();     // 7

Typical entry point: a duration_seconds column or an HH:MM:SS field from a media/CSV import that needs to render as human text in the UI and as an integer elsewhere. Reach for this package the first time you find yourself writing explode(':', $value) and multiplying by 60.

Implementation Patterns

Normalize at the boundary, not in the view

Parse whatever the outside world sends into seconds as soon as it enters your app, and keep seconds as the canonical stored representation.

class Lesson extends Model
{
    public function setDurationAttribute($value): void
    {
        $this->attributes['duration_seconds'] = (new Duration($value))->toSeconds();
    }

    public function getDurationForHumansAttribute(): string
    {
        return (new Duration($this->duration_seconds))->humanize();
    }
}

This keeps the third-party dependency at the edges of your codebase. If the package is abandoned, you change two accessors, not forty call sites.

Wrap it in a tiny value object

Given zero dependents and a 2021 last release, isolate it behind your own class so the rest of the app never imports Khill\Duration\Duration:

final class TimeSpan
{
    private function __construct(private int $seconds) {}

    public static function fromColon(string $value): self
    {
        return new self((new Duration($value))->toSeconds());
    }

    public static function fromSeconds(int $seconds): self
    {
        return new self($seconds);
    }

    public function humanize(): string
    {
        return (new Duration($this->seconds))->humanize();
    }

    public function seconds(): int
    {
        return $this->seconds;
    }
}

Your tests then pin the behavior you actually rely on, and swapping to CarbonInterval later is a one-file change.

Aggregate in seconds, format once

Never sum human-readable strings. Sum the raw seconds in SQL, then format a single value:

$totalSeconds = $playlist->lessons()->sum('duration_seconds');
$label = (new Duration($totalSeconds))->humanize();

This is also far cheaper than instantiating a Duration per row in a loop over a large collection.

API resources and Blade

Both layers just want a string, so format in an accessor or resource rather than in the template:

// LessonResource
'duration' => (new Duration($this->duration_seconds))->humanize(),
'duration_seconds' => $this->duration_seconds,

Emitting both is worth it — clients that need to sort, chart, or compute get the integer; clients that render get the label.

Validation before construction

Treat the constructor as a parser you don't fully trust. Validate the shape first with a rule, then construct:

$request->validate([
    'duration' => ['required', 'regex:/^\d{1,3}:[0-5]\d:[0-5]\d$/'],
]);

If the incoming value is already an integer count of seconds, cast to int and skip string parsing entirely — that path has the least ambiguity.

Rounding for reports

toMinutes() / toHours() give you whole units, which is right for "how many minutes of content is this" but wrong for billing or anything where the remainder matters. For those, keep toSeconds() and do your own division with explicit rounding, so the rounding policy lives in your code rather than being an implicit side effect of the library.

Gotchas and Tips

Unmaintained since 2021 — pin and test

The last release predates PHP 8.1+ and there's no active maintenance. Pin the version in composer.json, and add a small test file covering the exact conversions you depend on. If a future PHP version triggers a deprecation, that test suite tells you immediately instead of a user noticing a wrong number on screen.

Truncation, not rounding

Whole-unit converters (toMinutes, toHours, toDays) floor the value. toMinutes() on '1:00:59' gives you 60, not 61. This is the single most common source of off-by-one reports in duration UIs — if the label matters, decide explicitly whether you want floor, round, or ceil, and compute from toSeconds().

Zero and pluralization edge cases

Confirm how humanize() renders 0, 1, and values with zero intermediate units (e.g. '1:00:05'). Depending on the implementation you may get "5 seconds" with the hours/minutes omitted, or units you didn't expect. Assert the exact strings you need in tests rather than eyeballing them once — output formatting is the most likely thing to differ from your assumption.

Hours beyond 24 in colon format

If you feed it '36:00:00' for overtime or long-running jobs, verify it parses as 36 hours rather than wrapping or failing. Don't assume a H:MM:SS parser accepts two-digit-plus hours just because it's obvious to you. Test it before building a feature on it.

Malformed input handling

There's no schema validation inside the package. Garbage in may yield 0, may throw, or may silently parse a prefix — and different behaviors here become corrupt data in your database. Always validate the shape upstream, and consider rejecting rather than defaulting, since 0 is a legitimate duration and a silent fallback is indistinguishable from real data.

Not a date library

This does not do timezones, calendar math, business days, or localization. It's arithmetic on a span. If you find yourself wanting addDays() with DST awareness, you want CarbonInterval/DateInterval, not this. Mixing the two in one codebase is fine, but keep the roles clear: Duration for display and span conversion, Carbon for calendar semantics.

Immutability is unclear — don't rely on it

Before chaining any mutating methods (add/subtract style helpers), check the source to see whether they return a new instance or modify in place. Guard against subtle bugs by always assigning the result to a variable and, if it's cheap in your context, constructing fresh instances rather than reusing a variable across transformations.

Debugging

When a duration looks wrong, the only thing to inspect is the second count. Dump toSeconds() on both the input and the expected value and compare integers — that collapses every formatting and parsing question into one number and tells you instantly whether the bug is in parsing, in your math, or in presentation.

Extension points

There are none — no events, no config, no container bindings. If you need custom formatting (e.g. "1h 12m" compact style, or localized unit names), subclass the class or, preferably, format from toSeconds() in your own presenter. Subclassing ties you harder to an unmaintained package, so the wrapper approach is the safer long-term bet.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource