brick/date-time
Immutable, ISO-8601–focused date/time API for PHP 8.2+ built on top of native DateTime, adding missing types like LocalDate, LocalTime, YearMonth, and MonthDay. Inspired by Java’s JSR-310, well-tested, production-ready, Composer installable.
Installation:
composer require brick/date-time
Requires PHP 8.2+.
First Use Case:
Replace native DateTime with Brick\DateTime for immutable operations:
use Brick\DateTime\LocalDate;
use Brick\DateTime\TimeZone;
$today = LocalDate::now(TimeZone::utc());
echo $today; // Outputs: YYYY-MM-DD (e.g., 2023-10-05)
Key Classes to Explore:
LocalDate (immutable dates like 2023-10-05).ZonedDateTime (timezone-aware, like PHP’s DateTime but immutable).Duration/Period (time intervals, e.g., PT2H30M).Clock utilities (for testing, e.g., FixedClock).Where to Look First:
$date = LocalDate::of(2023, 10, 5);
$nextMonth = $date->withMonth(11); // Returns new instance
$zoned = ZonedDateTime::of(2023, 10, 5, 12, 0, 0, TimeZone::of('Europe/London'));
$utc = $zoned->withTimeZone(TimeZone::utc());
$date = LocalDate::parse('2023-10-05');
$zoned = ZonedDateTime::parse('2023-10-05T12:00:00+01:00');
$duration = Duration::ofHours(2);
$futureDate = $date->plus($duration);
$interval = Interval::between($date1, $date2);
echo $interval->toDuration(); // PT... (ISO 8601)
if ($date1->isBefore($date2)) { ... }
use Brick\DateTime\DefaultClock;
DefaultClock::freeze(Instant::of(1696476800)); // 2023-10-05T00:00:00Z
$date = LocalDate::now(TimeZone::utc()); // Always returns 2023-10-05
DefaultClock::travelTo(Instant::of(1696563200)); // 2023-10-06T00:00:00Z
DefaultClock::travelBy(Duration::ofDays(1)); // +1 day
DefaultClock::reset(); // Back to system time
Carbon\Carbon with ZonedDateTime for immutable operations:
$now = ZonedDateTime::now(); // Instead of Carbon::now()
LocalDate for database queries:
$results = Model::whereDate('created_at', $date->toDateTimeString())->get();
LocalDate::parse():
$date = LocalDate::parse($request->input('date'));
$range = LocalDateRange::between($start, $end);
if ($range->contains($date)) { ... }
$nextBusinessDay = $date->nextOrSameDayOfWeek(DayOfWeek::MONDAY);
$tz = TimeZone::of('America/New_York');
$localTime = $zoned->toLocalTime($tz);
Service Providers:
Bind DefaultClock to a singleton for global test control:
$this->app->singleton(DefaultClock::class, function () {
return new FixedClock(Instant::now()); // Or system clock
});
Model Casting:
Use Brick\DateTime\Casts\LocalDate in Eloquent:
use Brick\DateTime\Casts\LocalDate;
class Event extends Model {
protected $casts = [
'event_date' => LocalDate::class,
];
}
API Responses: Serialize dates to ISO strings:
return response()->json([
'event_date' => $event->event_date->toIsoString(),
]);
Validation:
Use LocalDate::parse() in Form Requests:
$this->validate($request, [
'date' => 'required|date_format:Y-m-d',
]);
$date = LocalDate::parse($request->date);
Immutability:
// ❌ Loses reference
$date->plus(Duration::ofDays(1))->minus(Duration::ofHours(1));
// ✅ Correct
$newDate = $date->plus(Duration::ofDays(1))->minus(Duration::ofHours(1));
Time Zone Handling:
ZonedDateTime is not equivalent to PHP’s DateTime. Use TimeZone::of() explicitly:
// ❌ Ambiguous
$zoned = new ZonedDateTime('2023-10-05T12:00:00');
// ✅ Explicit
$zoned = ZonedDateTime::of(2023, 10, 5, 12, 0, 0, TimeZone::utc());
Clock Leaks:
reset() DefaultClock in tests causes flaky tests:
// ❌ Test pollution
DefaultClock::freeze(...);
// ... other tests run with frozen time
// ✅ Reset in tearDown()
public function tearDown(): void {
DefaultClock::reset();
}
Deprecated Methods:
DayOfWeek::monday(); use DayOfWeek::MONDAY (enum):
// ❌ Deprecated
$day = DayOfWeek::monday();
// ✅ Modern
$day = DayOfWeek::MONDAY;
Duration vs. Period:
Duration = time-based (e.g., PT2H30M).Period = calendar-based (e.g., P2Y3M4D).$date->plus(Duration::ofDays(32)); // Correct (32 days)
$date->plus(Period::ofDays(32)); // May wrap months (e.g., Jan 31 + 32 days = Mar 4)
Parser Strictness:
parse() throws DateTimeParseException on invalid input:
try {
$date = LocalDate::parse('invalid-date');
} catch (DateTimeParseException $e) {
// Handle error
}
PHP 8.2+ Features:
Clock Issues:
DefaultClock is overridden:
echo DefaultClock::get()->getClass(); // Should be SystemClock in production
Time Zone Confusion:
toTimeZone() to debug:How can I help you explore Laravel packages today?