A user's 7:07 PM walk on December 27th appeared on December 28th in the planner. The walk timestamp was stored in UTC (2025-12-28T00:07:00Z), and the API extracted the date without converting to the user's timezone. I audited the entire codebase and found 14 locations with the same bug.

The Patterns

Pattern 1: Carbon::today() without timezone. Controllers using Carbon::today() got the server's UTC date, not the user's local date:

// BEFORE: server timezone (UTC)
$today = Carbon::today();

// AFTER: user's timezone
$today = Carbon::today($user->timezone);

Pattern 2: The whereDate() trap. Laravel's whereDate() extracts the date from a UTC timestamp column, which can return wrong results for users behind UTC:

// BEFORE: compares UTC date portion -- wrong for EST users after 7pm
$query->whereDate('created_at', $localDate);

// AFTER: compare within the user's full local day range
$startOfDay = Carbon::parse($localDate, $user->timezone)->startOfDay()->utc();
$endOfDay = Carbon::parse($localDate, $user->timezone)->endOfDay()->utc();
$query->whereBetween('created_at', [$startOfDay, $endOfDay]);

Pattern 3: Console commands with no user context. Scheduled commands like GenerateRecurringTasks used server time. Fix: iterate over users, convert to each user's timezone.

The Count

Location Category
5 API controllers Carbon::today() without timezone
3 console commands Server timezone assumed
3 query builders whereDate() on UTC columns
3 mobile utilities toISOString().split('T')[0] (covered in a previous post)

The fix for each is the same principle: always convert UTC to the user's timezone before extracting a date. Search your codebase for Carbon::today() and whereDate() -- you might be surprised.