bitandblack/duration
bitandblack/duration provides a small PHP/Laravel utility for working with time spans: create, parse, and format durations in a readable way, convert between units, and simplify common duration calculations for apps, jobs, and reports.
Installation
composer require bitandblack/duration
The package is framework-agnostic but Laravel-friendly. No additional configuration is needed beyond autoloading.
First Use Case: Creating and Using Durations
use BitAndBlack\Duration\Duration;
// Create a duration (e.g., 2 hours 30 minutes)
$duration = Duration::create(2, 'hours')->add(Duration::create(30, 'minutes'));
// Or from total seconds
$duration = Duration::fromSeconds(9000); // 9000 seconds = 2.5 hours
// Format for display
echo $duration->format('H:i:s'); // Outputs: "02:30:00"
Where to Look First
Duration – The main class for all operations.Duration::create(), Duration::fromSeconds(), Duration::fromMinutes(), etc.'seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years'.add(), subtract(), multiply(), divide(), getSeconds(), format().// From units
$duration = new Duration(1, 'day');
// From total seconds
$duration = Duration::fromSeconds(3600); // 1 hour
// From a string (if supported)
$duration = Duration::fromString('2 hours 30 minutes');
// Add/subtract durations
$total = $duration->add(Duration::create(30, 'minutes'));
$remaining = $duration->subtract(Duration::create(1, 'hour'));
// Scale durations
$doubled = $duration->multiply(2);
$halved = $duration->divide(2);
if ($duration->isGreaterThan(Duration::create(1, 'hour'))) {
// Handle longer durations
}
if ($duration->equals(Duration::fromSeconds(3600))) {
// Handle exact matches
}
$seconds = $duration->getSeconds(); // Total in seconds
$hours = $duration->getHours(); // Total in hours (fractional)
$minutes = $duration->getMinutes(); // Total in minutes (fractional)
// Human-readable format
echo $duration->format('H:i:s'); // "01:00:00" for 1 hour
// Custom format (e.g., for logs)
echo $duration->format('Y-m-d H:i:s'); // "00-00-01 01:00:00" for 1 day 1 hour
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($request->all(), [
'duration' => 'required|duration', // Custom rule for duration validation
]);
// Store as total seconds in a bigint column
$model->duration_seconds = $duration->getSeconds();
return response()->json([
'duration' => [
'seconds' => $duration->getSeconds(),
'human' => $duration->format('H:i:s'),
],
]);
use Carbon\Carbon;
$carbonDate = Carbon::now()->add($duration->toDateInterval());
Bind the Duration class to Laravel’s service container for dependency injection:
// app/Providers/AppServiceProvider.php
use BitAndBlack\Duration\Duration;
public function register()
{
$this->app->bind(Duration::class, function () {
return new Duration();
});
}
Create a facade for cleaner syntax:
// app/Facades/Duration.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Duration extends Facade
{
protected static function getFacadeAccessor()
{
return 'duration';
}
}
Register the facade in config/app.php:
'Duration' => App\Facades\Duration::class,
Extend Laravel’s validation rules:
// app/Rules/DurationRule.php
namespace App\Rules;
use BitAndBlack\Duration\Duration;
use Illuminate\Contracts\Validation\Rule;
class DurationRule implements Rule
{
public function passes($attribute, $value)
{
try {
$duration = Duration::fromString($value);
return true;
} catch (\Exception $e) {
return false;
}
}
public function message()
{
return 'The :attribute must be a valid duration.';
}
}
Usage:
$validator->rule(new DurationRule());
Unit Precision for Months/Years
add() or subtract() may yield unexpected results for these units.days for precise calculations or handle edge cases manually.Timezone Sensitivity
Carbon, ensure timezone consistency:
$carbonDate = Carbon::now()->setTimezone('UTC')->add($duration->toDateInterval());
String Parsing Limitations
fromString() method may not support all formats (e.g., "2h 30m" vs. "2 hours 30 minutes"). Test thoroughly with your expected inputs.Immutability
add() return new instances). This is good practice but requires adjusting to functional-style updates:
$newDuration = $duration->add(Duration::create(1, 'hour'));
// Original $duration remains unchanged
Database Storage Quirks
bigint) for precision.text) for display purposes only.Performance with Large Durations
Verify Unit Inputs
'hours', not hours). Case-sensitive in some implementations.Check for Silent Failures
fromString()) may throw exceptions for invalid inputs. Wrap in try-catch blocks:
try {
$duration = Duration::fromString($input);
} catch (\InvalidArgumentException $e) {
// Handle invalid duration
}
Log Duration Objects
__toString() for debugging:
$duration->__toString(); // May not work by default; use $duration->format('H:i:s') instead.
Validate Carbon Integration
DateInterval, ensure the result matches expectations:
$interval = $duration->toDateInterval();
var_dump($interval->format('%h hours %i minutes')); // Debug output
Custom Units
// Example: Add a "workday" unit (5 business hours)
$duration->add(Duration::create(1, 'workday')); // Requires custom logic
Localization
$duration->format('H:i:s', ['locale' => 'fr_FR']); // Hypothetical
Serialization
JsonSerializable for API responses:
class Duration implements JsonSerializable
{
public function jsonSerialize()
{
return [
'seconds' => $this->getSeconds(),
'human' => $this->format('H:i:s'),
];
}
}
Laravel Events
How can I help you explore Laravel packages today?