Carbon, 'end' => Carbon]] */ public function resolveRange( Request $request, string $tz, int $weekStart, int $weekEnd, string $defaultView, string $defaultDate ): array { $requestView = $request->query('view', $defaultView); $view = in_array($requestView, self::VIEWS, true) ? $requestView : 'month'; $date = $request->query('date', $defaultDate); $anchor = $this->safeDate($date, $tz, $defaultDate); switch ($view) { case 'day': $start = $anchor->copy()->startOfDay(); $end = $anchor->copy()->endOfDay(); break; case 'week': $start = $anchor->copy()->startOfWeek($weekStart); $end = $anchor->copy()->endOfWeek($weekEnd); break; case 'four': $start = $anchor->copy()->startOfDay(); $end = $anchor->copy()->addDays(3)->endOfDay(); break; default: // month $start = $anchor->copy()->startOfMonth(); $end = $anchor->copy()->endOfMonth(); } return [$view, ['start' => $start, 'end' => $end]]; } /** * Calendar grid span differs from logical range (e.g. month padding). */ public function gridSpan(string $view, array $range, int $weekStart, int $weekEnd): array { switch ($view) { case 'day': $start = $range['start']->copy()->startOfDay(); $end = $range['start']->copy()->endOfDay(); break; case 'week': $start = $range['start']->copy()->startOfWeek($weekStart); $end = $range['start']->copy()->endOfWeek($weekEnd); break; case 'four': $start = $range['start']->copy()->startOfDay(); $end = $range['start']->copy()->addDays(3); break; default: // month $start = $range['start']->copy()->startOfMonth()->startOfWeek($weekStart); $end = $range['end']->copy()->endOfMonth()->endOfWeek($weekEnd); } return ['start' => $start, 'end' => $end]; } /** * Navigation dates for header controls. */ public function navDates(string $view, Carbon $start, string $tz): array { $start = $start->copy()->tz($tz); return match ($view) { 'day' => [ 'prev' => $start->copy()->subDay()->toDateString(), 'next' => $start->copy()->addDay()->toDateString(), 'today' => Carbon::today($tz)->toDateString(), ], 'week' => [ 'prev' => $start->copy()->subWeek()->toDateString(), 'next' => $start->copy()->addWeek()->toDateString(), 'today' => Carbon::today($tz)->toDateString(), ], 'four' => [ 'prev' => $start->copy()->subDays(4)->toDateString(), 'next' => $start->copy()->addDays(4)->toDateString(), 'today' => Carbon::today($tz)->toDateString(), ], default => [ 'prev' => $start->copy()->subMonth()->startOfMonth()->toDateString(), 'next' => $start->copy()->addMonth()->startOfMonth()->toDateString(), 'today' => Carbon::today($tz)->toDateString(), ], }; } /** * Title text for the calendar header. */ public function headerTitle(string $view, Carbon $start, Carbon $end): array { $sameDay = $start->isSameDay($end); $sameMonth = $start->isSameMonth($end); $sameYear = $start->isSameYear($end); $strong = $start->format('F'); $span = $start->format('Y'); if ($view === 'day' || $sameDay) { return [ 'strong' => $start->format('F j'), 'span' => $start->format('Y'), ]; } if (in_array($view, ['week', 'four'], true)) { if ($sameMonth && $sameYear) { return [ 'strong' => $start->format('F j') . ' to ' . $end->format('j'), 'span' => $start->format('Y'), ]; } if ($sameYear) { return [ 'strong' => $start->format('F') . ' to ' . $end->format('F'), 'span' => $start->format('Y'), ]; } return [ 'strong' => $start->format('F Y') . ' to ' . $end->format('F Y'), 'span' => null, ]; } return ['strong' => $strong, 'span' => $span]; } /** * Safe date parsing with fallback to a default date string. */ public function safeDate(string $date, string $tz, string $fallbackDate): Carbon { try { return Carbon::createFromFormat('Y-m-d', $date, $tz)->startOfDay(); } catch (\Throwable $e) { return Carbon::createFromFormat('Y-m-d', $fallbackDate, $tz)->startOfDay(); } } }