feat: RainCalculator with timezone-aware day mapping and month total
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Weerscherm\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Weerscherm\RainCalculator;
|
||||
|
||||
final class RainCalculatorTest extends TestCase
|
||||
{
|
||||
private \DateTimeZone $tz;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->tz = new \DateTimeZone('Europe/Berlin');
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
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());
|
||||
}
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"body": {
|
||||
"1792621800": [
|
||||
1
|
||||
],
|
||||
"1792708200": [
|
||||
2
|
||||
],
|
||||
"1792794600": [
|
||||
3
|
||||
],
|
||||
"1792884600": [
|
||||
4
|
||||
],
|
||||
"1792971000": [
|
||||
9
|
||||
]
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"body": {
|
||||
"1790558220": [
|
||||
1
|
||||
],
|
||||
"1790644620": [
|
||||
2
|
||||
],
|
||||
"1790731020": [
|
||||
3
|
||||
],
|
||||
"1790817420": [
|
||||
4
|
||||
],
|
||||
"1790903820": [
|
||||
5
|
||||
]
|
||||
},
|
||||
"status": "ok"
|
||||
}
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"body": {
|
||||
"1788225420": [
|
||||
4.3
|
||||
],
|
||||
"1788311820": [
|
||||
0
|
||||
],
|
||||
"1788398220": [
|
||||
3.2
|
||||
],
|
||||
"1788484620": [
|
||||
5.1
|
||||
],
|
||||
"1788571020": [
|
||||
0
|
||||
],
|
||||
"1788657420": [
|
||||
0
|
||||
],
|
||||
"1788743820": [
|
||||
15.5
|
||||
],
|
||||
"1788830220": [
|
||||
1.2
|
||||
],
|
||||
"1788916620": [
|
||||
6.8
|
||||
],
|
||||
"1789003020": [
|
||||
0
|
||||
],
|
||||
"1789089420": [
|
||||
2.4
|
||||
]
|
||||
},
|
||||
"status": "ok",
|
||||
"time_exec": 0.02,
|
||||
"time_server": 1789129925
|
||||
}
|
||||
Reference in New Issue
Block a user