assertSame(DueMode::Local, $p->mode); $this->assertSame('2026-08-14', $p->date); $this->assertSame('24:00:00', $p->time); $this->assertNull($p->instant); $this->assertSame('2026-08-14', $p->dayFor('Asia/Shanghai')); } public function test_datetime_without_offset_is_local(): void { $p = DueAtParser::parse('2026-09-10T14:00'); $this->assertSame(DueMode::Local, $p->mode); $this->assertSame('2026-09-10', $p->date); $this->assertSame('14:00:00', $p->time); $this->assertSame('14:05:30', DueAtParser::parse('2026-09-10T14:05:30')->time); } public function test_datetime_with_offset_is_fixed(): void { $p = DueAtParser::parse('2026-09-10T08:15+02:00'); $this->assertSame(DueMode::Fixed, $p->mode); $this->assertNull($p->date); $this->assertSame('2026-09-10T06:15:00+00:00', $p->instant->utc()->toIso8601String()); $this->assertSame('2026-09-10', $p->dayFor('America/Montevideo')); $this->assertSame('2026-09-10', $p->dayFor('Asia/Shanghai')); $this->assertSame(DueMode::Fixed, DueAtParser::parse('2026-09-10T06:15:00Z')->mode); } public function test_garbage_is_rejected(): void { foreach (['', 'tomorrow', '2026-13-01', '14:00', '2026-09-10 14:00', '2026-09-10T25:00', '2026-02-30T08:15:00+02:00', '2026-09-10T25:00Z'] as $bad) { try { DueAtParser::parse($bad); $this->fail("accepted: $bad"); } catch (InvalidArgumentException) { $this->addToAssertionCount(1); } } } }