seriesMode || !$this->startDate || !$this->endDate) { return []; } try { // Ensure timezone is always set - use fallback if not initialized yet $timezone = $this->userTimezone ?: (auth()->user()->timezone ?? 'Europe/Berlin'); $startDate = \Carbon\Carbon::createFromFormat('Y-m-d H:i', $this->startDate . ' ' . $this->startTime, $timezone); $endDate = \Carbon\Carbon::createFromFormat('Y-m-d', $this->endDate, $timezone); // Use custom recurrence when dayOfWeek and dayPosition are set (e.g., "last Friday of month") if ($this->recurrenceDayOfWeek && $this->recurrenceDayPosition) { return $this->generateCustomRecurrenceDates($startDate, $endDate, $timezone, true); } // For weekly recurrence with a specific day of week (no position), // shift start date to the next occurrence of that weekday if ($this->recurrenceType === RecurrenceType::Weekly && $this->recurrenceDayOfWeek) { $dayOfWeekNumber = $this->getDayOfWeekNumber($this->recurrenceDayOfWeek); if ($dayOfWeekNumber !== null) { $adjustedStartDate = $startDate->copy(); // Find the next occurrence of the specified weekday while ($adjustedStartDate->dayOfWeek !== $dayOfWeekNumber) { $adjustedStartDate->addDay(); } // Generate weekly dates from the adjusted start $dates = []; $currentDate = $adjustedStartDate->copy(); while ($currentDate->lessThanOrEqualTo($endDate) && count($dates) < 100) { $dates[] = [ 'date' => $currentDate->copy(), 'formatted' => $currentDate->translatedFormat('l, d.m.Y'), 'time' => $currentDate->format('H:i'), ]; $currentDate->addWeek(); } return $dates; } } // Default: generate dates based on recurrence type $currentDate = $startDate->copy(); $dates = []; while ($currentDate->lessThanOrEqualTo($endDate) && count($dates) < 100) { $dates[] = [ 'date' => $currentDate->copy(), 'formatted' => $currentDate->translatedFormat('l, d.m.Y'), 'time' => $currentDate->format('H:i'), ]; if ($this->recurrenceType === RecurrenceType::Weekly) { $currentDate->addWeek(); } else { $currentDate->addMonth(); } } return $dates; } catch (\Exception $e) { return []; } } private function generateCustomRecurrenceDates(\Carbon\Carbon $startDate, \Carbon\Carbon $endDate, string $timezone, bool $formatted = false): array { $dates = []; // Start from the beginning of the month containing startDate $currentDate = $startDate->copy()->startOfMonth(); // Preserve the time from startDate for the occurrences $time = $startDate->format('H:i:s'); while ($currentDate->lessThanOrEqualTo($endDate) && count($dates) < 100) { $occurrenceDate = $this->findNextOccurrence($currentDate, $timezone); if ($occurrenceDate && $occurrenceDate->lessThanOrEqualTo($endDate)) { // Set the time from startDate, preserving the date $occurrenceWithTime = $occurrenceDate->copy()->setTimeFrom($startDate); // Only add if this is after or on the start date if ($occurrenceWithTime->gte($startDate)) { if ($formatted) { $dates[] = [ 'date' => $occurrenceWithTime, 'formatted' => $occurrenceWithTime->translatedFormat('l, d.m.Y'), 'time' => $time, ]; } else { $dates[] = $occurrenceWithTime; } } // Move to the next month $currentDate = $currentDate->copy()->addMonth(); } else { break; } } return $dates; } private function findNextOccurrence(\Carbon\Carbon $currentDate, string $timezone): ?\Carbon\Carbon { if (!$this->recurrenceDayOfWeek || !$this->recurrenceDayPosition) { return $currentDate; } $dayOfWeek = $this->getDayOfWeekNumber($this->recurrenceDayOfWeek); $dayPosition = $this->getDayPositionNumber($this->recurrenceDayPosition); if ($dayOfWeek === null || $dayPosition === null) { return $currentDate; } // Find the Nth dayOfWeek in the current month $date = $currentDate->copy()->startOfMonth(); if ($dayPosition === -1) { return $date->lastOfMonth($dayOfWeek)->setTime($currentDate->hour, $currentDate->minute, $currentDate->second); } $count = 0; while ($date->month === $currentDate->month) { if ($date->dayOfWeek === $dayOfWeek) { $count++; if ($count === $dayPosition) { return $date->copy()->setTime($currentDate->hour, $currentDate->minute, $currentDate->second); } } $date->addDay(); } // If we didn't find enough occurrences in this month, return null return null; } private function getDayOfWeekNumber(string $day): ?int { return match (strtolower($day)) { 'monday', 'montag' => \Carbon\Carbon::MONDAY, 'tuesday', 'dienstag' => \Carbon\Carbon::TUESDAY, 'wednesday', 'mittwoch' => \Carbon\Carbon::WEDNESDAY, 'thursday', 'donnerstag' => \Carbon\Carbon::THURSDAY, 'friday', 'freitag' => \Carbon\Carbon::FRIDAY, 'saturday', 'samstag' => \Carbon\Carbon::SATURDAY, 'sunday', 'sonntag' => \Carbon\Carbon::SUNDAY, default => null, }; } private function getDayPositionNumber(string $position): ?int { return match (strtolower($position)) { 'first', 'erster' => 1, 'second', 'zweiter' => 2, 'third', 'dritter' => 3, 'fourth', 'vierter' => 4, 'last', 'letzter' => -1, default => null, }; } public function getRecurrenceTypesProperty(): array { return [ RecurrenceType::Weekly, RecurrenceType::Monthly, ]; } public function getDaysOfWeekProperty(): array { return [ 'monday' => __('Montag'), 'tuesday' => __('Dienstag'), 'wednesday' => __('Mittwoch'), 'thursday' => __('Donnerstag'), 'friday' => __('Freitag'), 'saturday' => __('Samstag'), 'sunday' => __('Sonntag'), ]; } public function getDayPositionsProperty(): array { return [ 'first' => __('Erster'), 'second' => __('Zweiter'), 'third' => __('Dritter'), 'fourth' => __('Vierter'), 'last' => __('Letzter'), ]; } #[Validate('required|string|max:255')] public ?string $location = null; #[Validate('required|string')] public ?string $description = null; #[Validate('required|url|max:255')] public ?string $link = null; public function mount(): void { $this->country = request()->route('country', config('app.domain_country')); $this->userTimezone = auth()->user()->timezone ?? 'Europe/Berlin'; $timezone = $this->userTimezone; if ($this->event) { $localStart = $this->event->start->setTimezone($timezone); $this->startDate = $localStart->format('Y-m-d'); $this->startTime = $localStart->format('H:i'); $this->location = $this->event->location; $this->description = $this->event->description; $this->link = $this->event->link; if ($this->event->recurrence_type) { $this->seriesMode = true; $this->recurrenceType = $this->event->recurrence_type; $this->recurrenceDayOfWeek = $this->event->recurrence_day_of_week; $this->recurrenceDayPosition = $this->event->recurrence_day_position; $this->endDate = $this->event->recurrence_end_date ? $this->event->recurrence_end_date->format('Y-m-d') : ''; } } else { // Set default start time to next Monday at 19:00 in user's timezone $defaultStart = now($timezone)->next('Monday')->setTime(19, 0); $this->startDate = $defaultStart->format('Y-m-d'); $this->startTime = $defaultStart->format('H:i'); $this->endDate = $defaultStart->copy()->addMonths(6)->format('Y-m-d'); $this->recurrenceType = RecurrenceType::Weekly; } } public function save(): void { $validationRules = [ 'startDate' => 'required|date', 'startTime' => 'required', 'location' => 'required|string|max:255', 'description' => 'required|string', 'link' => 'required|url|max:255', ]; if ($this->seriesMode) { $validationRules['endDate'] = 'required|date|after:startDate'; $validationRules['recurrenceType'] = 'required'; } $this->validate($validationRules); $timezone = $this->userTimezone; if ($this->seriesMode && !$this->event) { // Create series of events $this->createEventSeries($timezone); } else { // Create or update single event $this->createOrUpdateSingleEvent($timezone); } $this->redirect(route('meetups.landingpage', ['meetup' => $this->meetup, 'country' => $this->country]), navigate: true); } private function createOrUpdateSingleEvent(string $timezone): void { // Combine date and time in user's timezone, then convert to UTC $localDateTime = \Carbon\Carbon::createFromFormat('Y-m-d H:i', $this->startDate . ' ' . $this->startTime, $timezone); $utcDateTime = $localDateTime->setTimezone('UTC'); $data = [ 'start' => $utcDateTime, 'location' => $this->location, 'description' => $this->description, 'link' => $this->link, ]; if ($this->event) { // Update existing event $this->event->update($data); session()->flash('status', __('Event erfolgreich aktualisiert!')); } else { // Create new event $this->meetup->meetupEvents()->create([ ...$data, 'created_by' => auth()->id(), 'attendees' => [], 'might_attendees' => [], ]); session()->flash('status', __('Event erfolgreich erstellt!')); } } private function createEventSeries(string $timezone): void { $startDate = \Carbon\Carbon::createFromFormat('Y-m-d H:i', $this->startDate . ' ' . $this->startTime, $timezone); $endDate = \Carbon\Carbon::createFromFormat('Y-m-d', $this->endDate, $timezone); $eventsCreated = 0; $dates = $this->generateEventDates($startDate, $endDate, $timezone); foreach ($dates as $date) { $utcDateTime = $date->copy()->setTimezone('UTC'); $this->meetup->meetupEvents()->create([ 'start' => $utcDateTime, 'location' => $this->location, 'description' => $this->description, 'link' => $this->link, 'created_by' => auth()->id(), 'attendees' => [], 'might_attendees' => [], ]); $eventsCreated++; } session()->flash('status', __(':count Events erfolgreich erstellt!', ['count' => $eventsCreated])); } private function generateEventDates(\Carbon\Carbon $startDate, \Carbon\Carbon $endDate, string $timezone): array { // Use custom recurrence when dayOfWeek and dayPosition are set (e.g., "last Friday of month") if ($this->recurrenceDayOfWeek && $this->recurrenceDayPosition) { return $this->generateCustomRecurrenceDates($startDate, $endDate, $timezone); } // For weekly recurrence with a specific day of week (no position), // shift start date to the next occurrence of that weekday if ($this->recurrenceType === RecurrenceType::Weekly && $this->recurrenceDayOfWeek) { $dayOfWeekNumber = $this->getDayOfWeekNumber($this->recurrenceDayOfWeek); if ($dayOfWeekNumber !== null) { $adjustedStartDate = $startDate->copy(); while ($adjustedStartDate->dayOfWeek !== $dayOfWeekNumber) { $adjustedStartDate->addDay(); } $dates = []; $currentDate = $adjustedStartDate->copy(); while ($currentDate->lessThanOrEqualTo($endDate)) { $dates[] = $currentDate->copy(); $currentDate->addWeek(); } return $dates; } } // Default: generate dates based on recurrence type $dates = []; $currentDate = $startDate->copy(); while ($currentDate->lessThanOrEqualTo($endDate)) { $dates[] = $currentDate->copy(); if ($this->recurrenceType === RecurrenceType::Weekly) { $currentDate->addWeek(); } else { $currentDate->addMonth(); } } return $dates; } public function delete(): void { if ($this->event) { $this->event->delete(); session()->flash('status', __('Event erfolgreich gelöscht!')); $this->redirect(route('meetups.landingpage', ['meetup' => $this->meetup, 'country' => $this->country]), navigate: true); } } }; ?>