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

Datetime Laravel Package

psx/datetime

PSX DateTime is a PHP library for working with dates and times, offering utilities for parsing, formatting, timezone conversion, and date arithmetic. It integrates with the PSX framework and provides a lightweight, object-oriented API for common temporal tasks in PHP applications.

View on GitHub
Deep Wiki
Context7

Getting Started

psx/datetime is a plain Composer library — no service provider, no config file, no facade. Install it and it's immediately usable:

composer require psx/datetime

Check composer.json for the PHP floor first; the package tracks modern PHP but you don't want to discover a constraint clash mid-sprint on a legacy app.

Where to look first: the src/ directory. The classes map 1:1 onto java.time, so if you know the Java API you already know this one:

  • DateTime — the workhorse, an immutable date-time with offset/zone; start here.
  • LocalDate, LocalTime, LocalDateTime — zone-less value objects for domain data.
  • Instant, Duration, Period — machine time, time-based amounts, date-based amounts.
  • ZoneId, ZoneOffset, DayOfWeek, Month — supporting types.

First use case: parse an ISO-8601 string from a third-party API, do arithmetic, and format it back out — without Carbon's mutability footguns:

use PSX\DateTime\DateTime;
use PSX\DateTime\Duration;

$dt    = DateTime::parse('2024-03-04T12:00:00+00:00');
$later = $dt->plus(Duration::ofHours(3));   // new instance, $dt untouched

echo $dt->format('Y-m-d H:i:sP');     // 2024-03-04 12:00:00+00:00
echo $later->format('Y-m-d H:i:sP'); // 2024-03-04 15:00:00+00:00

For date-only domain concepts (billing dates, birthdays, report periods), reach for LocalDate instead — it makes "no time component" explicit in your type signatures:

use PSX\DateTime\LocalDate;

$start = LocalDate::of(2024, 3, 4);
$end   = $start->plusDays(10);

Implementation Patterns

Convert at the boundary, keep PSX types inside. Laravel — Eloquent casts, Blade's diffForHumans, the date validation rule, Carbon::setTestNow() — speaks Carbon. Treat psx/datetime as an internal domain type and translate on the way in and out. DateTime is a \DateTimeImmutable subclass, so it satisfies \DateTimeInterface and can be handed to Carbon via Carbon::instance($psxDateTime) for presentation; LocalDate/LocalTime bridge through atTime()/atStartOfDay().

Custom Eloquent cast as the adapter. This is the single highest-value integration point:

use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use PSX\DateTime\LocalDate;

class LocalDateCast implements CastsAttributes
{
    public function get($model, $key, $value, $attributes): ?LocalDate
    {
        return $value === null ? null : LocalDate::parse($value);
    }

    public function set($model, $key, $value, $attributes): ?string
    {
        return $value === null ? null : $value->format('Y-m-d');
    }
}

Map LocalDate → DATE columns, LocalDateTime → DATETIME (no zone), and DateTime/ZonedDateTime → TIMESTAMP or a string column normalised to UTC. Never store a zoned value in a naive DATETIME and hope.

Validation rule for inbound ISO strings. Rather than letting a parse exception escape as a 500, wrap the parser:

Validator::extend('iso_date', fn ($attr, $value) => rescue(
    fn () => LocalDate::parse($value), fn () => null
) !== null);

JSON resources. The types serialise to ISO-8601, which is exactly what you want on the wire. Return them directly from API resources or cast to (string); do not let them fall through to json_encode on a wrapper object you haven't checked.

Calendar-aware diffs with Period. Period::between($start, $end) gives you years/months/days — the right primitive for "3 months and 2 days remaining" wording, pro-rata billing, and subscription proration. Use Duration::between() when you genuinely mean elapsed time (SLA timers, cache TTLs).

Business-calendar rules. DayOfWeek/Month make weekday logic readable and avoid magic integers scattered through a service class.

Gotchas and Tips

Everything is immutable — reassign. $date->plusDays(1); on its own line is a no-op. Coming from Carbon's mutating addDay(), this is the #1 source of silent bugs. On the plus side, no ->copy() before branching logic.

Period ≠ Duration. Duration is time-based (seconds, nanos); Period is date-based (years, months, days). Passing one where plus() expects the other either fails or gives you a result you didn't intend. Pick based on whether DST and month lengths should affect the answer.

Java overflow semantics differ from PHP's. LocalDate::of(2024, 1, 31)->plusMonths(1) clamps to 2024-02-29. \DateTime would roll to March 2. This is a feature for billing cycles and a regression if you're porting existing Carbon date math — write a test pinning the behaviour.

DST arithmetic follows Java rules. Adding one day across a DST boundary preserves local wall time; adding 24 hours as a Duration does not. Be explicit about which you mean, especially for schedulers and reminders.

No Carbon::setTestNow() equivalent. You can't freeze now(). Inject DateTime values as constructor/method dependencies instead of calling now() deep in a service, or your tests will be flaky. This is good design pressure, but it's a real migration cost.

(string) casts surprise people. Most types stringify to ISO-8601, which is fine in logs but wrong when a human expects d/m/Y. Always call format() explicitly at the display layer.

Parse failures throw. Wrap untrusted input in validation or rescue(); don't let a malformed third-party payload blow up a queued job.

Low-traffic package — pin and wrap it. Two stars and zero dependents means the docs are thin and the API surface is whatever the source says. Pin an exact version in composer.json, keep your PSX usage behind a small adapter (casts, a Clock-style wrapper, resource transformers), and you retain the option to swap back to Carbon without touching the domain layer.

Extension points are plain PHP. Subclass or wrap these types in your own value objects, implement JsonSerializable/Stringable for custom serialisation, and lean on Laravel's cast and rule contracts as the integration seam. There is no framework magic to fight — which is both the package's appeal and the reason it won't feel native in a Laravel codebase without that adapter layer.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core