Umrechnung von Stunden in y d h als Funktion in 99_myUtils.pm

Begonnen von Gisbert, 23 Februar 2026, 14:38:55

Vorheriges Thema - Nächstes Thema

Gisbert

Hallo zusammen,

ich möchte eine Stundenzahl in ein Format y d h bringen, hab aber das Problem des Schaltjahres.
Mit folgfender Definition:
sub h2human($){
  my ($diff) = @_;
  my ($y,$d,$h,$ret);
  ($y,$diff) = _s2h_Div($diff,8766);
  ($d,$diff) = _s2h_Div($diff,24);
  ($h,$diff) = _s2h_Div($diff,1);
  $ret  = "$y"."y "."$d"."d "."$h"."h";
  return $ret;
}

sub _s2h_Div($$) {
  my ($p1,$p2) = @_;
  return (int($p1/$p2), $p1 % $p2);
}
gelingt es halb.
Für ein Jahr ohne Schalttag gibt es: 8760 Stunden.
Für ein Jahr mit Schalttag gibt es: 8784 Stunden, im Mittel jedes Jahr also 6 Stunden mehr.

Die obige Funktion macht die Berechnung so halb richtig, aber es fehlen ein paar Stunden. Konkret ergibt die Anzahl von 16290 Stunden 1y 313d 12h mit der obigen Funktion, aber es müssten 1y 313d 17h sein. Das ist jetzt nur ein kleiner Unterschied, aber ich hätte es gerne richtig, falls der Aufwand vertretbar ist.

Das genaue Datum, bei dem die Stundenzahl beginnt, ist prinzipiell bekannt - damit ist die Stundenzahl auch immer bekannt, aber die Angabe im Format y d h aber nicht.

Ich hoffe, das mein Anliegen mit dem konkreten Beispiel erkennbar ist.

Viele Grüße Gisbert
Proxmox | UniFi | Homematic, VCCU, HMUART | ESP8266 | ATtiny85 | Wasser-, Stromzähler | tuya local | Wlan-Kamera | SIGNALduino, Rauchmelder FA21/22RF | RHASSPY | DEYE | JK-BMS | ESPHome | Panasonic Heishamon

betateilchen

Das lässt sich nicht so einfach mit Deiner Funktion umrechnen.
Innerhalb eines Zeitraumes von 5 Jahren können beispielsweise ein oder zwei Schaltjahre liegen.

Aber es gibt ja für perl durchaus Module, die solche Kalenderbe- und -umrechnungen unterstützen.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

Gisbert

Hallo betateilchen,

ich stimme dir vollkommen zu - , nur dass ich trotz Suche diese Perl-Funktionen noch nicht gefunden hab.

Hintergrund ist, dass ich Daten per ESPHome aus einem Akku auslesen möchte. Da ich so viele Daten wie möglich auslesen möchte, um eine gewisse Genauigkeit zu bekommen, aber andererseits Daten, die sich nicht ändern - jedenfalls nicht schnell - nur gelegentlich übertragen möchte, war ich auf die obige Idee gekommen.

Mittlerweile bin ich bei ESPHome etwas weiter, d.h. ich kann dort steuern, wie oft ich Daten übermittele kann. Im obigen Beispiel ist das ja nur 1xmal in der Stunde nötig.

D.h. mein Problem(chen) ist anderweitig gelöst. Wenn es noch neue Infos zu den passenden Perl-Funktionen gibt, dann schaue ich mir das auch an, aber es gibt damit keine Dringlichkeit (die hat es so oder so nicht gegeben 8)).

Viele Grüße Gisbert
Proxmox | UniFi | Homematic, VCCU, HMUART | ESP8266 | ATtiny85 | Wasser-, Stromzähler | tuya local | Wlan-Kamera | SIGNALduino, Rauchmelder FA21/22RF | RHASSPY | DEYE | JK-BMS | ESPHome | Panasonic Heishamon

JoWiemann

Die KI schlägt vor:

use strict;
use warnings;
use DateTime;
use DateTime::Duration;

my $stunden_input = 20000; # Beispiel: 20.000 Stunden

# Erstelle ein Dauer-Objekt aus Stunden
my $dur = DateTime::Duration->new( hours => $stunden_input );

# Umrechnung in Einheiten (normalized)
my ( $y, $m, $d, $h ) = $dur->in_units( 'years', 'months', 'days', 'hours' );

print "$stunden_input Stunden sind: $y Jahre, $m Monate, $d Tage und $h Stunden.\n";
# Ausgabe: 20000 Stunden sind: 2 Jahre, 3 Monate, 3 Tage und 8 Stunden.

Grüße Jörg
Jörg Wiemann

Slave: RPi B+ mit 512 MB, COC (868 MHz), CUL V3 (433.92MHz SlowRF); FHEMduino, Aktuelles FHEM

Master: CubieTruck; Debian; Aktuelles FHEM

betateilchen

@Jörg:

Die Frage ist, ob das Beispiel auch ohne Monat funktioniert und dann die richtige Anzahl Tage rauskommt.
Denn nach dem Monat ist in der Aufgabenstellung ja nicht gefragt.

Außerdem kann das Beispiel auch ohne Angabe eines Startzeitpunktes nicht immer das korrekte Ergebnis liefern, wenn es um Berücksichtigung der Schaltjahre geht.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

Gisbert

#5
Hallo betateilchen,
hallo Jörg,

da es anscheinend nicht ganz so einfach ist, die Berechnung unter Berücksichtung von Schaltjahren durchzuführen, belasse ich es damit, meine ausgedünnte Abfrage in ESPHome zu machen. Da der gewünschte Wert ja nur einmal pro Stunde anfällt (bzw. sich ändert), denke ich, dass ich das maximal mögliche gemacht habe, um die Datenmenge an dieser Stelle zu reduzieren.

Durch die Beschäftigung mit möglichen Datenreduktionen in ESPHome, habe ich einige weitere Readings ausgedünnt, so dass ich insgesamt ein besseres Ergebnis erhalten habe, als nur die nachträgliche Berechnung der Laufzeit in Fhem im Format y d h.

Falls jemand zufällig über diesen Thread stolpert, hier sind die Definitionen in ESPHome (beispielhaft):
Datenwert alle 5 Sekunden ermitteln:
jk_bms:
  - id: bms0
    jk_modbus_id: modbus0
    update_interval: 5s

Datenwert über 30 Sekunden mitteln und alle 30 Sekunden senden:
sensor:
  - platform: jk_bms
    min_cell_voltage:
      name: "${name} min cell voltage"
      filters:
        - sliding_window_moving_average:
            window_size: 6
            send_every: 6
Ebenfalls vom Typ sensor, falls eine Mittelwertbildung keinen Sinn macht, aber weniger Daten gewünscht werden (Übermittelung alle 60 Sekunden):
    min_voltage_cell:
      name: "${name} min voltage cell"
      filters:
      - throttle: 60s
Hier wird nur etwas gesendet, wenn es sich geändert hat (das ist die formatierte Laufzeit):
text_sensor:
  - platform: jk_bms   
    total_runtime_formatted:
      name: "${name} total runtime formatted"
      filters:
        - lambda: |-
            static std::string last = "";
            if (x == last)
              return {};
            last = x;
            return x;

Die Befehle in ESPHome sind zugegebenermaßen raubkopiert ;) - dafür funktionieren sie aber.
Viele Grüße Gisbert
Proxmox | UniFi | Homematic, VCCU, HMUART | ESP8266 | ATtiny85 | Wasser-, Stromzähler | tuya local | Wlan-Kamera | SIGNALduino, Rauchmelder FA21/22RF | RHASSPY | DEYE | JK-BMS | ESPHome | Panasonic Heishamon

Christoph Morrison

Zitat von: Gisbert am 23 Februar 2026, 14:38:55Die obige Funktion macht die Berechnung so halb richtig, aber es fehlen ein paar Stunden. Konkret ergibt die Anzahl von 16290 Stunden 1y 313d 12h mit der obigen Funktion, aber es müssten 1y 313d 17h sein. Das ist jetzt nur ein kleiner Unterschied, aber ich hätte es gerne richtig, falls der Aufwand vertretbar ist.

Ohne Startzeitpunkt kann man das nicht sinnvoll berechnen. Wenn du einen Startzeitpunkt kennst und angeben willst, geht es, aber es ist deutlich umfangreicher als dein Entwurf. Grundsätzlich versuchst du eine fest definierte Zeitdauer (1h = 60m = usw.) in eine undefinierte zu konvertieren. Ohne den Startzeitpunkt kann man nicht mal sagen, ob du mit 1y 313d 17h recht hast.

Ich hab es mal mit dem (unwahrscheinlich zutreffenden) Zeitpunkt 01.01. 00:00 berechnet:

2020-01-01 -> 2021-11-09 18:00:00  =>  1y 312d 18h
2021-01-01 -> 2022-11-10 18:00:00  =>  1y 313d 18h
2022-01-01 -> 2023-11-10 18:00:00  =>  1y 313d 18h
2023-01-01 -> 2024-11-09 18:00:00  =>  1y 313d 18h
2024-01-01 -> 2025-11-09 18:00:00  =>  1y 312d 18h
2025-01-01 -> 2026-11-10 18:00:00  =>  1y 313d 18h
2026-01-01 -> 2027-11-10 18:00:00  =>  1y 313d 18h

Hier mein Vorschlag:
use strict;
use warnings;
use DateTime;

sub hours_to_ydh {
    my ($start, $hours) = @_;

    my $end = $start->clone->add( hours => $hours );
    my $t  = $start->clone;

    my ($y,$d,$h) = (0,0,0);

    # Jahre
    while ( $t->clone->add( years => 1 ) <= $end ) {
        $t->add( years => 1 );
        $y++;
    }

    # Tage
    while ( $t->clone->add( days => 1 ) <= $end ) {
        $t->add( days => 1 );
        $d++;
    }

    # Reststunden (echte Zeitdifferenz)
    my $seconds_left = $end->epoch - $t->epoch;
    $h = int($seconds_left / 3600);

    return ($y,$d,$h, $end);
}

my $hours = 16290;

for my $year (2020 .. 2026) {

    my $start = DateTime->new(
        year      => $year,
        month    => 1,
        day      => 1,
        hour      => 0,
        minute    => 0,
        second    => 0,
        time_zone => 'Europe/Berlin',  # vermutlich Gisberts TZ
    );

    my ($y,$d,$h,$end) = hours_to_ydh($start, $hours);

    printf "%d-01-01 -> %s  =>  %dy %dd %dh\n",
        $year,
        $end->ymd . ' ' . $end->hms,
        $y, $d, $h;
}

DateTime gehört leider nicht zu den Core-Modulen. Aber ohne wirst du nur sehr schwer ein plausibles Ergebnis erhalten.