ValidityTimeType::TimeWindow, 'start_time' => '22:00', 'end_time' => '02:00']); $group = new ResolvedValidityGroup(collect([$window]), 'America/Argentina/Buenos_Aires'); foreach (['2026-09-22 01:00' => true, '2026-09-22 04:59' => true, '2026-09-22 05:00' => false, '2026-09-22 18:00' => false] as $time => $expected) { $at = CarbonImmutable::parse($time, 'UTC'); $this->assertSame($expected, $group->isValid($at), $time); $this->assertSame($expected, $window->isValid($at, 'America/Argentina/Buenos_Aires'), $time); } $this->assertFalse((new ResolvedValidityGroup(collect([$window]), 'Asia/Tokyo'))->isValid(CarbonImmutable::parse('2026-09-22 01:00', 'UTC'))); } public function test_event_anchor_uses_local_date_when_utc_date_is_next_day(): void { $fixed = new ValidityTime(['type' => ValidityTimeType::FixedWindow, 'fixed_starts_at' => '2026-09-22 01:00', 'fixed_expires_at' => '2026-09-22 06:00']); $window = new ValidityTime(['type' => ValidityTimeType::TimeWindow, 'start_time' => '22:00', 'end_time' => '02:00']); $group = new ResolvedValidityGroup(collect([$fixed, $window]), 'America/Argentina/Buenos_Aires'); $this->assertTrue($group->isValid(CarbonImmutable::parse('2026-09-22 04:00', 'UTC'))); $this->assertFalse($group->isValid(CarbonImmutable::parse('2026-09-23 04:00', 'UTC'))); $this->assertSame('2026-09-22 01:00', $group->effectiveStartsAt()->utc()->format('Y-m-d H:i')); $this->assertSame('2026-09-22 05:00', $group->effectiveExpiresAt()->utc()->format('Y-m-d H:i')); } public function test_local_window_compares_with_utc_now_in_resource(): void { $this->travelTo(CarbonImmutable::parse('2026-09-21 22:00', 'UTC')); $window = new ValidityTime(['type' => ValidityTimeType::TimeWindow, 'start_time' => '18:00', 'end_time' => '20:00']); $resource = ValidityTimeResource::make($window, 'America/Argentina/Buenos_Aires'); $this->assertTrue($resource->resolve()['is_valid']); $this->assertFalse($window->isValid(now(), 'Asia/Tokyo')); $this->travelBack(); } public function test_daylight_saving_uses_date_specific_offset(): void { $window = new ValidityTime(['type' => ValidityTimeType::TimeWindow, 'start_time' => '18:00', 'end_time' => '20:00']); $this->assertSame('2026-01-21 23:00', $window->startsAt(CarbonImmutable::parse('2026-01-21 12:00', 'UTC'), 'America/New_York')->utc()->format('Y-m-d H:i')); $this->assertSame('2026-07-21 22:00', $window->startsAt(CarbonImmutable::parse('2026-07-21 12:00', 'UTC'), 'America/New_York')->utc()->format('Y-m-d H:i')); } public function test_resolver_passes_tenant_timezone_to_groups(): void { $tenant = new Tenant(['timezone' => 'Asia/Tokyo']); $item = new CatalogItem; $item->setRelation('tenant', $tenant); $date = new EventDate; $date->setRelation('validityTime', new ValidityTime([ 'type' => ValidityTimeType::FixedWindow, 'fixed_starts_at' => '2026-09-21 13:00', 'fixed_expires_at' => '2026-09-21 17:00', ])); $variant = new Variant; $variant->setRelation('catalogItem', $item); $variant->setRelation('eventDates', new Collection([$date])); $variant->setRelation('eventDate', null); $variant->setRelation('definitions', new Collection); $resolved = (new TicketValidityResolver)->resolveVariant($variant); $this->assertSame('Asia/Tokyo', $resolved->groups->first()->timezone); } public function test_event_hours_are_local_and_end_can_be_next_day(): void { $date = new EventDate(['date' => '2026-09-21', 'time_start' => '22:00', 'time_end' => '02:00']); $date->setRelation('tenant', new Tenant(['timezone' => 'America/Argentina/Buenos_Aires'])); $this->assertSame('2026-09-22 01:00', $date->startsAt()->utc()->format('Y-m-d H:i')); $this->assertSame('2026-09-22 05:00', $date->endsAt()->utc()->format('Y-m-d H:i')); $date->setRelation('tenant', new Tenant(['timezone' => 'Asia/Tokyo'])); $this->assertSame('2026-09-21 13:00', $date->startsAt()->utc()->format('Y-m-d H:i')); } }