tz = new \DateTimeZone('Europe/Berlin'); } /** @return array */ private function fixture(string $name): array { return json_decode((string) file_get_contents(__DIR__ . "/fixtures/$name.json"), true)['body']; } private function now(string $s): \DateTimeImmutable { return new \DateTimeImmutable($s, $this->tz); } public function testNormalWeek(): void { $r = RainCalculator::compute($this->fixture('getmeasure'), $this->now('2026-09-11 14:32'), 9.9); self::assertSame(2.4, $r['today']); self::assertSame([ ['date' => '2026-09-08', 'mm' => 1.2], ['date' => '2026-09-09', 'mm' => 6.8], ['date' => '2026-09-10', 'mm' => 0.0], ], $r['previous_days']); self::assertEqualsWithDelta(38.5, $r['month_total'], 0.0001); } public function testMissingPreviousDayIsNullAndExcludedFromTotal(): void { $body = $this->fixture('getmeasure'); $sep9 = (string) $this->now('2026-09-09 03:17')->getTimestamp(); unset($body[$sep9]); $r = RainCalculator::compute($body, $this->now('2026-09-11 14:32'), null); self::assertNull($r['previous_days'][1]['mm']); self::assertSame('2026-09-09', $r['previous_days'][1]['date']); self::assertEqualsWithDelta(31.7, $r['month_total'], 0.0001); } public function testMissingTodayFallsBackToSumRain24(): void { $body = $this->fixture('getmeasure'); unset($body[(string) $this->now('2026-09-11 03:17')->getTimestamp()]); self::assertSame(2.4, RainCalculator::compute($body, $this->now('2026-09-11 14:32'), 2.4)['today']); self::assertNull(RainCalculator::compute($body, $this->now('2026-09-11 14:32'), null)['today']); } public function testMonthBoundaryKeepsPreviousDaysButNotInMonthTotal(): void { $r = RainCalculator::compute($this->fixture('getmeasure-month'), $this->now('2026-10-02 09:00'), null); self::assertSame(5.0, $r['today']); self::assertSame([ ['date' => '2026-09-29', 'mm' => 2.0], ['date' => '2026-09-30', 'mm' => 3.0], ['date' => '2026-10-01', 'mm' => 4.0], ], $r['previous_days']); self::assertEqualsWithDelta(9.0, $r['month_total'], 0.0001); } public function testDaylightSavingTransitionMapsTimestampsToLocalDates(): void { $r = RainCalculator::compute($this->fixture('getmeasure-dst'), $this->now('2026-10-25 12:00'), null); self::assertSame(4.0, $r['today'], 'meting om 01:30 lokaal (nog zomertijd) hoort bij 25 oktober'); self::assertSame([ ['date' => '2026-10-22', 'mm' => 1.0], ['date' => '2026-10-23', 'mm' => 2.0], ['date' => '2026-10-24', 'mm' => 3.0], ], $r['previous_days']); self::assertEqualsWithDelta(10.0, $r['month_total'], 0.0001, '26 oktober telt niet mee, ook al ligt de timestamp op 25 oktober in UTC'); } public function testEmptyBodyGivesNulls(): void { $r = RainCalculator::compute([], $this->now('2026-09-11 14:32'), null); self::assertNull($r['today']); self::assertSame([null, null, null], array_column($r['previous_days'], 'mm')); self::assertNull($r['month_total']); } public function testDateBeginIsMinOfFirstOfMonthAndThreeDaysAgo(): void { self::assertSame('2026-09-01 00:00:00', RainCalculator::dateBegin($this->now('2026-09-15 14:32'))->format('Y-m-d H:i:s')); self::assertSame('2026-09-29 00:00:00', RainCalculator::dateBegin($this->now('2026-10-02 09:00'))->format('Y-m-d H:i:s')); self::assertSame('Europe/Berlin', RainCalculator::dateBegin($this->now('2026-10-02 09:00'))->getTimezone()->getName()); } }