Reader Stacks

Working With Dates, Times, and Timezones in Laravel (Carbon)

Every Eloquent timestamp is already a Carbon instance — the part that actually causes bugs is timezone handling: what gets stored, what gets displayed, and where the conversion between them should happen.

Laravel uses Carbon (a thin, fluent wrapper around PHP's native DateTime) for every date-related value — created_at, updated_at, and any column cast to datetime come back from Eloquent as ready-to-use Carbon instances, not plain strings.

1. Getting the current date and time

$now = now();               // Carbon instance, current timestamp
$today = today();           // Carbon instance, midnight today

echo $now->format('Y-m-d H:i:s');
echo $now->diffForHumans(); // "3 minutes ago", "in 2 days"
echo $now->dayName;         // "Monday"

2. Formatting

$now->format('F j, Y');         // "March 9, 2023"
$now->toDateString();           // "2023-03-09"
$now->toDateTimeString();       // "2023-03-09 14:30:00"
$now->isoFormat('dddd, MMMM D'); // locale-aware formatting, e.g. "Thursday, March 9"

3. Comparing and calculating

$now->isPast();
$now->isFuture();
$now->addDays(7);
$now->subMonths(1);
$now->diffInDays($otherDate);

4. The part that actually causes bugs: timezones

Laravel's own default (config/app.php's timezone key, usually UTC) controls what timezone dates are stored and processed in internally — this should almost always stay UTC, regardless of where users actually are, and conversion to a local timezone should happen at display time, not storage time.

// Storing: always UTC, regardless of the visitor's timezone
$order->created_at = now(); // stored in UTC

// Displaying: convert to the user's timezone only when rendering
$order->created_at->setTimezone($user->timezone)->format('M j, g:i A');

Storing timestamps already converted to a specific user's local timezone is the mistake that causes real problems later — a database full of timestamps in mixed or user-specific timezones can't be reliably sorted, compared, or aggregated across users. Store everything in UTC; convert only for display.

5. Getting a user's timezone dynamically

// A simple approach: store the user's chosen timezone on their profile
$user->timezone = 'America/New_York';

// Then anywhere a date needs to be shown to that user:
$post->published_at->setTimezone($user->timezone ?? 'UTC')->format('M j, Y g:i A');

PHP's DateTimeZone (which Carbon uses internally) supports the full IANA timezone database (America/New_York, Europe/London, etc.) — a raw UTC offset like -05:00 is a weaker choice for a stored user preference, since it doesn't automatically account for daylight saving time the way a named IANA zone does.

6. Querying by date range

Order::whereBetween('created_at', [
    now()->subDays(7)->startOfDay(),
    now()->endOfDay(),
])->get();

Order::whereDate('created_at', today())->get(); // just today, ignoring time

whereDate() compares only the date portion, ignoring the time — useful for "orders placed today" style queries without needing to manually construct a start/end-of-day range every time.

Topics: Database Queries & Eloquent