lessThan($from)) { throw new InvalidArgumentException('The end of a date range cannot precede its start.'); } } public static function preset(string $preset): self { $now = Carbon::now(); return match ($preset) { 'today' => new self($now->copy()->startOfDay(), $now->copy()->endOfDay()), 'week' => new self($now->copy()->startOfWeek(), $now->copy()->endOfWeek()), 'month' => new self($now->copy()->startOfMonth(), $now->copy()->endOfMonth()), 'year' => new self($now->copy()->startOfYear(), $now->copy()->endOfYear()), 'all' => new self(Carbon::createFromTimestamp(0), $now->copy()->endOfDay()), default => throw new InvalidArgumentException("Unknown date range preset [{$preset}]."), }; } /** * @return array */ public static function presets(): array { return [ 'today' => 'Today', 'week' => 'This Week', 'month' => 'This Month', 'year' => 'This Year', 'all' => 'All Time', ]; } public function isBucketableByDay(): bool { return $this->from->copy()->startOfDay()->diffInDays($this->to->copy()->startOfDay()) < self::MAX_DAYS; } /** * Every day the range touches, as Y-m-d, so callers can zero-fill empty buckets. * * @return array */ public function days(): array { $days = []; $cursor = $this->from->copy()->startOfDay(); $last = $this->to->copy()->startOfDay(); if (! $this->isBucketableByDay()) { throw new InvalidArgumentException( 'A range wider than '.self::MAX_DAYS.' days cannot be bucketed by day; bucket by month instead.' ); } while ($cursor->lessThanOrEqualTo($last)) { $days[] = $cursor->toDateString(); $cursor->addDay(); } return $days; } }