AT kleine Helferlein

Aus FHEMWiki

Ein AT kann statt einer Uhrzeit bzw. eines Datums mit einer Uhrzeit auch eine Perl-Funktion nutzen. Beispiel solcher Funktionen sind at_ultimo oder das Modul SUNRISE_EL. Hier sollen weitere Funktionen gezeigt werden.

Der Code kann in der 99 myUtils untergebracht werden.

AT am x-ten Wochentag im Monat ausführen

Aufgabe ist es, das at z.B. an jedem 2. Montag im Monat auszuführen. Zusätzlich gibt es auch die Möglichkeit, das at immer am z.B. letzten Montag im Monat auszuführen.

########################################
#
# Berechnung des Datums des 
# x.ten Wochentags im Monat
#
# x=1-4 x.ter Wochentag im Monat
# x=9   letzter Wochentag im Monat
# w=1-7 Wochentag, 1=Montag
# h,m,s Uhrzeit Stunde, Minute, Sekunde
#
sub at_xwday($$$$$) {
    my ($x,$w,$h,$m,$s) = @_;

    my $ziel_tag = 0;

    # akt. Datum und Uhrzeit in Variablen aufteilen
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 
        localtime(time);

    # bei Neuberechnung einen Monat addieren
    my $add = $data{AT_RECOMPUTE} ? 1 : 0;
    $mon = $mon + $add;

    my ($nm, $ny) = ($mon == 12) ? (0,$year+1) : ($mon,$year);

    # Letztes Vorkommen des Wochentags berechnen?
    if ($x == 9) {
        # Berechnen des letzten Tags des Monats
        # ersten Tag des Folgemonats als timestamp
        my ($xm, $xy) = ($mon == 11) ? (0,$year+1) : ($mon+1,$year);
        my $nt = mktime(0, 0, 0, 1, $xm, $xy);

        # letzter Tag des aktuellen Monats = erster Tag des Folgemonats minus ein Tag
        my ($letzter_tag_des_monats, $letzter_wochentag) = (localtime($nt - DAYSECONDS))[3,6];

        # Prüfen, ob der letzte Wochentag im Monat mit dem gewünschten Wochentag übereinstimmt
        $ziel_tag = $letzter_tag_des_monats;
        if ($letzter_wochentag > $w) {
           # Falls der letzte Wochentag später im Monat ist, subtrahiere die Differenz
           my $differenz = $letzter_wochentag - $w;
           $ziel_tag -= $differenz;
        } elsif ($letzter_wochentag < $w) {
           # Falls der letzte Wochentag früher im Monat ist, subtrahiere 7 plus die Differenz
           my $differenz = $w - $letzter_wochentag;
           $ziel_tag -= (7 - $differenz);
        }
    } else {
        # Berechne den Tag des ersten Vorkommens des Wochentags im Monat
        my $erster_tag = 1;
        my $erster_tag_des_monats = (localtime(mktime(0,0,0,$erster_tag,$nm,$ny)))[6];
        my $erstes_vorkommen = ($w - $erster_tag_des_monats + 7) % 7 + 1;

        # Berechne den Tag des x.ten Vorkommens des Wochentags im Monat
        $ziel_tag = $erstes_vorkommen + 7 * ($x - 1);

        # Überprüfe, ob das Datum im gültigen Bereich liegt
        if ($ziel_tag > 31 || $ziel_tag < 1) {
          return "Ungültiges Datum";
        }
    }
    return mktime($s,$m,$h,$ziel_tag,$nm,$ny);
}

Als Parameter benötigt die Funktion, welches Vorkommen des Wochentags berechnet werden soll (1-4). Soll das letzte Vorkommen berechnet werden, übergibt man 9. Der Montag ist der Wochentag 1. Danach kommen als getrennte Parameter Stunde, Minute und Sekunde.

AT alle x Wochen ausführen

Soll ein Termin z.B. alle sechs Wochen stattfinden, so kann folgende Funktion genutzt werden.

########################################
#
# Berechnung des Datums des 
# nächsten Treffens in sechs Wochen

sub at_sixweek($) {
my ($tm) = @_;
my $delta = 3628800;   # Zeit zwischen den Terminen in Sekunden, hier 6 Wochen

  return "Wrong timespec, use \"yyyy-mm-ddThh:mm:ss\"" if($tm !~ m/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)$/);
  my ($y,$m,$d,$h,$m2,$s) = ($1,$2,$3,$4,$5,$6);
  my $abstime = mktime($s,$m2,$h,$d,$m-1,$y-1900, 0,0,-1);

  if ($data{AT_RECOMPUTE}){
    # bei Neuberechnung sechs Wochen zur aktuellen Zeit addieren
    my $now = int(time);
    $abstime = $now+$delta;
  }

  return $abstime;
}

Dabei wird der erste Ausführungstermin im FHEM-üblichen Format ("yyyy-mm-ddThh:mm:ss") übergeben. Die Variable $delta enthält das Intervall in Sekunden. Es kann entsprechend angepasst werden. Die Neuberechnung erfolgt ausgehend vom aktuellen Datum/Uhrzeit. Es kann also als erster Termin nur ein Termin in der Zukunft angegeben werden.