[GELÖST] FHEM: falsch, Perl-Online-Editor: richtig - Warum?

Begonnen von DocCyber, 25 März 2024, 19:08:58

Vorheriges Thema - Nächstes Thema

DocCyber

Hallo zusammen,

ich würde gern des letzten Tag eines Monats ermitteln, habe hierfür ein Stück Code gefunden und leicht modifiziert.
FHEM liefert mir für März ein falsches Ergebnis, nämlich den 30. als letzten Tag.
Der identische Code in Perl-Online-Editor liefert aber das korrekte Ergebnis (31.)
Kann mir jemand erklären, wieso das so ist? Offenbar habe ich etwas falsch gemacht, oder?

FHEM Kommandozeile:
{my $ult = ultimo(0);; return $ult;;}bzw.
{my $ult = ultimo(1);; return $ult;;}
sub ultimo($) {
  #use Time::Local;
  use POSIX qw(strftime);
  my $workday = shift;  # 0: Letzter Tag des Monats | 1: letzter Werktag des Monats
  my ($_year, $_month) = split(/\./, `date +%y.%m`);
  chomp($_year, $_month);
  # Letzter Tag des Monats:
  my $_next_year = ($_month == 12) ? $_year + 1 : $_year;
  my $_next_month = timelocal(0, 0, 0, 1, $_month % 12, $_next_year);
  my $_last_day = (localtime($_next_month - 86400))[3];
  # Letzter Werktag des Monats:
  if ($workday) {
    my $_day = strftime "%a", localtime($_next_month - 86400);
    if ($_day eq 'Sat') { $_last_day--; }
    if ($_day eq 'Sun') { for (1..2) { $_last_day--; } }
  }

  return "$_last_day.$_month.$_year";
}
Behandle die Menschen so, als wären sie, was sie sein sollten. Dadurch hilfst du ihnen zu werden, was sie sein können. (Goethe)


RPi-3 mit HM-CFG-LAN und jede Menge HM Komponenten.

Christoph Morrison

Der letzte Tag ist doch der 31, also zieht dein Programm einen Tag ab und kommt beim 30. raus. Der Unterschied könnte aus den locales kommen, bei denen Sat und Sun vielleicht was anderes ist als Sat und Sun, z.B. Sa und So (german locale) - siehe auch CAVEAT %A, %a, %B, %b, and friends

Du machst dir das ingesamt aber sehr kompliziert.

Aus Time::Piece:
$t->month_last_day      # 28-31
Deinen Abzug für's Wochenende kannst du dann wie gehabt dran bauen, $t->wday ist dein Freund, besser als auf Strings zu prüfen.

btw: Bei einer Zeitumstellung haut das mit 86400 nicht mehr unbedingt hin.

Otto123

Hi,

keine Antwort auf Deine Frage, aber - es gibt die Funktion at_ultimo in FHEM die tut das was Du suchst. Beispiel:
{localtime(at_ultimo)}Mit strftime() kannst Du weiter formatieren.

Gruß Otto
Viele Grüße aus Leipzig  ⇉  nächster Stammtisch an der Lindennaundorfer Mühle
RaspberryPi B B+ B2 B3 B3+ ZeroW,HMLAN,HMUART,Homematic,Fritz!Box 7590,WRT3200ACS-OpenWrt,Sonos,VU+,Arduino nano,ESP8266,MQTT,Zigbee,deconz

yersinia

Zitat von: Christoph Morrison am 25 März 2024, 22:00:08Du machst dir das ingesamt aber sehr kompliziert.

Aus Time::Piece:
$t->month_last_day      # 28-31
Auch DateTime kann Anzahl der Tage des Monats (indirekt dadurch auch den Letzten)
$dt->month_lengthund prüfen ob ein Datum der letzte Tag des Monats ist:
$dt->is_last_day_of_month :)
viele Grüße, yersinia
----
FHEM 6.3 (SVN) on RPi 4B with RasPi OS Bullseye (perl 5.32.1) | FTUI
nanoCUL->2x868(1x ser2net)@tsculfw, 1x433@Sduino | MQTT2 | Tasmota | ESPEasy
VCCU->14xSEC-SCo, 7xCC-RT-DN, 5xLC-Bl1PBU-FM, 3xTC-IT-WM-W-EU, 1xPB-2-WM55, 1xLC-Sw1PBU-FM, 1xES-PMSw1-Pl

betateilchen

Wie wäre es eigentlich mit einem sinnvollen Threadtitel?


-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

DocCyber

#5
Zitat von: Christoph Morrison am 25 März 2024, 22:00:08Du machst dir das ingesamt aber sehr kompliziert.
Nur weil $t->month_last_day einen einfachen Auruf darstellt, ist es nicht weniger kompliziert, denn schließlich steht Time::Piece dahinter.

Aber darum soll es hier nicht gehen.
Zitat von: Christoph Morrison am 25 März 2024, 22:00:08Der letzte Tag ist doch der 31, also zieht dein Programm einen Tag ab und kommt beim 30. raus
Wie gesagt: Das selbe Programm arbeitet korrekt, wenn ich den Code im Perl-Online-Editor laufen lasse.
Ich würde gern wissen, warum der letzte Tag in Fhem mit diesem Code nicht korrekt berechnet  wird.
Behandle die Menschen so, als wären sie, was sie sein sollten. Dadurch hilfst du ihnen zu werden, was sie sein können. (Goethe)


RPi-3 mit HM-CFG-LAN und jede Menge HM Komponenten.

DocCyber

Zitat von: betateilchen am 26 März 2024, 08:54:44Wie wäre es eigentlich mit einem sinnvollen Threadtitel?
Ich finde, der Titel trifft das Problem auf den Punkt.
Hättest du bei einem anderen Titel (Vorschlag?) denn eine Lösung für die Fragestellung?
Behandle die Menschen so, als wären sie, was sie sein sollten. Dadurch hilfst du ihnen zu werden, was sie sein können. (Goethe)


RPi-3 mit HM-CFG-LAN und jede Menge HM Komponenten.

betateilchen

#7
Zitat von: DocCyber am 26 März 2024, 10:18:59Ich finde, der Titel trifft das Problem auf den Punkt.

Ich finde, der Titel ist eine Katastrophe,

ZitatFHEM: falsch, Perl-Online-Editor: richtig - Warum?

weil er keinerlei Hinweis darauf gibt, um welches Thema es eigentlich geht: Du willst den letzten Tag eines Monats ermitteln und bist dabei auf ein Problem gestoßen.

Zitat von: DocCyber am 26 März 2024, 10:18:59Hättest du bei einem anderen Titel (Vorschlag?) denn eine Lösung für die Fragestellung?

Die einzig sinnvolle Lösung hat doch Otto schon gegeben, warum soll ich die wiederholen?
Benutze die Funktion, die FHEM von Haus aus mitbringt, um ultimo zu berechnen, denn genau dafür wurde diese Funktion ja eingebaut.
Und ob der Tag ein Werktag oder ein Sonn-/Feiertag ist, bekommst Du dann per holiday-Abfrage (ebenfalls FHEM Standard) heraus.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

betateilchen

Zitat von: DocCyber am 26 März 2024, 10:16:20Wie gesagt: Das selbe Programm arbeitet korrekt, wenn ich den Code im Perl-Online-Editor laufen lasse.
Ich würde gern wissen, warum der letzte Tag in Fhem mit diesem Code nicht korrekt berechnet  wird.

Weil Dein FHEM vermutlich nicht mit der locale C.UTF-8 läuft, im Gegensatz zum Online Editor. Und in Deutschland ist am 31.03.24 Zeitumstellung, was eine manuelle Berechnung zusätzlich kompliziert macht. Aber auch darauf wurdest Du ja schon hingewiesen.

Zitat von: Christoph Morrison am 25 März 2024, 22:00:08btw: Bei einer Zeitumstellung haut das mit 86400 nicht mehr unbedingt hin.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

Otto123

#9
Da Du nicht auf die Lösung des Problems sondern auf deine Frage beharrst (was ich ok finde, wir sind ja in der Perl für User Ecke):
Unter dem Aspekt und da ich auch immer gerne dazu lerne, habe ich mal etwas rumprobiert.
Ich finde den Code zu heterogen :)
Was macht ein online Editor mit dieser Zeile (Systemkommando oder sehe ich das falsch?)?
  my ($_year, $_month) = split(/\./, `date +%y.%m`);warum nicht Perl?
my ($_year, $_month) = split(/\./, strftime("%y.%m",localtime(time)) );Du ziehst mit einem ganzen Tag am Tag der Zeitumstellung einfach zuviel ab!? (Aber ob das so simple ist glaube ich nicht.)
Dein Code rechnet nur im März falsch.

Kann man mit der Zeile in FHEM "erspielen"
{strftime ("%d.%m.%Y",localtime (timelocal(0, 0, 0, 1, 3, 24) - 1) )}
Der Online Editor steht eventuell in Indien? Zumindest der den ich gefunden habe. -> IST
Dort gibt es keine Sommerzeit
use POSIX;
print strftime("%Z", localtime()), "\n";

Edit ok - jetzt gesehen: Deiner macht UTC - auch ohne Sommerzeit ;)

Im übrigen arbeitet Dein Code korrekt wenn Du nicht die lokale Zeit nimmst sondern auf UTC gehst:
timelocal -> timegm
localtime -> gmtime

{strftime ("%d.%m.%Y", gmtime (timegm(0, 0, 0, 1, 3, 24) - 86400) )}
Ob das die richtige Lösung deiner Fragestellung ist weiß ich allerdings nicht.

Gruß Otto
Viele Grüße aus Leipzig  ⇉  nächster Stammtisch an der Lindennaundorfer Mühle
RaspberryPi B B+ B2 B3 B3+ ZeroW,HMLAN,HMUART,Homematic,Fritz!Box 7590,WRT3200ACS-OpenWrt,Sonos,VU+,Arduino nano,ESP8266,MQTT,Zigbee,deconz

DocCyber

Zitat von: betateilchen am 26 März 2024, 11:10:01Die einzig sinnvolle Lösung hat doch Otto schon gegeben, warum soll ich die wiederholen?
Weil es hier primär um die Antwort meiner konkreten Fragestellung geht, und nicht um eine (andere) Lösung - sei sie auch noch so gut.

Wenn Probleme solcher Art auftreten, versuche ich, den Grund dafür zu verstehen. Dabei gibt es einen Lerneffekt.
Eine andere Lösung ist an sich ja toll, weicht aber dem genannten Problem aus.
Behandle die Menschen so, als wären sie, was sie sein sollten. Dadurch hilfst du ihnen zu werden, was sie sein können. (Goethe)


RPi-3 mit HM-CFG-LAN und jede Menge HM Komponenten.

DocCyber

Zitat von: betateilchen am 26 März 2024, 11:19:20Weil Dein FHEM vermutlich nicht mit der locale C.UTF-8 läuft, im Gegensatz zum Online Editor

Das könnte eine echte Antwort auf meine Frage sein - danke  :)
Behandle die Menschen so, als wären sie, was sie sein sollten. Dadurch hilfst du ihnen zu werden, was sie sein können. (Goethe)


RPi-3 mit HM-CFG-LAN und jede Menge HM Komponenten.

Christoph Morrison

Zitat von: yersinia am 26 März 2024, 07:30:13Auch DateTime

Der Nachteil an DateTime ist, dass es kein Core-Module ist und ggf. erst installiert werden muss - wäre sonst auch mein Vorschlag gewesen. Time::Piece dagegen ist eines.

Christoph Morrison

Zitat von: DocCyber am 26 März 2024, 10:16:20Nur weil $t->month_last_day einen einfachen Auruf darstellt, ist es nicht weniger kompliziert, denn schließlich steht Time::Piece dahinter.

Doch, weil Time::Piece eh installiert ist und garantiert von besserer Codequalität als dein Versuch (shell exec für's Datum?!). Wäre deine Codequalität so gut, hättest du a) gewusst, warum %a und ein Stringvergleich nicht unproblematisch sind und b) hättest gleich Time::Piece oder ein anderes Modul gewählt. NIH-Syndrom und so.

ZitatWie gesagt: Das selbe Programm arbeitet korrekt, wenn ich den Code im Perl-Online-Editor laufen lasse.
Ich würde gern wissen, warum der letzte Tag in Fhem mit diesem Code nicht korrekt berechnet  wird.

Darauf hatte ich dir auch eine Antwort gegeben. Du hast sie nur nicht verstanden oder gar nicht erst gelesen. Ansonsten raten wir alle nur, weil wir auch nicht wissen, welche Locale dein FHEM nutzt.

btw: Dein Threadtitel, und da muss ich betateilchen zustimmen, ist keine gute Wahl. Vor allem: Du gehst davon aus, dass wir "Perl Online Editor" kennen, als ob das irgendeine offizielle oder wenigstens quasi offizielle Sache wäre. Um mal hier den Habeck zu machen: FHEM macht nichts falsch, es liefert nur nicht, was du erwartest (und ich bin nicht unbedingt als Nicht-Kritiker von FHEM bekannt ...).

Otto123

#14
ich meine, die locale ist hier nicht die Ursache.
{setlocale(LC_CTYPE, "C.UTF-8");; ultimo(0)}Liefert das gleiche Ergebnis.
Der vom TE erwähnte Perl Online Editor (er hat ihn ja nachträglich verlinkt) arbeitet mit "C.UTF-8"
Liege ich mit meiner Erklärung bzw. mit dem Beleg, dass die Vermutung von Christoph bez. Zeit Umstellung die richtige ist, so falsch?
Ich meine, die Rechnerei für die hier gestellte Aufgabe kann man besser mit UTC bzw. GMT durchführen. Es geht ja um den letzten Tag und nicht die letzte Sekunde des Monats?

at_ultimo liefert übrigens die letzte Minute des Monats, egal ob "C.UTF-8" oder "de_DE.UTF-8" ;)
Viele Grüße aus Leipzig  ⇉  nächster Stammtisch an der Lindennaundorfer Mühle
RaspberryPi B B+ B2 B3 B3+ ZeroW,HMLAN,HMUART,Homematic,Fritz!Box 7590,WRT3200ACS-OpenWrt,Sonos,VU+,Arduino nano,ESP8266,MQTT,Zigbee,deconz

Christoph Morrison

LANG=de_DE.UTF-8:
Ultimo letzter Tag: 30.03.24
Ultimo letzter Werktag: 30.03.24

Falsch.

LANG=C:
Ultimo letzter Tag: 30.03.24
Ultimo letzter Werktag: 29.03.24

Falsch.

So sieht's bei mir aus. Meine Vermutung gerade: Es hat mit locale und TZ zu tun. Ich hab mir mal die berechneten Timestamps angeguckt und siehe da:

LANG=de_DE.UTF-8, TZ=CET
Ultimo letzter Tag: 30.03.24
Ultimo letzter Werktag: 30.03.24

Wird wärmer.

LANG=de_DE.UTF-8, TZ=UTC
Ultimo letzter Tag: 30.03.24
Ultimo letzter Werktag: 29.03.24

Mhm.

LANG=C, TZ=UTC
Ultimo letzter Tag: 31.03.24
Ultimo letzter Werktag: 29.03.24

Und das ist der Gewinner.

DocCyber

Zitat von: Christoph Morrison am 26 März 2024, 17:06:53LANG=C, TZ=UTC
Ultimo letzter Tag: 31.03.24
Ultimo letzter Werktag: 29.03.24

Und das ist der Gewinner.

Die Antwort ist also ganz und gar nicht trivial, und ich hätte sie vermutlich nie herausgefunden.
Vielen Dank!


(Kleine Nachbemerkung: Zugegeben - der Code ist nicht gerade als elegant zu bezeichnen. Zu meiner Entschuldigung kann ich nur sagen, dass es nicht "mein" Code ist, sondern ich habe ihn wie erwähnt im Netz gefunden.
Aber es ging ja auch nicht um den Code als solchen, sondern um die Fragestellung, warum er unterschiedliche Resultate liefert. Denn es kann nicht sein, dass ein Code gleichzeitig falsch und richtig ist.)
Behandle die Menschen so, als wären sie, was sie sein sollten. Dadurch hilfst du ihnen zu werden, was sie sein können. (Goethe)


RPi-3 mit HM-CFG-LAN und jede Menge HM Komponenten.

Christoph Morrison

#17
Ich war mal so frei und habe eine bessere Version von ultimo geschrieben, die unabhängig von Timezone, Locale und zusätzlichen Modulen ist:

use constant {
    ONLY_EOM          => 0,  # only last day of a month
    CONSIDER_WEEKENDS => 1,  # consider weekends in calculation
};

sub ultimo {
    use Time::Piece;   # core ≥ v5.9.5
    use List::Util;    # core ≥ v5.7.3
    use warnings;      # core ≥ v5.6.0
    use Carp;          # core ≥ v5
    use strict;        # core ≥ v5

    my $time_piece_object = localtime;

    my $consider_modifications  = shift || ONLY_EOM;
    my $year                    = shift || $time_piece_object->year;
    my $month                   = shift || $time_piece_object->mon;

    if (! List::Util::any { $_ == $consider_modifications } ( CONSIDER_WEEKENDS, ONLY_EOM ) ) {
        carp("$consider_modifications is not a valid value. Resetting to " . ONLY_EOM);
        $consider_modifications = ONLY_EOM;
    }

    # set calculation base to year-month-date
    my $calc_base_date = $time_piece_object->strptime("$year-$month-1", '%Y-%m-%d');

    # eom = End of Month
    my $eom = $calc_base_date->month_last_day;

    # don't consider workdays, just the eom
    if ($consider_modifications == 0) {
        return $eom;
    }

    # set Timme::Pice to current last month day
    $calc_base_date = $time_piece_object->strptime("$year-$month-$eom", '%Y-%m-%d');

    # substract two days if the eom day is a sunday
    if ($calc_base_date->wday == 1) {
        return $eom - 2;
    }

    # substract one day if the eom day is a saturday
    if ($calc_base_date->wday == 7) {
        return $eom - 1;
    }
}

Die Funktion unterstützt neben ONLY_EOM (Ende des Monats ohne Abzüge) auch CONSIDER_WEEKENDS als Parameter (Samstage sind je nach Gesetz Werktage oder auch nicht, aber immer Wochenende).

Außerdem kannst du mit dem zweiten Parameter ein Jahr angeben und mit dem dritten Parameter einen Monat, so kannst du auch für andere Monate als für den aktuellen das Monatsende bestimmen. Beispiele:

# heute = 26.03.2024
ultimo(ONLY_EOM)            = 31
identisch zu
ultimo(ONLY_EOM, 2024, 3)  = 31
ultimo(CONSIDER_WEEKENDS);  = 29

In der Zukunft:
ultimo(ONLY_EOM, 2024, 6);  = 30
ultimo(CONSIDER_WEEKENDS, 2024, 6);  = 28

In der Vergangenheit:
ultimo(ONLY_EOM, 2023, 12)  = 31
ultimo(CONSIDER_WEEKENDS, 2023, 12)  = 29

betateilchen

Zitat von: Otto123 am 26 März 2024, 16:26:24at_ultimo liefert übrigens die letzte Minute des Monats

Aber nur, wenn Du nichts anderes angibst.

{at_ultimo(12,34,56)}
liefert nicht die letzte Minute des Monats.

ZitatTo execute a FHEM command on every last day of the month,
the function at_ultimo() can be used as perlfunc for datespec.
define at_ultimo at *{at_ultimo()} set lamp1 off
This will create an at device which will be executed at 23:59:00 on the last day of month.
at_ultimo() can take additional parameters to specify an other time on this day
define at_ultimo at *{at_ultimo(12,23,45)} set lamp1 off
This will create an at device which will be executed ad 12:34:45 on the last day of month.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

DocCyber

Zitat von: Christoph Morrison am 26 März 2024, 21:35:40Ich war mal so frei und habe eine bessere Version von ultimo geschrieben, die unabhängig von Timezone, Locale und zusätzlichen Modulen ist
Klasse - sehr professionell! Danke!
Behandle die Menschen so, als wären sie, was sie sein sollten. Dadurch hilfst du ihnen zu werden, was sie sein können. (Goethe)


RPi-3 mit HM-CFG-LAN und jede Menge HM Komponenten.