145 lines
4.3 KiB
PHP
145 lines
4.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Belevo\EnelixUtils\Energiediagramm;
|
|
|
|
use DateTimeImmutable;
|
|
use DateTimeZone;
|
|
use InvalidArgumentException;
|
|
|
|
final class EnergyChartCalculator
|
|
{
|
|
public const RANGES = ['day', 'week', 'month', 'year', 'total', 'custom'];
|
|
|
|
public static function isValidDate(string $date): bool
|
|
{
|
|
$value = DateTimeImmutable::createFromFormat('!Y-m-d', $date);
|
|
$errors = DateTimeImmutable::getLastErrors();
|
|
|
|
return $value !== false
|
|
&& ($errors === false || ($errors['warning_count'] === 0 && $errors['error_count'] === 0))
|
|
&& $value->format('Y-m-d') === $date;
|
|
}
|
|
|
|
/**
|
|
* @return array{0: int, 1: int}
|
|
*/
|
|
public static function rangeBounds(
|
|
string $range,
|
|
string $date,
|
|
string $customStart,
|
|
string $customEnd,
|
|
?int $now = null
|
|
): array {
|
|
if (!in_array($range, self::RANGES, true)) {
|
|
throw new InvalidArgumentException('Unbekannter Zeitraum.');
|
|
}
|
|
|
|
$now ??= time();
|
|
if ($range === 'total') {
|
|
return [0, $now];
|
|
}
|
|
|
|
if ($range === 'custom') {
|
|
$start = self::date($customStart);
|
|
$end = self::date($customEnd);
|
|
if ($start > $end) {
|
|
throw new InvalidArgumentException('Das Startdatum liegt nach dem Enddatum.');
|
|
}
|
|
|
|
return [$start->getTimestamp(), $end->modify('+1 day')->getTimestamp()];
|
|
}
|
|
|
|
$base = self::date($date);
|
|
switch ($range) {
|
|
case 'day':
|
|
$start = $base;
|
|
$end = $start->modify('+1 day');
|
|
break;
|
|
case 'week':
|
|
$start = $base->modify('monday this week');
|
|
$end = $start->modify('+1 week');
|
|
break;
|
|
case 'month':
|
|
$start = $base->modify('first day of this month');
|
|
$end = $start->modify('first day of next month');
|
|
break;
|
|
case 'year':
|
|
$start = $base->setDate((int) $base->format('Y'), 1, 1);
|
|
$end = $start->modify('+1 year');
|
|
break;
|
|
default:
|
|
throw new InvalidArgumentException('Unbekannter Zeitraum.');
|
|
}
|
|
|
|
return [$start->getTimestamp(), $end->getTimestamp()];
|
|
}
|
|
|
|
/**
|
|
* @return array{
|
|
* Produktion: float,
|
|
* Einspeisung: float,
|
|
* Netzbezug: float,
|
|
* Hausverbrauch: float,
|
|
* Eigenverbrauch: float,
|
|
* Eigenverbrauchsquote: float,
|
|
* Autarkiegrad: float
|
|
* }
|
|
*/
|
|
public static function metrics(
|
|
float $production,
|
|
float $feedIn,
|
|
float $grid,
|
|
?float $consumption = null
|
|
): array {
|
|
$production = max(0.0, $production);
|
|
$feedIn = max(0.0, $feedIn);
|
|
$grid = max(0.0, $grid);
|
|
$house = $consumption === null
|
|
? max(0.0, $production - $feedIn + $grid)
|
|
: max(0.0, $consumption);
|
|
$selfConsumption = min($production, max(0.0, $house - $grid));
|
|
|
|
return [
|
|
'Produktion' => $production,
|
|
'Einspeisung' => $feedIn,
|
|
'Netzbezug' => $grid,
|
|
'Hausverbrauch' => $house,
|
|
'Eigenverbrauch' => $selfConsumption,
|
|
'Eigenverbrauchsquote' => $production > 0.0
|
|
? min(100.0, ($selfConsumption / $production) * 100.0)
|
|
: 0.0,
|
|
'Autarkiegrad' => $house > 0.0
|
|
? min(100.0, ($selfConsumption / $house) * 100.0)
|
|
: 0.0,
|
|
];
|
|
}
|
|
|
|
public static function normalizeColor(string $color, string $fallback): string
|
|
{
|
|
$color = trim($color);
|
|
if (preg_match('/^#[0-9A-Fa-f]{6}$/', $color) === 1) {
|
|
return strtoupper($color);
|
|
}
|
|
|
|
if (preg_match('/^[0-9]+$/', $color) === 1) {
|
|
$legacyColor = (int) $color;
|
|
if ($legacyColor >= 0 && $legacyColor <= 0xFFFFFF) {
|
|
return sprintf('#%06X', $legacyColor);
|
|
}
|
|
}
|
|
|
|
return strtoupper($fallback);
|
|
}
|
|
|
|
private static function date(string $date): DateTimeImmutable
|
|
{
|
|
if (!self::isValidDate($date)) {
|
|
throw new InvalidArgumentException('Ungültiges Datum.');
|
|
}
|
|
|
|
return new DateTimeImmutable($date . ' 00:00:00', new DateTimeZone(date_default_timezone_get()));
|
|
}
|
|
}
|